$ cat ./parsimony.md

The system behind the system.

Every color, type style, spacing value, motion, and radius on this site comes from Parsimony, a design system I maintain to keep every River Romney property consistent. Primitives feed the semantic layer. UI code never touches hex.

The architecture takes a cue from Unix’s small, sharp tools philosophy. Primitives hold values, semantics name roles, and components render decisions. Each layer does one thing well, and the next composes from it.

 v0.3.1 · Production119 semantic tokens, generated from source

Color every hex earns its way in

Background
--color-background-default
Base canvas #0A0D0A. Use for every page background and full-bleed section. Not for cards or elevated elements.
#0A0D0A
--color-background-alt
Elevated surface #1E241E. Use for cards, panels, code blocks, inputs, tooltips. One subtle step above canvas.
#1E241E
--color-background-action
Interactive fill #4ADE6E. Use for filled primary buttons, selected states, active toggles. Pair with foreground.on-action.
#4ADE6E
--color-background-action-hover
Hover fill for primary action elements. 16% darker than background.action. Use for button hover backgrounds when the border matches the background.
#3EBA5C
--color-background-action-active
Active/pressed fill for primary action elements. 30% darker than background.action. Use for the moment a button is held down.
#349B4D
--color-background-disabled
Fill for disabled interactive elements (inputs, buttons, selects). Same value as background.alt — disabled controls blend into elevated surfaces, signalling they are part of the layout but not actionable.
#1E241E
Foreground
--color-foreground-default
Primary text and icons #C8CFC4. Use for headings, body copy, nav labels. Contrast vs background.default: 12.26:1 AAA.
#C8CFC4
--color-foreground-alt
Secondary text and icons #A0A89A. Use for metadata, timestamps, supporting labels. Contrast vs background.default: 7.97:1 AA.
#A0A89A
--color-foreground-muted
Muted text — secondary labels, captions, timestamps, metadata. Also use for input placeholder text (foreground.disabled exists for disabled element text — placeholder copy on active inputs uses muted, not disabled).
#8B9683
--color-foreground-disabled
Text and icons on disabled elements. Deeply receded against the dark canvas — communicates unavailability without being invisible. WCAG contrast exemption applies to disabled text (1.4.3 exception).
#1E241E
--color-foreground-action
Interactive text and icons #4ADE6E. Use for links, active nav items, interactive labels. Never for static text. Contrast: 11.13:1 AAA.
#4ADE6E
Foreground on fills
--color-foreground-on-action
Text/icons on background.action #0A0D0A. Use for labels inside filled interactive components (e.g. primary button). Contrast vs action: 11.13:1 AAA.
#0A0D0A
Border
--color-border-default
Standard legible border #5C685A. The default edge for most UI — input outlines, control boundaries, card edges where the edge should read. Clears the SC 1.4.11 non-text floor on the canvas (3.33:1) while staying below border.hover (6.32:1) so the state ladder holds. For deliberately quiet, decorative edges use border.alt. Changed from green.900 (#1E241E, 1.23:1 — invisible where it was the only boundary indicator) 2026-07-02, #28.
#5C685A
--color-border-alt
The quiet boundary OF a surface #1E241E — card, menu, panel, and dialog frames; decorative outlines; container edges (the previous border.default value). For separator lines WITHIN a surface (table rows, menu dividers) use border.muted, one rung below. Exempt from SC 1.4.11 (decorative, not required to identify a control) — never use as the only boundary of an interactive component. Shares value with background.alt but different intent. Rule codified 2026-07-02 from observed usage: alt = frames, muted = rows.
#1E241E
--color-border-elevated
Structural chrome borders — headers, card frames, section dividers, prominent UI edges. One step above border-alt in the decorative ladder (muted < alt < elevated); not interactive. Sits below border.default in contrast — for a legible functional edge use border.default itself.
#2E3A2E
--color-border-hover
Border on interactive elements in hover state. Mid-toned green — clearly visible against the dark canvas, signals interactivity without using the full accent.
#8B9683
--color-border-active
Border on interactive elements in active/pressed state. Lighter than hover — maximum border prominence for the held-down moment.
#A0A89A
--color-border-action
Border for interactive outlined controls — the secondary (ghost) button. Vivid phosphor green #4ADE6E; a resting accent border is rule-compliant here because the control is interactive. Matches the primary button fill (filled primary vs outlined secondary). Contrast vs background.default: 11.13:1; vs background.alt: 9.02:1.
#4ADE6E
--color-border-focus
Focus ring color. Applied as an outline on keyboard-focused interactive elements. Vivid phosphor green — maximum visibility against the dark canvas. Always pair with a non-color indicator (outline offset) for non-color-blind users.
#4ADE6E
--color-border-disabled
Border on disabled elements. Recedes to near-canvas — the element visually withdraws. Combine with reduced opacity on the overall component.
#0A0D0A
--color-border-muted
Separator lines WITHIN a surface #0F2016 — table row separators, menu/list dividers, section rules inside a panel. Near-invisible hairline; the lowest rung of the decorative ladder (muted < alt < elevated). For the boundary OF a surface (card/menu/panel frames) use border.alt, one rung above. Base dark: intentionally very subtle — use spacing + background.alt to create separation in dark contexts. Rule codified 2026-07-02 from observed usage: muted = rows, alt = frames.
#0F2016
Feedback
Success
--color-background-success
Strong success fill — success badges, status pills, success CTA fills. Pair with foreground.on-success (dark text). Base dark: phosphor green.
#4ADE6E
--color-background-success-alt
Subtle success tint — success alert/banner backgrounds. Base dark: deep green tint #0F2016.
#0F2016
--color-foreground-success
Success text and icons. Base dark: phosphor green #4ADE6E — vivid and legible on dark canvas (11.1:1 AAA).
#4ADE6E
--color-foreground-on-success
Text/icons on success fill backgrounds. Near-black on vivid green — high contrast (11.1:1 AAA).
#0A0D0A
Warning
--color-background-warning
Strong warning fill — warning badges, status pills. Pair with foreground.on-warning (dark text).
#FCD34D
--color-background-warning-alt
Subtle warning tint — warning alert/banner backgrounds. Base dark: deep amber tint #221B08.
#221B08
--color-foreground-warning
Warning text and icons. Base dark: amber.400 #FCD34D — bright amber on dark canvas (13.6:1 AAA).
#FCD34D
--color-foreground-on-warning
Text/icons on warning fill backgrounds. Near-black on amber — always use dark text on amber fills.
#0A0D0A
Danger
--color-background-danger
Danger fill #C8002E. Use for filled destructive buttons (delete, remove). Always pair with foreground.on-danger (white, 6.01:1 AA). Moved from red.500 2026-07-02: white on #E73027 was 4.33:1 (AA fail); red.600 also aligns the danger fill with decision-engine's.
#C8002E
--color-background-danger-alt
Subtle danger tint #2A0A0A — dark red for error alert/badge backgrounds, matching the other feedback -alt tints. Pair with foreground.danger (light red) for text. DE overrides to a warm light-red tint.
#2A0A0A
--color-foreground-danger
Error/destructive text and icons #F87171. Use for delete labels, validation errors, Deny outcomes, and danger alert/badge text. Light red so it reads on the canvas (7.06:1 AAA) and on the dark danger-alt tint (6.62:1 AA). Consistent with the other bright feedback foregrounds.
#F87171
--color-foreground-on-danger
Text/icons on background.danger #FFFFFF. Use inside filled destructive components (e.g. filled delete button). Contrast vs background.danger (red.600): 6.01:1 AA. Never on white or dark canvases.
#FFFFFF
Info
--color-background-info
Strong info fill — info badges, status pills. Pair with foreground.on-info (white).
#2456E4
--color-background-info-alt
Subtle info tint — info alert/banner backgrounds. Base dark: deep blue tint #0D1830.
#0D1830
--color-foreground-info
Info text and icons. Base dark: blue.sky #93C5FD — light blue on dark canvas (10.8:1 AAA).
#93C5FD
--color-foreground-on-info
Text/icons on info fill backgrounds. White on blue fill.
#FFFFFF
Accents
Background
--color-background-accent-green
Dark green tint #0F2016. Subtle chip/tag fills, callout panels with green emphasis. Pair with foreground.accent-green for text.
#0F2016
--color-background-accent-blue
Dark blue tint #0D1830. Subtle chip/tag fills, callout panels with blue emphasis. Pair with foreground.accent-blue for text.
#0D1830
--color-background-accent-violet
Dark violet tint #18102E. Subtle chip/tag fills, callout panels with violet emphasis. Pair with foreground.accent-violet for text.
#18102E
--color-background-accent-amber
Dark amber tint #221B08. Subtle chip/tag fills, callout panels with warm amber emphasis. Pair with foreground.accent-amber for text.
#221B08
--color-background-accent-indigo-bold
Saturated indigo fill for bold accent surfaces — avatar backgrounds, identity badges. Pair with foreground.accent-on-indigo (white). Contrast vs white: 7.98:1 AAA.
#4338CA
--color-background-accent-sky-bold
Saturated sky blue fill for bold accent surfaces — avatar backgrounds, identity badges. Pair with foreground.accent-on-sky (white). Contrast vs white: 5.90:1 AA.
#0369A1
--color-background-accent-green-bold
Saturated emerald fill for bold accent surfaces — avatar backgrounds, identity badges. Pair with foreground.accent-on-green (white on dark fills). Contrast vs white: 5.47:1 AA.
#047857
--color-background-accent-amber-bold
Saturated amber fill for bold accent surfaces — avatar backgrounds, identity badges. Pair with white text (DS debt: accent-on-amber is dark for tints). Contrast vs white: 5.02:1 AA.
#B45309
Foreground
--color-foreground-accent-green
Phosphor green accent #4ADE6E. Use for emphasis text, icons, and labels that should read in the green family. Pair with background.accent-green for tinted surfaces. Contrast vs background.default: 11.13:1 AAA.
#4ADE6E
--color-foreground-accent-blue
Light blue accent #93C5FD. Use for emphasis text, icons, and labels that should read in the blue family. Pair with background.accent-blue for tinted surfaces. Contrast vs background.default: ~7.5:1 AA.
#93C5FD
--color-foreground-accent-violet
Light violet accent #C4B5FD. Use for emphasis text, icons, and labels that should read in the violet family. Pair with background.accent-violet for tinted surfaces. Contrast vs background.default: ~7:1 AA.
#C4B5FD
--color-foreground-accent-amber
Warm amber accent #FCD34D. Use for emphasis text, icons, and labels that should read in the warm/amber family. Pair with background.accent-amber for tinted surfaces. Contrast vs background.default: ~12:1 AAA.
#FCD34D
Foreground on accent
--color-foreground-accent-on-green
Text/icons on green accent tint fills. Near-black on dark theme. For bold green fills, white text is needed — DE overrides to white.
#0A0D0A
--color-foreground-accent-on-blue
Text/icons on blue accent fills. Near-black on dark theme.
#0A0D0A
--color-foreground-accent-on-violet
Text/icons on violet accent fills. Near-black on dark theme.
#0A0D0A
--color-foreground-accent-on-amber
Text/icons on amber accent fills. Near-black on dark theme.
#0A0D0A
--color-foreground-accent-on-indigo
Text/icons on indigo accent fills. White — indigo-700 provides 7.98:1 contrast (AAA).
#FFFFFF
--color-foreground-accent-on-sky
Text/icons on sky accent fills. White — sky-700 provides 5.90:1 contrast (AA).
#FFFFFF
--color-foreground-accent-on-amber-bold
Text/icons on amber bold accent fills (background.accent-amber-bold = amber.600 #B45309). White — 5.02:1 AA contrast. Use for avatar initials, identity badge labels on saturated amber surfaces. Do not confuse with foreground.accent-on-amber (near-black), which is for amber tint backgrounds.
#FFFFFF
--color-foreground-accent-on-green-bold
Text/icons on green-bold accent fills (background.accent-green-bold = emerald.600). White — 5.47:1 AA contrast. Distinct from foreground.accent-on-green (near-black #0A0D0A), which is for the tint background. Use this for avatar initials and identity badges on saturated green surfaces.
#FFFFFF

Typography three voices, one system

Families
--font-family-sans
'Space Grotesk'
Aa Bb Cc 0123
--font-family-serif
Spectral
Aa Bb Cc 0123
--font-family-mono
'JetBrains Mono'
Aa Bb Cc 0123
Display
--font-display
Display text. Space Grotesk 300 at 40px / 1.1 line-height. The largest type in the system — reserved for hero statements, splash headlines, and page-defining moments. This is not a structural heading; it is an expressive, editorial register. Do not use it to introduce sections or content groups — that is font.title.large's job. Use sparingly: one instance per page is typical. The tight line-height (1.1) is intentional at this scale, where lines read as a visual unit rather than separate lines.
Display
Title
--font-title-large
Primary page heading. Space Grotesk 300 at 32px / 1.25 line-height. The h1 equivalent — use for the main structural heading of a page or the first heading inside a major content section. One level below font.display, but this is a structural role where display is expressive. Most pages should have exactly one instance.
Title Large
--font-title-medium
Section heading. Space Grotesk 300 at 24px / 1.25 line-height. The h2 equivalent — use to introduce major sections within a page, blog post subheadings, and category or feature group headers. Clearly subordinate to title.large while still commanding attention.
Title Medium
--font-title-small
Sub-section heading. Space Grotesk 300 at 20px / 1.25 line-height. The h3 equivalent — use for nested content groups, tertiary sections within a blog post, and UI panel headers where a visual step down from title.medium is needed. The smallest size where the light weight still reads as intentionally editorial rather than just small text.
Title Small
Body
--font-body-large
Default body and paragraph text. Spectral 400 at 16px / 1.6 line-height. The standard reading size for all prose content — articles, case study copy, Substack posts, and any extended text block. Use this as the default for all body content unless a specific size variation is needed.
Body Large
--font-body-medium
Secondary body text. Spectral 400 at 14px / 1.6 line-height. Use for supporting prose that is present but subordinate — sidebar text, supplementary descriptions, secondary content blocks. Not for primary reading content.
Body Medium
--font-body-small
Tertiary body text and fine prose. Spectral 400 at 12px / 1.6 line-height. Use sparingly — for footnotes, endnotes, legal text, or other non-critical prose that must be present but should not draw attention. The serif at this size is still legible but clearly de-emphasised.
Body Small
Label
--font-label-large
Prominent UI labels and section headers. Space Grotesk 400 at 16px / 1.25 line-height. Use for labels that introduce major UI sections, prominent navigation items, or any interface label that needs to carry visual weight without being a full title. The snug line-height reflects its UI role — tighter than body text.
Label Large
--font-label-medium
Standard UI text. Space Grotesk 400 at 14px / 1.25 line-height. The workhorse label size — use for navigation links, button text, form field labels, tag labels, and most standard interface text. The default choice for any UI element that is not a heading, body copy, or fine print.
Label Medium
--font-label-small
Meta text, captions, and fine print. Space Grotesk 400 at 12px / 1.25 line-height. Use for timestamps, image captions, helper text, badges, and any label that needs to be present but clearly secondary. The sans-serif at this size remains legible where the serif (font.body.small) would feel more literary.
Label Small
--font-label-xsmall
Micro UI label. Space Grotesk 400 at 10px / 1.25 line-height. Use only where space is critically constrained — color chip annotations, icon overlays, tightly packed badges. Below this size legibility degrades significantly; prefer font.label.small whenever layout permits.
Label Xsmall
Label Strong
--font-label-strong-large
Emphasized large label. Space Grotesk 500 at 16px / 1.25. Use for section header labels that need to carry more visual weight, active primary nav items.
Label Strong Large
--font-label-strong-medium
Emphasized standard label. Space Grotesk 500 at 14px / 1.25. The primary use case: button text, active nav links, selected tab labels, table column headers.
Label Strong Medium
--font-label-strong-small
Emphasized small label. Space Grotesk 500 at 12px / 1.25. Use for active badge labels, selected filter chips, emphasized metadata — situations where small text needs to assert itself.
Label Strong Small
--font-label-strong-xsmall
Emphasized micro UI label. Space Grotesk 500 at 10px / 1.25 line-height. Use for active or selected states of xsmall labels — e.g., a step indicator inside a color chip, or a selected micro badge. The medium weight helps the label assert itself at this very small size.
Label Strong Xsmall
Mono Label
--font-mono-label-xsmall
Micro monospaced label. JetBrains Mono 500 at 10px / 1.25 line-height. Use for tightly constrained mono identifiers — color step numbers, token chip labels, compact code annotations. Smaller and heavier than font.mono-label-small; the medium weight compensates for the very small size.
Mono Label Xsmall
--font-mono-label-small
Monospaced UI metadata label. JetBrains Mono 400 at 12px / 1.25 line-height. Use for inline token names, value readouts, technical identifiers, and any interface metadata that should read as code but at label scale — smaller and tighter than font.code. Common in design system tooling, token tables, dev-facing UIs.
Mono Label Small
--font-mono-label-strong-small
Emphasized monospaced UI label. JetBrains Mono 500 at 12px / 1.25 line-height. Use for all-caps category labels, active token identifiers, section markers in monospaced UI chrome. Same role as font.label-strong.small but in the mono family — use when the mono voice needs to assert itself.
Mono Label Strong Small
Code
--font-code
Code blocks and inline code. JetBrains Mono 400 at 14px / 1.6 line-height. Use for all code samples, terminal output, inline code references, and any monospaced content. The slightly generous line-height (1.6) aids readability in multi-line code blocks. The terminal-style aesthetic of JetBrains Mono is intentionally coherent with the system's phosphor-green visual language.
Code
Letter-spacing
--letter-spacing-display-0.01em
Display tracking. Applied to the largest type on the page — hero headlines and page titles using font.display. At very large sizes, the typeface's default tracking reads as too loose; pulling it in slightly (-0.01em) restores optical density without over-tightening.
--letter-spacing-title-0.025em
Title tracking. Applied to structural headings using font.title.large, font.title.medium, and font.title.small. Tighter tracking than display (-0.025em) helps headings read as a cohesive unit at mid-range sizes.
--letter-spacing-body0em
Body tracking. No adjustment from the typeface's built-in tracking. The default for all Spectral body text — font.body.large, font.body.medium, font.body.small. Tracking adjustments at reading sizes typically hurt legibility more than they help. Do not tighten or open body text without a specific, tested reason.
--letter-spacing-label0.03em
Label tracking. Slightly open tracking for small Space Grotesk UI text — navigation, button labels, captions, tag text. At small sizes (12–14px), a modest +0.03em opening improves legibility and gives the label a clean, structured feel. Use for font.label.small and font.label.medium.
--letter-spacing-all-caps0.1em
All-caps tracking. Generously open tracking exclusively for text set in ALL CAPS. All-caps text at default or tight tracking reads as dense and unpolished — the wide +0.1em opening is the convention that makes it legible and intentional. Do not use at body sizes or mixed-case; it will read as broken text.

Spacing 8-point base scale

--spacing-micro
Micro spacing. For icon-to-text margins, gap between a badge and its label, and tight internal nudges within a single element. At 4px the gap signals that two things are part of the same unit — barely breathing room, not real separation.
4px
--spacing-tight
Tight spacing. Between closely related items that belong to the same thought — e.g., an icon and its label, a checkbox and its text, stacked lines within a single element. At 8px, the gap is felt as belonging rather than separating.
8px
--spacing-inline
Inline spacing. Between items that are related but distinct — e.g., items in a navigation row, pills in a tag group, or gaps in a horizontal list. Signals adjacency without implying the items are the same unit.
12px
--spacing-element
Element spacing. The default internal padding for most components — input fields, buttons, menu items, list rows. Sets the baseline breathing room inside a discrete UI unit. Also used between sibling elements within the same component zone.
16px
--spacing-component
Component spacing. Between distinct UI components in a layout — e.g., the gap between a label and its input, between a card and the next card in a list, or between a toolbar and the content below it. Signals that two things are related but not the same.
24px
--spacing-group
Group spacing. Between related groups of components within a layout zone — stacked form sections, card grid gaps, gap between a heading block and its content cluster. Fills the jump between component (24px) and layout (48px).
32px
--spacing-layout
Layout spacing. Between major layout regions within a page section — e.g., between a sidebar and main content, between a page header and the first content block, or between stacked content groups. Larger than component spacing but not a full section break.
48px
--spacing-block
Block spacing. Between major content blocks within a page section — distinct from component groupings but not a full section break. Use for the vertical gap between a text block and a media element, between a heading cluster and a feature grid, or between stacked editorial rows.
64px
--spacing-page
Page spacing. Between major editorial sections — the standard vertical separator for hero-to-content and section-to-section gaps in portfolio and documentation contexts. Larger than spacing.block (content-level rhythm) but not as expansive as spacing.section (full page breathing room).
80px
--spacing-section
Section spacing. Between top-level page sections — the full whitespace beat that tells the eye a new chapter has begun. Use for gaps between hero, features, testimonials, and footer regions. Not for component-level gaps; it will feel like the page fell apart.
128px

Radius

--radius-none
No rounding. For components that must read as rigid, grid-locked, or structural — code blocks, data tables, horizontal rules, layout dividers, and elements that deliberately read as part of the surface rather than floating above it. The default aesthetic for this system.
0
--radius-sm
Subtle rounding. Barely perceptible softening — the component retains a structured, sharp feel with just enough rounding to avoid reading as strictly rectilinear. Use for text inputs, small tags, inline chips, and compact UI elements where a minimal hint of friendliness is appropriate.
4px
--radius-default
Default rounding. The standard border-radius for interactive components — buttons, dropdown menus, select controls, toggles, and most clickable UI elements. Visibly rounded without feeling soft or balloon-like. When in doubt about which radius to apply to an actionable component, use this one.
8px
--radius-lg
Large rounding. For contained content surfaces — cards, panels, sidebars, and info regions. Reads as a finished, elevated surface rather than a raw layout container. The rounding at this scale signals 'this is a discrete object' without being decorative.
12px
--radius-xl
Extra-large rounding. For hero cards, featured surfaces, and modals where the rounding is a conscious design decision rather than a subtle softening. Use sparingly — it competes with the system's sharp-corner default and should feel deliberate, not incidental.
16px
--radius-full
Pill or circle. Always fully rounded regardless of element dimensions. Use for status badges, avatar circles, tag chips, toggle switches, and decorative pills. At 9999px, the element becomes a perfect pill at any width or height — no recalculation needed.
9999px

Motion duration + easing

--motion-duration-instant120ms
Instant feedback. The shortest transition in the system — fast enough that the change feels immediate, but present enough to avoid a jarring cut. Use for hover color changes, focus ring appearance, active/pressed state toggles, and any micro-interaction where the UI must feel truly responsive. At 120ms, the motion is felt but not consciously watched.
--motion-duration-standard200ms
Standard transition. The default duration for most state changes involving layout or visibility — dropdowns opening, tooltips appearing, accordions expanding, tabs switching. Fast enough to feel snappy; slow enough for the eye to register what changed. The workhorse of the motion system.
--motion-duration-emphasized350ms
Emphasized transition. Reserved for large, page-level motions where the movement itself carries meaning — modal entry and exit, drawer slides, full-screen overlays, page transitions. The extra time lets the user's eye track the motion deliberately. Do not apply to hover states or small component toggles — 350ms on a button hover reads as broken.
--motion-easing-entercubic-bezier(0, 0, 0.58, 1)
Easing for elements entering the screen or appearing. Ease-out: starts fast, decelerates to rest. The deceleration signals arrival — the element lands rather than snapping into place. Use for modals appearing, drawers sliding in, tooltips fading up, dropdowns opening.
--motion-easing-exitcubic-bezier(0.42, 0, 1, 1)
Easing for elements leaving the screen or disappearing. Ease-in: starts slow, accelerates to exit. The acceleration signals departure — the element moves away with purpose rather than drifting off. Use for modals closing, drawers retracting, tooltips fading out, dropdowns collapsing.
--motion-easing-movecubic-bezier(0.42, 0, 0.58, 1)
Easing for elements repositioning within the viewport. Ease-in-out: accelerates then decelerates symmetrically, giving the motion a smooth arc. Use for sliding panels, tab indicator underlines moving between tabs, loading indicators traversing a track, or any element traveling from one place to another without entering or exiting.
--motion-easing-defaultcubic-bezier(0.25, 0.1, 0.25, 1)
General-purpose easing. CSS ease — a gentle acceleration and deceleration with no strong directional character. Use when the transition has no meaningful direction — opacity fades, color shifts, scale pulses. Falls back gracefully for any motion where enter/exit/move semantics don't clearly apply.
--motion-transition-micro120ms cubic-bezier(0.25, 0.1, 0.25, 1) 0ms
Micro-interaction transition. The fastest composite in the system, with directionless easing. Use for property changes that should feel instantaneous but not jarring: hover color shifts, focus ring fade-in, active/pressed background flips, checkbox tick reveals, link underline color swaps. Do not use for any motion that involves layout, size, or position change — at 120ms those read as twitchy.
--motion-transition-standard200ms cubic-bezier(0.25, 0.1, 0.25, 1) 0ms
Standard component transition. The workhorse composite — pair with most state changes that involve visibility or layout: dropdown menus opening, tooltips appearing, accordions expanding, tabs switching, popovers fading in. Directionless easing keeps the motion neutral so the same token can run both directions without feeling lopsided. If the element is *clearly* entering or exiting and you want to underscore that, reach for motion.easing.enter or motion.easing.exit on a custom transition instead.
--motion-transition-emphasized350ms cubic-bezier(0.42, 0, 0.58, 1) 0ms
Emphasized transition for layer-shifting motion. Reserved for full-screen or near-full-screen movement where the motion itself is part of the meaning: modal entry and exit, drawer slides, full-page sheet reveals, route-level transitions. The 350ms duration gives the eye time to follow; the symmetric in-out easing reads as a deliberate slide rather than an entrance or exit, which keeps the same token usable both ways. Do not apply to small components — emphasized timing on a hover or a dropdown feels broken and slow.
--motion-transition-move200ms cubic-bezier(0.42, 0, 0.58, 1) 0ms
Repositioning transition. Standard duration paired with symmetric in-out easing — the right combination for elements that travel from one position to another within the viewport without entering or exiting. Use for tab indicator underlines sliding between tabs, segmented control thumbs, accordion content shifting siblings, drag-and-drop snap, sticky header offset changes. The arc-shaped easing gives the motion a sense of weight and direction that linear or ease-out would flatten.

Shadow elevation, used sparingly

--shadow-none0 0 0 0 rgba(0,0,0,0)
No shadow. The element sits flush with its background — zero elevation cue. Use when depth is communicated by background color (color.background.alt) rather than shadow, or when the component is intentionally part of the surface rather than floating above it. Code blocks, data tables, inline elements.
--shadow-raised0 1px 3px 0 rgba(0,0,0,0.08)
Raised elevation. Minimal lift — just enough to separate an element from its parent surface without creating strong depth. The element reads as slightly above the canvas but still close to it. Use for inputs on hover or focus, inline dropdowns, subtle interactive feedback. On a dark background, this shadow is nearly imperceptible at rest — which is correct.
--shadow-overlay0 6px 20px 0 rgba(0,0,0,0.16)
Overlay elevation. Strong depth cue — the element is clearly floating above the page layer. Use for context menus, popover panels, dropdown lists, and any element that temporarily overlays content without dimming the background. The jump from raised to overlay is intentionally large: there is no 'card' shadow in this system — cards use background.alt for separation instead.
--shadow-dialog0 12px 40px 0 rgba(0,0,0,0.24)
Dialog elevation. The maximum shadow in the system — reserved for components that must read as the highest layer regardless of page context: modals, full-screen drawers, bottom sheets, and toast notifications. The strong shadow (0 12px 40px) grounds a floating dialog against an already-dark background. Do not apply to cards, panels, or overlays — it will feel overblown.

Icon size under construction

Provisional — the icon system isn't finalized yet, so these sizes are placeholders and may change.

--icon-size-compact16px
Compact icon. 16px — use in dense UI contexts: input adornments, tag/chip icons, tight toolbar rows. The smallest interactive icon size.
--icon-size-default20px
Default icon. 20px — the standard icon size for most UI contexts: nav items, button icons, list row glyphs, form icons. When in doubt, use this.
--icon-size-large24px
Large icon. 24px — for icons that anchor a UI region: section headers, standalone icon buttons, prominent action icons.
--icon-size-xl32px
Extra-large icon. 32px — reserved for empty states, onboarding illustrations, and feature highlight graphics. Never inline with text.

Every token above is generated at build time from Parsimony, so this page can’t drift from the source. Component-scoped tokens (badge, button, input, and so on) aren’t listed here, they live in each component’s own contract. This portfolio is the system eating its own dog food.

Back to the index or selected work.