Layout UI

Theming

The Layout UI token contract: how --layout-* canonical tokens, shadcn compatibility, dark mode, brand scoping, and density all fit together.

The token contract

Layout UI defines a canonical --layout-* token namespace. Components never reference raw values or arbitrary colours. They always reference intent tokens like --layout-primary or --layout-border. Because no component hard-codes a value, changing a token changes every surface that consumes it.

TokenTailwind utility resolves toPurpose
--layout-bgbg-backgroundPage background
--layout-fgtext-foregroundPrimary body text
--layout-surfacebg-cardRaised surface (cards)
--layout-overlaybg-popoverFloating layers
--layout-primarybg-primary / text-primaryPrimary actions, CTAs
--layout-primary-fgtext-primary-foregroundText on primary
--layout-secondarybg-secondarySecondary actions
--layout-mutedbg-mutedSubdued fills
--layout-muted-fgtext-muted-foregroundSecondary text
--layout-accentbg-accentHover fills, tonal containers
--layout-dangerbg-destructiveDestructive / error state
--layout-successbg-successSuccess state
--layout-warningbg-warningWarning state
--layout-borderborder-borderAll borders and dividers
--layout-inputborder-inputForm field borders
--layout-ringring, outline-colorFocus ring colour
--layout-radius--radius-lg (base)Base border-radius; all variants derive from this
--layout-shadow-smshadow-smCard-level elevation
--layout-shadow-mdshadow-mdDropdown elevation
--layout-duration-fastduration-[var(--layout-duration-fast)]Quick micro-interactions (100ms)
--layout-duration-baseduration-[var(--layout-duration-base)]Standard transitions (150ms)
--layout-ease-outease-outDeceleration easing
--layout-space-unit--spacing (Tailwind base)All spacing utilities scale from this unit

shadcn compatibility

Every Tailwind utility in Layout UI resolves through a fallback chain. For example, bg-background maps to:

--color-background: var(--background, var(--layout-bg));

If a shadcn or tweakcn theme sets --background, that value wins. Without one, the --layout-bg default drives the colour. You can drop Layout UI components into any shadcn project without any token conflict. The host theme simply takes precedence.

Dark mode

Dark mode is applied by adding .dark or data-theme="dark" to the <html> element. The Layout token contract ships dark values for every token out of the box. Use the ThemeToggle component from this site as a reference implementation.

/* globals.css, auto-included with @layout/theme-layout */
@custom-variant dark (
  &:where(.dark, .dark *, [data-theme="dark"], [data-theme="dark"] *)
);

[data-theme="dark"] {
  --layout-bg: oklch(0.17 0.004 95);
  --layout-primary: oklch(0.93 0.004 95);
  /* ... all tokens redefined for dark ... */
}

Brands and scoped reskinning

A brand is simply a CSS block that redefines --layout-* tokens under a [data-brand] selector. Set the attribute on <html> to reskin the entire app, or on any wrapper element to scope the reskin to that subtree.

The three demos below are rendered on the server with scoped data-brand wrappers: the same Button, Card, Badge, and Input components, zero changes:

data-brand="stripe" · Stripe kit

DefaultSuccess
Sign in
Enter your email to continue.

data-brand="linear" · Linear kit

DefaultSuccess
Sign in
Enter your email to continue.

data-brand="notion" · Notion kit

DefaultSuccess
Sign in
Enter your email to continue.
/* How a brand is defined in themes.css */
[data-brand="stripe"] {
  --layout-bg: #f6f9fc;
  --layout-primary: #635bff;
  --layout-primary-fg: #ffffff;
  --layout-radius: 0.375rem;
  --layout-font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  /* ... all tokens ... */
}

Layout kits from layout.design are compiled from design tokens and style profiles extracted from real products. Install a kit and apply its brand attribute to instantly reskin your project.

Every kit in the gallery is available in the brand switcher in the top bar: the entire gallery compiles into theme blocks automatically. In this repo, npm run sync:kits fetches all published kits from the layout.design API, maps each kit's style profile (16 brand colours, radii, shadows, density, mode) onto the token contract, and regenerates kit-themes.css plus the brand manifest. A new kit published to the gallery becomes a theme by re-running the sync; no component work, ever.

Density

Density is controlled by a single CSS custom property, --layout-space-unit, which maps to Tailwind's --spacing base unit. Every spacing utility (p-4, gap-2, h-9) scales proportionally, with no per-component overrides needed.

/* Comfortable (default) */
:where(:root) {
  --layout-space-unit: 0.25rem;   /* 1 spacing unit = 4px */
}

/* Compact */
[data-density="compact"] {
  --layout-space-unit: 0.215rem;  /* ~14% tighter across all UI */
}

Apply compact density by setting data-density="compact" on <html>. The DensityToggle in this site's top bar is a working example.