Skip to content
Facade UI
Colour mode
GitHub (opens in a new tab)
filetheme

Design tokens

The Facade token layer: shadcn-compatible colour names plus --facade-* display type, section rhythm and motion tokens. Import it from your globals.css.

Installation

Package manager
pnpm dlx shadcn@latest add https://facadeui.dev/r/tokens.json

Source

What the CLI copies into your project, byte for byte.

styles/facade-tokens.css
/**
 * Facade UI — design tokens.
 *
 * Two token families live here:
 *
 * 1. shadcn-compatible names (`--background`, `--primary`, `--radius`, …). Facade
 *    sections only ever reference these, so dropping a section into an existing
 *    shadcn project makes it inherit that project's theme with zero edits.
 * 2. Marketing-specific tokens under a `--facade-` prefix (display type, section
 *    rhythm, motion) that shadcn does not define and that would otherwise collide.
 *
 * a11y: `--ring` is deliberately darker than shadcn's default so the focus ring
 * clears 3:1 against `--background` (WCAG 2.2 AA, non-text contrast).
 */

@import "tailwindcss";

@custom-variant dark (&:is(.dark *));

:root {
  /* ---------------------------------------------------------------- shadcn */
  --radius: 0.625rem;

  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.145 0 0);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.145 0 0);
  --primary: oklch(0.205 0 0);
  --primary-foreground: oklch(0.985 0 0);
  --secondary: oklch(0.97 0 0);
  --secondary-foreground: oklch(0.205 0 0);
  --muted: oklch(0.97 0 0);
  --muted-foreground: oklch(0.505 0 0);
  --accent: oklch(0.97 0 0);
  --accent-foreground: oklch(0.205 0 0);
  --destructive: oklch(0.577 0.245 27.325);
  --destructive-foreground: oklch(0.985 0 0);
  --border: oklch(0.922 0 0);
  /*
   * `--input` is the border of a form control, not a divider, so it has to
   * clear 3:1 against the background on its own (WCAG 1.4.11). That is a good
   * deal darker than `--border`, and deliberately so — a hairline is decorative,
   * the edge of a text field is the only thing identifying it as one.
   */
  --input: oklch(0.78 0 0);
  --ring: oklch(0.45 0 0);

  /* ---------------------------------------------------------------- facade */
  --facade-font-sans:
    ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
    sans-serif;
  --facade-font-mono:
    ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* Fluid display scale. Marketing headlines should not need breakpoint juggling. */
  --facade-text-display-sm: clamp(1.75rem, 1.45rem + 1.4vw, 2.25rem);
  --facade-text-display-md: clamp(2.25rem, 1.75rem + 2.4vw, 3rem);
  --facade-text-display-lg: clamp(2.75rem, 1.95rem + 3.8vw, 4rem);
  --facade-text-display-xl: clamp(3.25rem, 2.1rem + 5.4vw, 5.5rem);

  --facade-leading-display: 1.08;
  --facade-tracking-display: -0.022em;

  /* Vertical rhythm. `--facade-section-y` is the default band padding. */
  --facade-section-y-sm: clamp(2.5rem, 1.75rem + 3vw, 4rem);
  --facade-section-y: clamp(3.5rem, 2.25rem + 5vw, 7rem);
  --facade-section-y-lg: clamp(5rem, 3rem + 8vw, 10rem);

  --facade-container-max: 80rem;
  --facade-container-gutter: 1.25rem;

  /* Motion. Durations are read by FacadeMotionProvider and fed to MotionConfig. */
  --facade-duration-fast: 150ms;
  --facade-duration-base: 320ms;
  --facade-duration-slow: 620ms;
  --facade-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --facade-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --facade-ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
  --facade-motion-distance: 1rem;
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --card: oklch(0.205 0 0);
  --card-foreground: oklch(0.985 0 0);
  --popover: oklch(0.269 0 0);
  --popover-foreground: oklch(0.985 0 0);
  --primary: oklch(0.922 0 0);
  --primary-foreground: oklch(0.205 0 0);
  --secondary: oklch(0.269 0 0);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.782 0 0);
  --accent: oklch(0.371 0 0);
  --accent-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.704 0.191 22.216);
  --destructive-foreground: oklch(0.145 0 0);
  --border: oklch(1 0 0 / 12%);
  --input: oklch(0.74 0 0);
  --ring: oklch(0.78 0 0);
}

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);

  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
  --radius-2xl: calc(var(--radius) + 10px);

  --font-sans: var(--facade-font-sans);
  --font-mono: var(--facade-font-mono);

  /* `text-display-lg` and friends. */
  --text-display-sm: var(--facade-text-display-sm);
  --text-display-sm--line-height: var(--facade-leading-display);
  --text-display-sm--letter-spacing: var(--facade-tracking-display);
  --text-display-md: var(--facade-text-display-md);
  --text-display-md--line-height: var(--facade-leading-display);
  --text-display-md--letter-spacing: var(--facade-tracking-display);
  --text-display-lg: var(--facade-text-display-lg);
  --text-display-lg--line-height: var(--facade-leading-display);
  --text-display-lg--letter-spacing: var(--facade-tracking-display);
  --text-display-xl: var(--facade-text-display-xl);
  --text-display-xl--line-height: var(--facade-leading-display);
  --text-display-xl--letter-spacing: var(--facade-tracking-display);

  /* `py-section`, `max-w-facade`. */
  --spacing-section-sm: var(--facade-section-y-sm);
  --spacing-section: var(--facade-section-y);
  --spacing-section-lg: var(--facade-section-y-lg);
  --spacing-gutter: var(--facade-container-gutter);
  --container-facade: var(--facade-container-max);

  /* `ease-facade-out`, `duration-facade-base`. */
  --ease-facade-out: var(--facade-ease-out);
  --ease-facade-in-out: var(--facade-ease-in-out);
  --ease-facade-spring: var(--facade-ease-spring);
}

/**
 * `duration-facade-*` — Tailwind v4 has no `--duration-*` theme namespace, so the
 * three motion durations are exposed as explicit utilities instead.
 */
@utility duration-facade-fast {
  transition-duration: var(--facade-duration-fast);
}
@utility duration-facade-base {
  transition-duration: var(--facade-duration-base);
}
@utility duration-facade-slow {
  transition-duration: var(--facade-duration-slow);
}

@layer base {
  *,
  ::after,
  ::before,
  ::backdrop,
  ::file-selector-button {
    border-color: var(--color-border);
  }

  body {
    background-color: var(--color-background);
    color: var(--color-foreground);
    font-family: var(--font-sans);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
  }

  /* Every interactive element in the registry relies on this ring. */
  :focus-visible {
    outline: 2px solid var(--color-ring);
    outline-offset: 2px;
  }

  /*
   * Belt and braces alongside MotionConfig reducedMotion="user": kills CSS
   * transitions and scroll behaviour for users who ask for less motion.
   */
  @media (prefers-reduced-motion: reduce) {
    *,
    ::before,
    ::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
  }
}