Design Tokens
Design tokens are the named, reusable primitives the whole system is built from — spacing, radius, elevation, motion, typography, z-index, and breakpoints (colours have their own richer system; see Custom Color System).
Single source of truth
The tokens are declared once, as CSS custom properties, in
app/assets/stylesheets/midwest/tokens.css under the @layer midwest.tokens
cascade layer. That CSS is the canonical definition. Everything else reads
from it rather than re-declaring values:
- Components consume them directly in CSS via
var(--space-md),var(--radius-md), etc. - Ruby reads them through
Midwest::Tokens, which parsestokens.cssat runtime — so the Ruby values can never drift from what components render. This mirrors the contractMidwest::Themehas withcolor-system.css. - The MCP server exposes them through the
get_design_tokenstool. - The CLI exports them with
midwest_mcp tokens:export.
All length tokens are expressed in rem, so they scale with the responsive root
font-size. Steps use a t-shirt scale (xs … 3xl) that lines up with the
component option vocabulary (size:, padding:).
Using tokens in CSS
Reference a token anywhere you would otherwise hard-code a value:
@layer midwest.my-component { .midwest-my-component { padding: var(--space-md); border-radius: var(--radius-md); box-shadow: var(--shadow-lg); transition: transform var(--duration-base) var(--ease-standard); }}For example, CardComponent drives its padding, radius, sticky-header stacking,
and flip animation entirely from tokens:
.midwest-card { --border-radius: var(--radius-md); --padding: var(--space-md);}.midwest-card.padding-lg { --padding: var(--space-lg); }Token reference
The tables below are generated live from Midwest::Tokens, so they always match
the shipped tokens.css.
Spacing
| Token | Value |
|---|---|
--space-none |
0 |
--space-3xs |
0.125rem |
--space-2xs |
0.25rem |
--space-xs |
0.5rem |
--space-sm |
0.75rem |
--space-md |
1rem |
--space-lg |
1.5rem |
--space-xl |
2rem |
--space-2xl |
3rem |
--space-3xl |
4rem |
Border radius
| Token | Value |
|---|---|
--radius-none |
0 |
--radius-sm |
0.25rem |
--radius-md |
0.5rem |
--radius-lg |
0.75rem |
--radius-xl |
1rem |
--radius-full |
9999px |
Elevation (shadow)
| Token | Value |
|---|---|
--shadow-none |
none |
--shadow-sm |
0 1px 2px 0 rgb(0 0 0 / 5%) |
--shadow-md |
0 4px 6px -1px rgb(0 0 0 / 10%), 0 2px 4px -2px rgb(0 0 0 / 10%) |
--shadow-lg |
0 10px 15px -3px rgb(0 0 0 / 10%), 0 4px 6px -4px rgb(0 0 0 / 10%) |
--shadow-xl |
0 20px 25px -5px rgb(0 0 0 / 10%), 0 8px 10px -6px rgb(0 0 0 / 10%) |
Motion
Durations:
| Token | Value |
|---|---|
--duration-fast |
150ms |
--duration-base |
200ms |
--duration-slow |
350ms |
--duration-slower |
500ms |
Easings:
| Token | Value |
|---|---|
--ease-standard |
cubic-bezier(0.4, 0, 0.2, 1) |
--ease-in |
cubic-bezier(0.4, 0, 1, 1) |
--ease-out |
cubic-bezier(0, 0, 0.2, 1) |
--ease-emphasized |
cubic-bezier(0.2, 0, 0, 1) |
Typography
Font families:
| Token | Value |
|---|---|
--font-family-sans |
"Inter var", ui-sans-serif, system-ui, sans-serif |
--font-family-mono |
ui-monospace, "SF Mono", "Cascadia Code", "Fira Code", monospace |
Font sizes:
| Token | Value |
|---|---|
--font-size-xs |
0.75rem |
--font-size-sm |
0.875rem |
--font-size-md |
1rem |
--font-size-lg |
1.125rem |
--font-size-xl |
1.25rem |
--font-size-2xl |
1.5rem |
--font-size-3xl |
1.875rem |
--font-size-4xl |
2.25rem |
--font-size-5xl |
3rem |
--font-size-6xl |
3.75rem |
Font weights:
| Token | Value |
|---|---|
--font-weight-normal |
400 |
--font-weight-medium |
500 |
--font-weight-semibold |
600 |
--font-weight-bold |
700 |
Line heights:
| Token | Value |
|---|---|
--leading-none |
1 |
--leading-tight |
1.25 |
--leading-snug |
1.375 |
--leading-normal |
1.5 |
--leading-relaxed |
1.625 |
Letter spacing:
| Token | Value |
|---|---|
--tracking-tight |
-0.01em |
--tracking-normal |
0.02em |
--tracking-wide |
0.05em |
--tracking-wider |
0.1em |
Z-index
Named stacking order. --z-max matches the legacy 999999 sentinel used by
sticky/overlay surfaces.
| Token | Value |
|---|---|
--z-base |
0 |
--z-raised |
10 |
--z-dropdown |
1000 |
--z-sticky |
1100 |
--z-overlay |
1200 |
--z-modal |
1300 |
--z-popover |
1400 |
--z-toast |
1500 |
--z-tooltip |
1600 |
--z-max |
999999 |
Breakpoints
These mirror the responsive root-font-size steps. They are exposed for JavaScript and container queries; CSS media queries still use the literal pixel values.
| Token | Value |
|---|---|
--breakpoint-xs |
400px |
--breakpoint-sm |
600px |
--breakpoint-md |
800px |
--breakpoint-lg |
1000px |
--breakpoint-xl |
1200px |
--breakpoint-2xl |
1400px |
Ruby API
Midwest::Tokens gives read access to every token. Colours are folded in by
delegating to Midwest::Theme, so the API covers the whole system.
Midwest::Tokens.categories # => [:font_size, :spacing, :radius, ...]Midwest::Tokens.spacing # => { "none" => "0", "md" => "1rem", ... }Midwest::Tokens.get(:radius, "md") # => "0.5rem"Midwest::Tokens.z_index # => { "modal" => "1300", "max" => "999999", ... }Midwest::Tokens.colors["gray"]["0"] # => "#f6f6f7" (delegated to Midwest::Theme)Midwest::Tokens.get(:colors, "gray") # => the full shade hashMidwest::Tokens.all # => the full token tree, colours includedExporting tokens
The same tree can be exported in three formats, for handing tokens to other tools (design tools, non-Rails apps, a Sass pipeline):
Midwest::Tokens.to_json # => JSON string of the full treeMidwest::Tokens.to_css # => ":root { --space-md: 1rem; ... }" (scalars + colour hex)Midwest::Tokens.to_scss # => "$space-md: 1rem;\n$radius-md: 0.5rem;\n..."From the command line
The midwest_mcp executable exposes the same exporters:
midwest_mcp tokens:export --format json # defaultmidwest_mcp tokens:export --format cssmidwest_mcp tokens:export --format scssAI assistance (MCP)
The Midwest MCP server exposes the token tree to AI tools through
get_design_tokens. Call it with no arguments for the full tree, or pass a
category (spacing, radius, shadow, duration, easing, font_size,
font_weight, font_family, line_height, letter_spacing, z_index,
breakpoint, or colors) to scope the result. Because the tool reads from
Midwest::Tokens, the values it returns are exactly what the components render.