Treat Flowbite Svelte and its theme tokens as the frontend's single source of visual truth
Context and Problem Statement
ADR 0002 upgraded web/ to Tailwind
CSS v4 and flowbite-svelte 1.x as a pure version bump, carrying the existing markup across mechanically with
no visual changes intended. Reviewing that markup afterward surfaced a set of small inconsistencies that had
accumulated across the app: <br /> used as a layout primitive instead of Tailwind spacing utilities;
Flowbite's semantic Alert/Card color props overridden with raw Tailwind classes in a way that actually
broke dark-mode styling (tailwind-merge replaces the light-mode classes but leaves the theme's dark:
classes in place, producing a saturated box in light mode and a washed-out one in dark mode); the same
"system error" alert markup duplicated across three files; per-status icon/color mapping duplicated and
disagreeing between the two ArgoCD status components; page headings sometimes using Flowbite's Heading
component and sometimes a hand-styled <h5>; a Navbar color="form" prop that does nothing (Navbar has no
color prop in 1.x — it silently falls through to a DOM attribute); and every prominent call-to-action using a
stock GradientButton color="pinkToOrange" gradient with no relationship to the custom coral
--color-primary-* ramp defined in app.css. None of this was a functional bug, but left unaddressed each of
these was a template for the next component to copy.
Decision Drivers
- AGENTS.md's testing philosophy of verifying observable behavior implies a parallel expectation for the visual layer: a consistent design language should be enforced by convention, not left to accumulate drift component by component.
app.cssalready defines a real brand palette (--color-primary-*, coral#fe795dat 500) that most of the UI wasn't using — the highest-leverage fix available was making the app actually use the token investment already made, rather than inventing a new one.tailwind-merge(which Flowbite Svelte'stv()-based theming uses internally) makes a raw-Tailwind override of a semantic color prop something worse than redundant — as observed with the red alert above, it can silently break the light/dark pairing a component's theme was designed to keep together.- The project has no visual regression suite (noted as a known gap in ADR 0002), so preventing drift up front is cheaper than catching it later.
Considered Options
- Leave the inconsistencies as found and address them opportunistically as each is next touched.
- Migrate off Flowbite Svelte to a different component library as part of fixing this.
- Formalize Flowbite Svelte's components and
app.css's theme tokens as the single source of visual truth, and fix the concrete inconsistencies found under that rule.
Decision Outcome
Chosen option: formalize Flowbite Svelte + app.css theme tokens as the source of truth, with a concrete rule
against overriding a component's semantic color prop with raw Tailwind color classes, and fix the
inconsistencies found. Specifically:
- Every layout gap uses a Tailwind spacing utility (
space-y-*,mt-*/mb-*) on a container or element —<br />is reserved for its actual purpose (a line break inside a run of text), not page layout. - The duplicated "System error!" alert became one component,
lib/ui/components/ErrorAlert.svelte(<Alert color="red" border>, no class override), used byApplicationsGrid.svelte,AppManifests.svelte, androutes/diffs/+page.svelte. - The duplicated, disagreeing status→icon/color mapping in
ArgoCDHealthStatus.svelte/ArgoCDSyncStatus.sveltebecame one helper,lib/ui/status.ts'sstatusAppearance(), with both components reduced to thin wrappers and a single, deliberate fallback (amber "unknown") instead of two different accidental ones. - All page/card headings use Flowbite's
Headingcomponent; hand-rolled heading markup was removed. - The dead
Navbar color="form"prop was removed; primary call-to-action buttons switched fromGradientButton color="pinkToOrange"to aButton color="primary"with abg-gradient-to-br from-primary-400 to-primary-700overlay — colorful, but built from the theme's own ramp instead of an unrelated stock preset. - Nav-link active-state highlighting (
NavUl/NavLiinHeader.svelte) was fixed to actually work — thehrefs needed a trailing slash to matchpage.url.pathnameunder this app'strailingSlash = 'always'config, which had silently made two of the three nav links permanently non-highlightable — and restyled tofont-bold text-black dark:text-white(a legibility choice made after visual review, not the initial primary-colored-background attempt).
Consequences
- Good, because the app now actually uses the brand palette it already pays the token-maintenance cost for,
and retheming (changing
--color-primary-*) now visibly affects the whole app, not just a few buttons. - Good, because the shared
ErrorAlertandstatusAppearance()mean the next error surface or status value gets consistent treatment for free instead of by copy-paste. - Neutral, because this is a convention, not something enforced by tooling — a future PR can still add a raw Tailwind color override; there is no lint rule for it (see More Information for a possible follow-on).
- Bad, because Flowbite's
secondarycolor (onButton/Badge/Alert) has no backing--color-secondary-*tokens inapp.cssorflowbite/plugin, so it renders unstyled if ever reached for — a trap for a future contributor that this decision does not close, only documents.
Pros and Cons of the Options
Leave as-is, fix opportunistically
- Good, because it requires no dedicated effort right now.
- Bad, because "opportunistic" is exactly how the inconsistencies accumulated in the first place — nothing about touching one file next incentivizes fixing an unrelated one.
Migrate off Flowbite Svelte
- Good, because it would be a chance to pick a smaller-surface library if Flowbite's pace becomes a problem.
- Bad, because it is the same much-larger, unrelated migration ADR 0002 already rejected for this reason, and none of the problems found here are caused by Flowbite itself — they're caused by not using it consistently.
Formalize Flowbite + theme tokens as source of truth (chosen)
See Decision Outcome.
More Information
- Implementation plan: Flowbite design consistency pass
- Related: ADR 0002 (the upgrade this pass follows), ADR 0008 (a related but distinct decision from the same review, about when the UI performs network actions rather than how it looks)
- A lint rule or code-review checklist item against raw-Tailwind color-class overrides on Flowbite components would make this decision self-enforcing instead of convention-only; not implemented here.