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