Kaotypr Design System cheat sheet
One page. No marketing copy. Token names by family, tier rules, forbidden patterns, common-task snippets. Optimised for context-efficient ingestion.
Top rules
- Shadcn props surface is immutable. Variants are additive only.
- Components reference tokens only — never inline
bg-black/30,rounded-3xl,z-50,text-foreground/60. - Semantic pairs are atomic:
bg-{role} text-{role}-foreground. Never split. - Status uses the SOLID pattern:
bg-destructive text-destructive-foreground border-destructive-border. Neverbg-{status}/10. - Text-entry surfaces apply
text-base md:text-sm(iOS Safari focus-zoom). - Focus ring is pinned at
--ring-width: 3px.
Tier semantics
System identity. Never override per-project. Includes z-index, ring-width, status hues, state-layer opacities, --size-* ratios, --opacity-* scale.
Override expected per-project. Surfaces, foregrounds, borders (values only — not the 3-/4-tier structure), status lightness/chroma, fonts, semantic radii, chart palette, sidebar palette.
Override allowed where it helps the product. Shadows, motion, overlay, per-control surfaces (switch/slider/scrollbar/skeleton), placeholder, link/code/kbd, blur scale.
Token names by family
Blur — Backdrop blur scale
--blur-lg--blur-md--blur-sm
Color — Surfaces, brand, status, state layers, charts
--color-destructive--color-destructive-border--color-destructive-foreground--color-info--color-info-border--color-info-foreground--color-ring-invalid--color-state-active--color-state-expanded--color-state-focus--color-state-hover--color-state-selected--color-success--color-success-border--color-success-foreground--color-warning--color-warning-border--color-warning-foreground
--color-accent--color-accent-foreground--color-background--color-border--color-border-focus--color-border-input--color-border-strong--color-border-subtle--color-card--color-card-foreground--color-chart-1--color-chart-10--color-chart-11--color-chart-12--color-chart-2--color-chart-3--color-chart-4--color-chart-5--color-chart-6--color-chart-7--color-chart-8--color-chart-9--color-chart-axis--color-chart-grid--color-chart-tooltip-bg--color-chart-tooltip-fg--color-foreground--color-foreground-disabled--color-foreground-subtle--color-input--color-muted--color-popover--color-popover-foreground--color-primary--color-primary-foreground--color-ring--color-secondary--color-secondary-foreground--color-sidebar--color-sidebar-accent--color-sidebar-accent-foreground--color-sidebar-border--color-sidebar-foreground--color-sidebar-primary--color-sidebar-primary-foreground--color-sidebar-ring--gradient-brand--slider-range--switch-track-on
--code-bg--code-fg--color-muted-foreground--color-overlay--kbd-bg--kbd-border--kbd-fg--label-color--link--link-hover--link-visited--placeholder-color--required-indicator-color--scrollbar-thumb--scrollbar-track--selection-bg--selection-fg--skeleton-bg--skeleton-shimmer--slider-thumb--slider-track--switch-thumb--switch-track-off
Density — Comfortable vs compact mode mapping
--density-card-padding--density-control-h--density-field-gap
Focus ring — Pinned to 3px
--ring-offset--ring-width
Font — Sans, mono, heading, display
--font-display--font-heading--font-mono--font-sans
Form — Field padding, label rhythm
--field-gap--field-padding-x
Layout — Container widths, sidebar widths
--container-content--container-dashboard--container-prose--container-wide--sidebar-width--sidebar-width-icon
Motion — Durations + easing curves
--duration-fast--duration-instant--duration-normal--duration-slow--duration-slower--ease-in-out--ease-out--ease-overshoot--ease-spring
Opacity — Disabled, hover, pressed, overlay, loading
--opacity-disabled--opacity-hover--opacity-loading--opacity-overlay--opacity-pressed
Radius — Generic scale + semantic radii per registry component
--radius-lg--radius-md--radius-sm--radius-xl
--radius-button--radius-card--radius-chip--radius-dialog--radius-input--radius-popover--radius-sheet
Shadow — Elevation tokens; --shadow-focus is locked
--shadow-focus
--shadow-card--shadow-inset--shadow-overlay--shadow-popover
Sizing — Icon, avatar, control, touch-target sizes
--size-avatar-lg--size-avatar-md--size-avatar-sm--size-avatar-xl--size-control-lg--size-control-md--size-control-sm--size-icon-lg--size-icon-md--size-icon-sm--size-touch-target
Z-index — Cross-product layering — never override
--z-index-base--z-index-dropdown--z-index-modal--z-index-overlay--z-index-popover--z-index-sticky--z-index-toast--z-index-tooltip
Disambiguation — which token for this job?
Several tokens are semantically close. This section is the decision tree.
- bg-background
- the page itself, the lowest layer
- bg-card
- elevated content blocks (article cards, dashboard tiles, list rows that read as separate)
- bg-popover
- transient floating layers (dropdowns, tooltips, command palettes, comboboxes, datepickers)
- bg-muted
- low-emphasis fills inside a card or surface (input wells, deemphasized sections, code blocks, sidebar groups)
- bg-secondary
- secondary buttons, badges, low-emphasis interactive controls (NOT a backdrop)
- bg-accent
- highlight surface for active nav items, selected list rows, current-step indicators
- text-foreground
- primary body text, headings, labels — the default
- text-foreground-subtle
- captions, helper text, deemphasized headings (still legible)
- text-muted-foreground
- muted body copy on bg-muted, placeholders that aren't using --placeholder-color, ancillary metadata
- text-foreground-disabled
- disabled control labels — never use for design choice, only for disabled state
- border-border
- default outlines, dividers between sibling cards
- border-border-subtle
- low-contrast separators inside a card, table row dividers, list item separators
- border-border-strong
- emphasized outlines, hover/active outlines on cards, table column separators in dense layouts
- border-input
- form inputs only (slightly stronger than -subtle for affordance)
- rounded-input
- form inputs, search boxes, single-line text controls
- rounded-button
- buttons, icon buttons, segmented controls
- rounded-chip
- tags, filter pills, badges, status chips (pill-shaped)
- rounded-card
- content cards, dashboard tiles, list-row cards
- rounded-popover
- dropdowns, tooltips, comboboxes, hover cards
- rounded-dialog
- modal dialogs, alert dialogs (always centered, scrim behind)
- rounded-sheet
- side sheets, drawers, mobile bottom sheets
- shadow-card
- content cards at rest
- shadow-popover
- floating layers (dropdowns, tooltips, comboboxes)
- shadow-overlay
- modal dialogs and side sheets — the highest elevation
- shadow-focus
- focus-ring glow on inputs (paired with the ring token)
- shadow-inset
- pressed states, sunken wells, scrollable region indicators
- bg-state-hover
- hover affordance on buttons, list rows, nav items (4–6% layer)
- bg-state-focus
- keyboard-focus background — usually paired with the focus ring
- bg-state-active
- pressed state (8–12% layer)
- bg-state-expanded
- open/expanded affordance: aria-expanded='true' on triggers, current open menu item
- bg-state-selected
- persistent selection: selected row in a table, picked option in a list, current step in a wizard
- duration-instant
- 0ms — feedback that should feel immediate (toggle flips, focus rings); also the motion-reduce: fallback
- duration-fast
- micro-interactions: hover, ripple, tooltip show, pressed feedback
- duration-normal
- layout reveals, panel slides, accordion expand, tab content swap
- duration-slow
- page transitions, large surface entrances, modal open
- duration-slower
- confidence-builder moments only (success animations, celebratory reveals)
- max-w-(--container-prose)
- article-style reading content, ~65ch — comfortable for body copy
- max-w-(--container-content)
- marketing pages, single-column docs, narrow forms
- max-w-(--container-dashboard)
- app shells with sidebars, multi-column dashboards (default for product UI)
- max-w-(--container-wide)
- data-heavy tables, design canvases, full-bleed dashboards
Migration map — Tailwind drift → tokens
When retrofitting an existing codebase, replace left with right.
| From (drift) | To (token) |
|---|---|
| bg-white | bg-background |
| bg-gray-50 / bg-zinc-50 / bg-slate-50 | bg-muted |
| bg-gray-100 | bg-muted |
| bg-gray-900 / bg-zinc-900 / bg-black | bg-card |
| bg-white shadow-md (a card) | bg-card shadow-card |
| text-black / text-gray-900 | text-foreground |
| text-gray-700 | text-foreground-subtle |
| text-gray-500 / text-zinc-500 | text-muted-foreground |
| text-gray-400 | text-foreground-disabled |
| text-white (paired with a colored bg) | text-{role}-foreground |
| border-gray-200 / border-zinc-200 | border-border |
| border-gray-100 | border-border-subtle |
| border-gray-300 | border-border-strong |
| border (on input) | border-input |
| bg-red-500 / text-red-600 | bg-destructive / text-destructive (with paired foreground + border) |
| bg-green-500 / text-green-600 | bg-success / text-success |
| bg-yellow-500 / bg-amber-500 | bg-warning |
| bg-blue-500 / text-blue-600 | bg-info / text-info |
| rounded-md (control) | rounded-input or rounded-button |
| rounded-lg / rounded-xl (card) | rounded-card |
| rounded-2xl / rounded-3xl (modal) | rounded-dialog or rounded-sheet |
| rounded-full (pill chip) | rounded-chip |
| shadow / shadow-md (card) | shadow-card |
| shadow-lg / shadow-xl (popover) | shadow-popover |
| shadow-2xl (modal) | shadow-overlay |
| z-10 (sticky header) | z-sticky |
| z-40 (dropdown) | z-dropdown |
| z-50 (overlay/modal) | z-overlay or z-modal |
| duration-150 / duration-200 | duration-fast |
| duration-300 | duration-normal |
| duration-500 | duration-slow |
| ease-in-out (UI) | ease-out (most cases) |
| size-4 (icon) | size-(--size-icon-sm) |
| size-5 (icon) | size-(--size-icon-md) |
| size-6 (icon) | size-(--size-icon-lg) |
| h-10 (control) | h-(--density-control-h) |
| min-h-[44px] | min-h-(--size-touch-target) |
| max-w-2xl / max-w-3xl (article) | max-w-(--container-prose) or max-w-(--container-content) |
| max-w-7xl (dashboard) | max-w-(--container-dashboard) |
Forbidden patterns
className="bg-black/30"className="bg-overlay"Inline color literal. Use --overlay or token-mapped utilities.
className="text-gray-500" /* or text-zinc-500 / text-slate-500 */className="text-muted-foreground"Tailwind palette literals never enter components. Muted body copy is text-muted-foreground; deemphasized headings are text-foreground-subtle.
className="bg-gray-100" /* or bg-zinc-100 / bg-slate-50 */className="bg-muted"Low-emphasis surface background is bg-muted. Inputs / wells / deemphasized sections all use it.
className="bg-zinc-900 text-white"className="bg-card text-card-foreground" /* or bg-popover for floating */Theme-fixed dark surfaces break light/dark mode. Pick the semantic surface (bg-card / bg-popover / bg-secondary) and pair it with its foreground.
className="border-gray-200"className="border-border" /* or border-border-subtle for low contrast */Use the 4-tier border vocabulary (border, border-subtle, border-strong, border-input). Never reach for Tailwind palette borders.
className="text-red-500"className="text-destructive"Status uses semantic names (destructive / success / warning / info), not red/green/yellow/blue.
className="bg-red-500 text-white"className="bg-destructive text-destructive-foreground border border-destructive-border"Solid status pattern requires the full triple: bg + foreground + border.
className="text-white" /* or text-black */className="text-{role}-foreground" /* paired with its bg-{role} */Theme-fixed text colors break dark mode. Pair every surface with its semantic foreground.
className="bg-destructive/10 text-destructive"className="bg-destructive text-destructive-foreground border border-destructive-border"Status uses the SOLID pattern. /10 on a pastel base loses contrast.
className="bg-card text-foreground/70"className="bg-card text-card-foreground"Always pair bg-{role} with text-{role}-foreground. Never split.
className="text-foreground/60"className="text-muted-foreground"Use the foreground hierarchy (foreground / -subtle / -disabled / muted-foreground), not opacity fractions.
className="rounded-3xl" /* or rounded-2xl */className="rounded-card" /* or rounded-dialog / rounded-sheet */Use the semantic radius vocabulary (rounded-button, rounded-input, rounded-chip, rounded-card, rounded-popover, rounded-dialog, rounded-sheet).
className="rounded-md" /* on a control */className="rounded-input" /* or rounded-button */Per-component radii are tokenized. rounded-md is a fallback, not the convention.
<button className="rounded-full px-3">Filter</button><button className="rounded-chip px-3">Filter</button>Pill-shaped chips/badges/filters use rounded-chip — never raw rounded-full on text-bearing controls.
className="shadow-lg" /* or shadow-xl / shadow-2xl */className="shadow-card" /* content */ /* or shadow-popover for floating, shadow-overlay for modal */Shadows are tokenized by surface role: shadow-card, shadow-popover, shadow-overlay, shadow-focus, shadow-inset.
className="drop-shadow-md"className="shadow-card"drop-shadow is for SVG/icon glyphs. Surfaces use the shadow-* tokens.
className="z-50"className="z-overlay" /* or z-modal / z-popover / z-tooltip / z-toast / z-dropdown / z-sticky */The z-index ladder is locked across products. Pick the semantic level: sticky 30 < dropdown 40 < overlay 50 < modal/popover 60 < tooltip 70 < toast 80.
className="duration-200" /* or duration-150 / duration-300 */className="duration-fast" /* or duration-normal / duration-slow / duration-slower */Motion durations are tokenized: instant / fast / normal / slow / slower. Numbered Tailwind durations bypass the system.
className="ease-in-out" /* on UI motion */className="ease-out" /* or ease-spring / ease-overshoot */UI motion defaults to ease-out (decelerating). Reach for ease-spring for layout reveals and ease-overshoot for confidence-builder moments.
className="transition-all duration-200"className="transition-colors duration-fast" /* or transition-transform */transition-all triggers layout/paint on every property change. Be specific (colors / transform / opacity) and use a duration token.
<motion.div animate={{ x: 100 }} transition={{ duration: 0.3 }} /><motion.div animate={{ x: 100 }} transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }} /> /* read tokens via CSS or constants */Hardcoded Motion durations/easings drift from the design system. Pull values from the tokens (CSS getPropertyValue or a shared constants module).
<Icon className="size-5" /><Icon className="size-(--size-icon-sm)" /> /* or --size-icon-md / --size-icon-lg */Icon sizes use the locked --size-icon-* ratios. Hardcoded size-* doesn't survive density changes.
className="h-10"className="h-(--density-control-h)"Control heights use --density-control-h so they auto-tighten under data-density="compact". Hardcoded h-* breaks compact mode.
className="p-5"className="p-(--density-card-padding)"Card padding uses --density-card-padding (compact-aware). Reach for it before raw p-*.
className="min-h-[44px]"className="min-h-(--size-touch-target)"Touch target floor is tokenized at 44px via --size-touch-target. Don't hardcode the magic number.
className="ring-2 ring-blue-500"className="ring-(length:--ring-width) ring-ring"Focus ring is pinned at --ring-width: 3px. Use the canonical ring color.
className="focus:ring-2 focus:ring-blue-500"className="focus-visible:ring-(length:--ring-width) focus-visible:ring-ring/40 focus-visible:border-ring focus-visible:outline-none"Use focus-visible (not focus) and the canonical ring tokens. Pair with focus-visible:outline-none and a colored border.
className="hover:bg-gray-100 active:bg-gray-200"className="hover:bg-state-hover active:bg-state-active"Interactive states use the 5-stop state-layer scale (4/6/8/12/16% opacity), not Tailwind palette colors.
className="aria-expanded:bg-secondary"className="aria-expanded:bg-state-expanded" /* or bg-state-selected */Open/expanded/selected affordances are state-layer tokens, not surface roles.
className="gap-3 p-5"className="gap-2 p-4" /* or gap-4 / p-6 */Prefer even Tailwind spacing steps (2, 4, 6, 8, 10, 12). Odd steps usually mean fighting the layout — pick the nearest even step or reach for a density token.
<div><Card className="mb-4" /><Card className="mb-4" /></div><div className="flex flex-col gap-4"><Card /><Card /></div>Margins collapse and fight stacking. Use gap-* on a flex/grid parent for sibling spacing.
className="max-w-3xl"className="max-w-(--container-content)"Page widths use container tokens (--container-content, --container-prose, --container-dashboard, --container-wide). Never ad-hoc Tailwind max-w sizes.
<input className="text-sm" /><input className="text-base md:text-sm" />iOS Safari focus-zoom rule. Text-entry surfaces must be ≥16px on mobile.
<input className="border" /><input className="border-input" />Inputs use --border-input (slightly stronger than --border-subtle for affordance). Generic border-* loses the input shape.
className="font-mono" /* on the brand wordmark */className="font-display" /* paired with --font-display */Display, heading, sans, and mono are distinct font roles. Don't reuse font-mono outside of code.
className="opacity-50" /* on a disabled control */className="opacity-(--opacity-disabled)" /* or aria-disabled with --opacity-disabled */Disabled / loading / pressed / hover / overlay opacities are tokenized so they're consistent across components.
<motion.div animate={{ opacity: 1 }} transition={{ duration: 0.4 }} /><motion.div animate={{ opacity: 1 }} transition={{ duration: 0.4 }} className="motion-reduce:!duration-(--duration-instant)" />Respect prefers-reduced-motion. Either gate the animation behind motion-safe: or collapse the duration to --duration-instant under motion-reduce:.
Common tasks
<div className="flex gap-3 rounded-card border bg-{status} text-{status}-foreground border-{status}-border p-4">
<Icon weight="fill" className="size-5 shrink-0" />
<div>
<p className="font-medium">Title</p>
<p className="text-sm opacity-90">Body</p>
</div>
</div><Badge className="bg-success text-success-foreground border border-success-border">
Active
</Badge><div className="rounded-card bg-card text-card-foreground shadow-card ring-1 ring-border-subtle p-(--density-card-padding)">
…
</div>className="focus-visible:outline-none focus-visible:ring-(length:--ring-width) focus-visible:ring-ring/40 focus-visible:border-ring"className="hover:bg-state-hover active:bg-state-active aria-expanded:bg-state-expanded"<DialogOverlay className="fixed inset-0 bg-overlay z-overlay" />
<DialogContent className="z-modal rounded-dialog bg-popover text-popover-foreground shadow-overlay" /><SheetContent className="z-modal rounded-sheet bg-popover text-popover-foreground shadow-overlay">
…
</SheetContent><PopoverContent className="z-popover rounded-popover bg-popover text-popover-foreground shadow-popover border-border-subtle">
…
</PopoverContent><TooltipContent className="z-tooltip rounded-popover bg-popover text-popover-foreground shadow-popover px-2 py-1 text-xs">
…
</TooltipContent><Toast className="z-toast rounded-card bg-card text-card-foreground shadow-overlay border-border-subtle p-4">
…
</Toast><div className="flex flex-col items-center gap-3 rounded-card bg-muted/40 border border-border-subtle p-(--density-card-padding) text-center">
<Icon className="size-(--size-icon-lg) text-muted-foreground" />
<p className="font-medium">No items yet</p>
<p className="text-sm text-muted-foreground">Get started by creating one.</p>
</div><div className="rounded-card bg-card border border-border-subtle p-(--density-card-padding) space-y-3">
<div className="h-4 w-1/3 rounded bg-muted animate-pulse" />
<div className="h-3 w-2/3 rounded bg-muted animate-pulse" />
</div><label className="flex flex-col gap-1.5">
<span className="text-sm font-medium">Email</span>
<input
type="email"
className="h-(--density-control-h) rounded-input border-input bg-background px-3 text-base md:text-sm focus-visible:outline-none focus-visible:ring-(length:--ring-width) focus-visible:ring-ring/40 focus-visible:border-ring"
/>
</label><a
href="#"
data-active={isActive}
className="flex items-center gap-2 rounded-input px-3 h-(--density-control-h) text-sm hover:bg-state-hover data-[active=true]:bg-state-selected data-[active=true]:text-foreground"
>
…
</a><html data-density="compact"> {/* or scope to a section */}
…controls auto-tighten via --density-* tokens
</html>const palette = Array.from({ length: 12 }, (_, i) => `var(--color-chart-${i + 1})`);
<Bar fill={palette[0]} /> // anchored to brand<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.4 }}
className="motion-reduce:!duration-(--duration-instant)"
/>Either gate behind motion-safe: or collapse to --duration-instant under motion-reduce:.
Source-of-truth for tokens: app/globals.css. This page is generated from that file at build time so it never drifts.