Skip to content

Tailwind CSS + shadcn/ui — Design Tokens

OWNER: alexander ALSO_USED_BY: floris, floor LAST_VERIFIED: 2026-03-26 GE_STACK_VERSION: tailwindcss ^4


Overview

Design tokens are the single source of truth for visual decisions in every GE project. Tailwind v4 defines tokens in CSS via @theme — they auto-generate utility classes AND CSS variables. Agents use this page when creating or modifying project themes, color palettes, or spacing systems.


Token Architecture (Three Layers)

GE uses a three-layer token system for every client project:

Layer 1: Base Tokens (raw values)

Defined in @theme. These are the primitive palette.

@import "tailwindcss";

@theme {
  /* Base color scale — OKLCH for perceptual uniformity */
  --color-gray-50: oklch(98% 0.005 250);
  --color-gray-100: oklch(95% 0.005 250);
  --color-gray-200: oklch(90% 0.005 250);
  --color-gray-300: oklch(82% 0.005 250);
  --color-gray-400: oklch(65% 0.01 250);
  --color-gray-500: oklch(50% 0.01 250);
  --color-gray-600: oklch(40% 0.01 250);
  --color-gray-700: oklch(32% 0.01 250);
  --color-gray-800: oklch(25% 0.01 250);
  --color-gray-900: oklch(18% 0.01 250);
  --color-gray-950: oklch(12% 0.01 250);

  /* Brand — populated per client */
  --color-brand-50: oklch(97% 0.02 250);
  --color-brand-500: oklch(55% 0.16 250);
  --color-brand-600: oklch(48% 0.14 250);
  --color-brand-700: oklch(40% 0.12 250);

  /* Base spacing — 0.25rem increments (Tailwind v4 default) */
  --spacing: 0.25rem;

  /* Font families */
  --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, monospace;
}

CHECK: Base tokens use OKLCH color format. IF: A designer provides HEX or HSL values. THEN: Convert to OKLCH before adding to @theme — OKLCH produces perceptually uniform scales.

Layer 2: Semantic Tokens (purpose-driven)

Defined in :root and [data-theme="dark"]. These map meaning to base tokens.

:root {
  --background: var(--color-gray-50);
  --foreground: var(--color-gray-900);
  --card: var(--color-gray-50);
  --card-foreground: var(--color-gray-900);
  --primary: var(--color-brand-500);
  --primary-foreground: oklch(100% 0 0);
  --secondary: var(--color-gray-100);
  --secondary-foreground: var(--color-gray-900);
  --muted: var(--color-gray-100);
  --muted-foreground: var(--color-gray-500);
  --accent: var(--color-gray-100);
  --accent-foreground: var(--color-gray-900);
  --destructive: oklch(55% 0.22 27);
  --destructive-foreground: oklch(100% 0 0);
  --border: var(--color-gray-200);
  --input: var(--color-gray-200);
  --ring: var(--color-brand-500);
  --radius: 0.5rem;
}

[data-theme="dark"] {
  --background: var(--color-gray-950);
  --foreground: var(--color-gray-50);
  --card: var(--color-gray-900);
  --card-foreground: var(--color-gray-50);
  --primary: var(--color-brand-500);
  --primary-foreground: var(--color-gray-950);
  --border: var(--color-gray-800);
  --input: var(--color-gray-800);
}

CHECK: Semantic tokens reference base tokens via var(). IF: A semantic token hardcodes a raw color value. THEN: Extract the raw value to @theme as a base token first.

ANTI_PATTERN: Defining semantic tokens inside @theme. FIX: @theme is for base tokens only. Semantic tokens go in :root / [data-theme].

Layer 3: Component Tokens (optional, per-component)

Used only when a component needs overridable sub-values.

:root {
  --sidebar-width: 16rem;
  --sidebar-collapsed-width: 4rem;
  --header-height: 4rem;
  --toast-max-width: 24rem;
}

CHECK: Component tokens are defined. IF: The value is used by only one component and never needs overriding. THEN: Use a Tailwind utility directly — no token needed.


@theme vs :root — Decision Rule

Use @theme Use :root
Value should generate a Tailwind utility class Value is internal / structural
Colors, fonts, spacing scales, breakpoints Semantic mappings, component dimensions
Shared across all themes Changes between light/dark/custom themes

CHECK: You are adding a new CSS variable. IF: You want bg-{name}, text-{name}, etc. to work. THEN: Define it in @theme with the correct namespace (--color-*, --font-*, etc.). IF: You only need var(--name) access. THEN: Define it in :root.


Color Palette Conventions

GE projects use a consistent naming pattern:

Token Purpose
--color-brand-* Client brand color scale (50-950)
--color-gray-* Neutral scale (50-950)
--color-success Green confirmation
--color-warning Amber caution
--color-error Red destructive
--color-info Blue informational

CHECK: A status color is needed. IF: Using red for errors, green for success, etc. THEN: Use semantic names (error, success) not raw colors (red-500).

ANTI_PATTERN: Using Tailwind's default red-500, green-500 for status colors. FIX: Map to semantic tokens: --color-error, --color-success.


Typography Scale

@theme {
  --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, monospace;

  --text-xs: 0.75rem;
  --text-xs--line-height: 1rem;
  --text-sm: 0.875rem;
  --text-sm--line-height: 1.25rem;
  --text-base: 1rem;
  --text-base--line-height: 1.5rem;
  --text-lg: 1.125rem;
  --text-lg--line-height: 1.75rem;
  --text-xl: 1.25rem;
  --text-xl--line-height: 1.75rem;
  --text-2xl: 1.5rem;
  --text-2xl--line-height: 2rem;
}

CHECK: Font imports are handled. IF: Using a Google Font or custom font. THEN: Import via @font-face in CSS — never via <link> in HTML.


Spacing System

Tailwind v4 generates spacing utilities dynamically from the --spacing base value (default: 0.25rem). Every integer multiple is available: p-1 = 0.25rem, p-4 = 1rem, p-21 = 5.25rem.

CHECK: A spacing value is needed. IF: The value is a multiple of 0.25rem. THEN: Use the number directly — mt-6, gap-4, p-8. IF: The value is not a multiple of 0.25rem. THEN: Use arbitrary values: mt-[0.625rem]. If recurring, add to @theme.

ANTI_PATTERN: Using arbitrary values for standard spacing (mt-[1rem] instead of mt-4). FIX: Use the numeric utility — Tailwind v4 supports every 0.25rem increment natively.


Breakpoints

Tailwind v4 default breakpoints remain the same. GE uses mobile-first — all base styles target mobile.

Prefix Min-width Target
(none) 0px Mobile
sm: 640px Large phone / small tablet
md: 768px Tablet
lg: 1024px Desktop
xl: 1280px Large desktop
2xl: 1536px Ultrawide

CHECK: A custom breakpoint is needed. IF: The design requires a non-standard breakpoint. THEN: Add to @theme: --breakpoint-{name}: {value}.


Runtime Access in JavaScript

All @theme tokens are exposed as CSS variables on :root.

// Read a token value at runtime
const primary = getComputedStyle(document.documentElement)
  .getPropertyValue('--color-brand-500')

CHECK: You need a token value in JS. IF: For conditional logic or canvas rendering. THEN: Use getComputedStyle — never hardcode the value in JS.


Cross-References

READ_ALSO: wiki/docs/stack/tailwind-shadcn/index.md READ_ALSO: wiki/docs/stack/tailwind-shadcn/component-patterns.md READ_ALSO: wiki/docs/stack/tailwind-shadcn/responsive.md READ_ALSO: wiki/docs/stack/tailwind-shadcn/pitfalls.md