Design Language

Color

Two gardens, one palette, and the glass between them.

Two gardens

Phloom has two palettes, and they are not a light theme and its inverted copy. Garden Morning is warm cream paper at oklch(0.985 0.005 80), not white; the hue is on the yellow side, so the whole light theme reads as paper rather than as a screen. Twilight Garden is a deep plum-violet, not neutral black, so the pink sits in the same family as its background instead of glowing off it.

Everything is authored in oklch. That is load-bearing rather than fashionable: the team palette is derived from one hue with oklch(from var(--brand) L C h) and calc(h - 30) style hue rotations, which is only possible in a perceptual space where holding lightness constant actually holds lightness constant.

Every value below is read out of the running document, so this page cannot disagree with the stylesheet. The number after resolvedis the browser's own canonical form, not the authored one: Tailwind registers theme colours as <color>, so Chrome reports lab(). The oklch() you would edit lives in app/globals.css. The chip itself is painted with var(--token), so the swatch is the browser resolving the variable rather than us re-serialising it.

Surfaces
Background--backgroundresolved The page itself. Cream in light, deep plum in dark.
Foreground--foregroundresolved Body text. Never pure black or pure white.
Card--cardresolved Opaque raised surface.
Card foreground--card-foregroundresolved
Popover--popoverresolved Menus and floating panels.
Popover foreground--popover-foregroundresolved
Muted--mutedresolved Recessed fills: inert tiles, table zebra, inset panels.
Muted foreground--muted-foregroundresolved Secondary copy. The most-used text colour after --foreground.
Border--borderresolved An alpha value, not a solid: it tints whatever it sits on.
Input--inputresolved Slightly stronger than --border so fields read as editable.
Brand
Brand--brandresolved Phloom Pink. Re-pointed per team by TeamPaletteProvider.
Brand foreground--brand-foregroundresolved Text and lettering placed ON brand pink.
Primary (alias of brand)--primaryresolved
Primary foreground--primary-foregroundresolved
Accent--accentresolved Derived: oklch(from var(--brand) 0.94 0.02 h). Hover tint.
Accent foreground--accent-foregroundresolved
Ring--ringresolved Focus. A colour-mix of --brand, so focus follows the team.
Secondary--secondaryresolved Quiet fills that must not read as brand.

--primary is an alias of --brand, which is what keeps shadcn's primary-* classes on-brand without a second colour to maintain. Interactive controls use bloom pink; there is no separate lavender accent any more.

Semantic roles come in pairs

A status colour is never used on its own. Each one has a foreground token measured against it, and the pair is the unit you reach for. Reaching for --warn without --warn-foreground is how you end up with amber text on amber, which is why these are shown as plates rather than as chips.

SuccessCompleted, healthy, online
--success: --success-foreground:
WarnDegraded, needs attention
--warn: --warn-foreground:
InfoNeutral notice, in progress
--info: --info-foreground:
DestructiveDelete, revoke, irreversible
--destructive: --destructive-foreground:
BrandPrimary action, brand moment
--brand: --brand-foreground:
MutedRecessed, inert, secondary
--muted: --muted-foreground:
the same roles on live components
DefaultSecondaryOutlineDestructive

These are the real Badge and Button primitives, not restyled copies. If a variant changes, this row changes with it.

The glass system

The app shell is frosted glass: a translucent slab with a blur behind it, a sheen across the top, and an inner hairline that catches light on the upper edge. It is built from four layers, and the reason it holds up is that none of them is a single flat colour. --shell-glass-bg is a 168° gradient from white through white to a brand-tinted corner, so the slab has a direction to it.

The one rule that matters when extending it: never feed a backdrop-filter chain into an SVG filter. That combination lags by design. Refract a static copy through a CSS filter instead.

Glass surfaces
Chrome--glass-chromeresolved Sidebar and topbar base translucency.
Chrome strong--glass-chrome-strongresolved Where nav has to stay legible over busy content.
Overlay--glass-overlayresolved Page-interior cards. This section is drawn on it.
Glass border--glass-borderresolved The hairline around a glass surface.
Panel border--shell-panel-borderresolved
Panel ring--shell-panel-ringresolved
the shell's own glass, over something busy
Chrome slab.shell-panel-surface
Floating panel.shell-floating-panel
Page cardbg-glass-overlay/45

The plate underneath is a brand-hued gradient standing in for page content. Glass only reads as glass when there is something behind it to blur, which is why a swatch cannot show this and a specimen can.

Colour derived from one hue

A team can set its own brand colour, and the shell follows without a second palette being authored. Thirty-odd tokens are stated as functions of --brand rather than as values, so re-pointing one variable rotates the scrollbar rail, the sidebar ambient wash, the focus ring, the accent tint, and the glass corners together.

The offsets are chosen so the default pink reproduces the hand-tuned original exactly. calc(h - 30) in the scrollbar preserves the warm-to-deep travel the original 350-to-320 gradient had, at any hue.

Derived from --brand
Accent--accentresolved oklch(from var(--brand) 0.94 0.02 h): brand hue, near-white lightness.
Ring--ringresolved color-mix(in oklch, var(--brand) 50%, transparent).
Sidebar ambient A--sidebar-ambient-aresolved Brand hue at 0.78 L.
Sidebar ambient B--sidebar-ambient-bresolved calc(h + 45), the warm side of the fan.
Sidebar ambient C--sidebar-ambient-cresolved calc(h - 60), the cool side.
Chart series
Series 1--chart-1resolved
Series 2--chart-2resolved
Series 3--chart-3resolved
Series 4--chart-4resolved
Series 5--chart-5resolved

Five hues, spaced for categorical data rather than for a gradient. Series 1 is brand pink, so the first series in any chart is on-brand by default.

Bloom backdrop
Bloom stop 1--bloom-stop-1resolved
Bloom stop 2--bloom-stop-2resolved

Two stops consumed by both the body::before pseudo-element and the BloomBackdrop component, so a palette change reaches both implementations without touching either.

Theming

Theme is a class on <html>, set by next-themes with defaultTheme="system". Every token above is declared twice, once in :root and once in .dark, which is why the 144 token names come to 221 declarations.

Switching themes plays a frost veil: an opacity-only overlay that covers, swaps mid-cover, and uncovers, so the change never reads as a flash of the wrong colours. It is opacity-only on purpose. A blur there would be the expensive thing in the one moment the whole page is repainting.

Everything on this page re-reads itself when the class changes, so the theme toggle in the header above is the demo. Flip it and watch the values, not just the chips.

what the two themes are

Garden Morning

Warm cream paper, ink on the plum side of black.

Twilight Garden

Deep plum-violet, so the pink belongs to its background.

Both plates are painted with the same token names. Only the theme class differs, and the dark plate forces it locally.