230. Flowbite Pro is adapted onto Tailwind 4 and Flowbite 4, a brand's colour is the primary scale, and one capability judges every repository's interface against it
Context
On 2026-10-28 the owner ordered that every one of their projects with a user interface is
built on their licensed Flowbite Pro: documented, enforced, back-propagated to every
repository and verified. An audit of about thirty repositories that day found every way of
missing it. Some used free Flowbite with a hand-rolled shell. Some loaded Tailwind from the
Play CDN. Some mixed in daisyUI, Preline, TW Elements or shadcn/Radix. Some wired the
drawer through a hand-written script instead of Flowbite's JavaScript. This repository's
own site re-implements Flowbite's navigation in site/nav.js.
Two facts make the order more than a checklist:
- The licensed templates target an older stack. The Admin Dashboard 1.1.1 that the
owner's adaptation (poppyhead) starts from targets
tailwindcss ^3andflowbite ^1.5. It configures the theme intailwind.config.js: the plugin underplugins, Flowbite undercontent,primaryundertheme.extend.colors, anddarkMode: "class". The repositories that are current build with Tailwind 4, which is configured in CSS, and with Flowbite 4. - Several products are brand-driven. catharsis, astronomists, unable.cz, u-tygra and
vomaste.cz each have a colour of their own, and some are dark only. Pro's palette is one
blue
primaryover grays.
A rule nobody can run drifts the way the audit found. The owner's skill ships an interim
scanner (verify.mjs), but it checks only dependencies and whether the rule file exists.
Decision
-
Pro's structure, adapted onto Tailwind 4 and Flowbite 4. A repository takes Pro's markup and class lists and builds them with Tailwind 4 and Flowbite 4. It does not pin Pro's Tailwind 3 stack. The v3 configuration maps one to one:
Pro (Tailwind 3) Tailwind 4 / Flowbite 4 plugins: [require('flowbite/plugin')]@plugin "flowbite/plugin";in the stylesheet that imports Tailwindcontent: ['./node_modules/flowbite/**/*.js']@source "../node_modules/flowbite";theme.extend.colors.primary@theme { --color-primary-50: …; … --color-primary-950: …; }darkMode: 'class'@custom-variant dark (&:where(.dark, .dark *));fontFamily.sans: ['Inter', …]@theme { --font-sans: 'Inter', …; }transitionProperty.widthtransition-[width]safelist: [...]@source inline("...");Pro's markup uses utilities that Tailwind 4 renamed, so the markup is renamed when it is adapted, never shimmed:
Tailwind 3 Tailwind 4 shadow-smshadow-xsshadowshadow-smrounded-smrounded-xsroundedrounded-smringring-3outline-noneoutline-hiddenflex-shrink-0shrink-0bg-opacity-50the colour's alpha, as in bg-gray-900/50The
data-*attributes that Flowbite's JavaScript drives (drawer, dropdown, modal, tabs, collapse, tooltip) are unchanged, and so is the shell: a fixed navbar, the sidebar drawer, the breadcrumb page header,gray-50/gray-900pages,white/gray-800surfaces andgray-200/gray-700borders. -
A brand's colour is Tailwind's
primaryscale. A brand-driven product declares its colour as--color-primary-*and keeps Pro's structure and grays. It never adds a second accent, and its surfaces stay on the Pro shell. A design system that names surfaces by role, such as this repository's--mj-*tokens, uses the role aliases the kit profile names (bg-page,bg-surface,border-default). The check accepts an alias in place of the raw grays, so a token rule and the kit do not compete. The aliases are the profile's alone: a repository's policy adds none, and a new alias is a change of the profile in the distribution. -
One capability judges it, from markers.
design.kit(CLImajordomus design kit, MCPmajordomus_design_kit; no HTTP route, since itsevidenceinput names a file the process opens) judges a repository's tree against a kit profile inshare/design/kits/. It has six signals:- S0: whether there is a user interface at all;
- S1: the dependency;
- S2: the plugin and its source;
- S3: Flowbite's JavaScript behind every interactive attribute;
- S4: no other kit;
- S5: the shell on every root layout.
S0 cuts the tree into surfaces: the root's manifest (or the one in
assets/), every workspace the root declares, every nested app manifest with markup in its own subtree, and every Mix project on Phoenix LiveView. Nothing under examples, fixtures or tests is a surface, and its manifests and Mix projects count for nothing, S4 and the stacks included. Published pages count as an interface whatever builds them. A manifest that is no surface is printed as a notice, never judged.The tree is the repository's files: in a Git work tree the tracked ones and the untracked ones Git does not ignore. A Git submodule, and a Git repository nested in the work tree that it does not track, is judged in its own repository, as every repository is: the answer names each one in a notice, and nothing below it is read here. A workspace of many submodules (prismatic-platform's
garden/) is judged one repository at a time, with--repo. The profile's markers are of two kinds. Wiring markers connect the kit to a build: the package, the plugin and source, the script that loads it. Usage markers show it in use: the attributes its JavaScript drives, its React components, its API. A surface passes S1 to S3 only with usage in its own files, because a dependency nobody uses proves nothing.Each finding names its signal, path, evidence and remedy.
--checkexits 10 on a finding. A repository without a user interface passes vacuously. A policy that cannot be read exits 2.The policy side is the
ui:block the design licence declares (I4336), which both judges read:ui.kitnames the profile (empty, or absent, when no kit is adopted, and flowbite-pro is then the profile judged), andui.exemptlists the exemptions, each exactly{path, because}. An exemption moves every finding at its path to the exempted list, and every answer lists it with its reason. The root layouts, the role aliases and which layouts are a marketing page are the profile's alone, and the policy declares none of them: an exemption names no signal and no shell. A published site's layout (undersite/,www/ordocs/) is judged as a marketing page, by a navbar and a footer. A Phoenix layout module (layouts.ex) is judged with the app and root templates it embeds, never with another template of its directory, such as an email's. -
A repository adopts it with one command.
majordomus design kit adopt flowbite-prowrites three things and nothing that is already there:- the rule
project.ui-uses-flowbite-pro, from the distribution's template; ui.kit: flowbite-pro, set in the policy'sui:block when it names no kit (the skeleton seeds it empty) or the block appended when there is none. Aui.kitnaming another profile is refused, andui.exemptis left to the repository;- the enforcement entries
design-kit-on-push(pre-push) anddesign-kit(the CI workflow).
majordomus doctornames each entry until the hook and the workflow invoke it, and reads the adopted policy, whose keys the schema declares. - the rule
-
Licence: markers only. The profile names attributes, class lists, package names and file shapes. No Pro template, block, stylesheet or script is copied into this repository, the distribution or a published page.
A licensed kit's profile also declares its licensed markers: what in a tree is the licensed distribution itself, such as a directory or a manifest named after a Pro product, or a dependency on a Pro package (a name under dependencies, devDependencies, peerDependencies or optionalDependencies, never another key of a manifest). Where a tree carries one, the design licence decides it.
licence_answerasks it through its library function (design::licence::assess_root, the same judgement asmajordomus design licence) and maps its verdict as agreed with I4336:activeandinactivepass through,refusedstays refused, andnot_judged, or a question it cannot answer at all, is undecided. Onlyactivepasses:inactiveandrefusedmake the marker unlicensed, and an undecided licence is a finding no exemption moves, never a pass. -
The adopted rule keeps a section of the repository's own. The template ends with
# In this repository. Adoption writes it once, and a later adoption brings the rest of the rule up to the template without overwriting that section.
Consequences
- Every supervised repository gets the same gate in one command, and an unsupervised one
can be judged with
--repowithout Majordomus being installed into it. The decision to supervise it stays the owner's. - The judgement is pure over a tree built in memory, so each signal is a unit test, and case 4334 proves each signal by mutation in a disposable repository.
- This repository does not adopt the rule in the same change. Its site's navigation is
hand-written (
site/nav.js), and the site would fail S3 and S5 today. Adoption here is a later slice that moves the site, the Cockpit and/uionto the Pro shell, mapping Tailwind 4 onto the--mj-*roles as decision 2 allows. - The markers are heuristics over text, not a renderer. A shell that is assembled at run time from components the profile does not name will read as missing. Such a layout is exempted with its reason, or its shape is added to the profile, never passed silently.
- Moving the policy side under
ui:retired three per-repository knobs: an exemption'ssignals(an exemption now covers every finding at its path), itsshell: marketing(the profile names the marketing layouts) and the block'slayoutsandroles(the profile names them). A repository that needed one of them asks for a change of the profile, which every repository on the kit then shares. - A Tailwind 3 configuration through flowbite-react's helper passes S2 only by calling
.plugin()and.content()on the identifier it binds toflowbite-react/tailwind, matched by name because the expression engine has no backreference.
Alternatives rejected
- Pinning Pro's Tailwind 3 stack. It would freeze every repository on a major version that the current ones have left, and it would fork the configuration of each product that already builds with Tailwind 4.
- Copying the Pro templates into
share/for the check to compare against. That is the one thing the licence forbids, and it would publish the templates with every release of the distribution. - The skill's scanner as the gate. It runs on one machine, it is not a projection of the registry, and it cannot be required by CI or by a pre-push hook in the repositories it judges.
Verification
cargo test --lib design::kitcovers every signal, the vacuous pass, exemptions, the marketing shell, policy parsing and idempotent adoption, a Mix project under examples, a submodule and a nested repository, the dependency marker, the bound flowbite-react helper, the templates an embedded layout is judged with, and the licence seam against the design licence's own judgement.test/cases/4334_the_ui_kit_is_checked_by_one_capability.shmutates a conformant disposable repository once per signal and expects each finding and exit code. It adopts twice to prove idempotence, and refuses an exemption without a reason. It also proves a published page, docs/ published only by a publish_dir or Pages step, a nested repository named in a notice, a workspace that is wired and uses nothing, a manifest that is only a notice, adoption settingui.kitin the skeleton'sui:block and the check passing under it, a licensed marker that is unlicensed before adoption and undecided after it, aui.exemptentry doctor accepts, and the rule's own section surviving a second adoption.