Skip to content
Design System 2026.05.27

A system, not a collection of one-offs.

The canonical foundation for devin.vc. Every typographic decision, every color, every animation curve lives here. Build with these primitives. Resist the urge to make new ones.

08 Foundation tokens
09 Primitives
Composable surfaces
01 Overview

The discipline.

This system replaces the dozens of bespoke styles scattered through page sections with a small set of primitives whose variants are sufficient. If you find yourself reaching for a custom font-size, a raw hex value, or a one-off clamp() — stop. Open this page. Find the variant. Or extend the system here, not in the consuming component.

The rules are blunt. No font-family declarations in components — use var(--font-display), var(--font-body), or var(--font-mono). No raw hex outside global.css. No fresh clamp() values for spacing — pick from the eight scale tokens. Animation timing comes from the three duration tokens and two easing curves, nothing else.

02 Color

Warm, restrained, theme-aware.

All color flows through CSS custom properties defined in global.css. Dark theme is default; light theme swaps via [data-theme="light"]. Toggle above to compare. Never write raw hex values in components.

--color-bg Page background
--color-surface Elevated surface
--color-text Primary text
--color-text-muted Secondary text
--color-text-subtle Tertiary text
--color-accent Warm gold accent
--color-accent-teal Cool accent
--color-accent-amber Warm secondary
--color-accent-rust Saturated accent
--color-error Error / destructive
--color-success Confirmation
03 Typography

Two voices.

Funnel Display variable for headlines — a technical, architectural sans-serif. DM Sans variable for body. var(--font-mono) for code and metadata. No fourth font. Ever.

Display · serif

A quiet partner

var(--font-display)
Body · sans

Capital, placed carefully.

var(--font-body)
Token Value Sample
--text-4xl clamp(4rem, 9vw + 1.5rem, 7.5rem) Quiet capital
--text-3xl clamp(3rem, 5.5vw + 1.4rem, 4.75rem) Selected work
--text-2xl clamp(2.125rem, 3.5vw + 1.2rem, 3rem) A founder’s partner
--text-xl clamp(1.625rem, 2vw + 1.1rem, 2.125rem) Section heading
--text-lg clamp(1.25rem, 0.9vw + 1.05rem, 1.5rem) Leading paragraph for emphasis at the top of long passages.
--text-md clamp(1.0625rem, 0.45vw + 0.95rem, 1.1875rem) Body emphasis sits a step above the baseline.
--text-base 1rem Body. The neutral default for prose, set in DM Sans at 1rem with 1.55 line-height.
--text-sm 0.875rem Caption and metadata copy lives here.
--text-xs 0.75rem Eyebrow and label text, uppercase, tracked wide.
04 Spacing

Eight scales. No more.

Every padding, margin, and gap pulls from one of these tokens. If a layout needs something between two scales — fix the scale, not the layout.

--space-section Between major sections clamp(8rem, 20vh, 14rem)
--space-page-top Top of every page clamp(8rem, 18vh, 12rem)
--space-page-bottom Bottom of every page clamp(4rem, 10vh, 8rem)
--space-block-lg Large block padding clamp(4rem, 8vw, 6rem)
--space-block Standard block padding clamp(3rem, 6vw, 5rem)
--space-element Between elements in a stack clamp(1.5rem, 3vw, 2.5rem)
--space-card Card interior padding clamp(1.5rem, 3vw, 2rem)
--space-page-x Horizontal page gutter clamp(1.5rem, 5vw, 3.5rem)
05 Motion

Two curves. Three speeds.

Each curve is rendered as a position-vs-time graph. The trailing dot demonstrates the curve in motion. Static SVG ensures the comparison is visible even with prefers-reduced-motion.

--ease-out-expo var(--ease-out-expo)

Default. Entrances, hovers, micro-interactions.

--ease-in-out-smooth var(--ease-in-out-smooth)

Symmetrical transitions, view transitions, scroll.

--duration-fast

200ms

Hovers, focus, color shifts

--duration-normal

350ms

Entrances, layout changes

--duration-slow

500ms

Heroes, emphasized moments

06 Surface

Edges and depth.

Radius is small and restrained — 2px on buttons, 4px on cards. Sharp edges read editorial; round edges read consumer-app. Shadows are layered, color-aware, and reserved for genuine elevation.

--shadow-sm

Hairline lift

--shadow-md

Card elevation

--shadow-lg

Modal / overlay

07 Heading

The voice of the page.

Semantic level decoupled from visual size. Use level for HTML semantics, size for visual weight. Default family is serif; switch to sans for utility headings.

size="4xl"

Quiet capital

size="3xl"

Selected work

size="2xl"

A founder's partner

size="xl"

Section heading

size="lg"

A softer note

size="md" · family="sans"

Utility heading

size="sm" · family="sans"

Small label heading

tone="muted"

Recessed heading

tone="accent"

Highlighted

<Heading level={1} size="4xl">Quiet capital</Heading>
<Heading level={2} size="3xl">A founder’s partner</Heading>
<Heading level={3} size="md" family="sans">Utility heading</Heading>

// Props
//   level:   1..6                            (semantic)
//   size:    '4xl' | '3xl' | '2xl' | 'xl' | 'lg' | 'md' | 'sm'
//   family:  'serif' | 'sans'                (default: serif)
//   tone:    'default' | 'muted' | 'accent'
//   balance: boolean                         (default: true)
//
// Headings never render italic. Nested <em> tags are normalized.
08 Text

Every paragraph, every label.

One component for all non-heading copy. Variants cover size, weight, tone, family, and leading. measure caps line length at 62ch — use it for long-form prose.

size="lg" · leading="relaxed"

A leading paragraph that sets up the section with a slightly larger, looser feel.

size="base" · default

Body copy. Neutral, readable, set at 1rem with 1.55 line-height. The workhorse.

tone="muted"

Muted body for secondary information, captions under primary content, supporting prose.

size="sm" · tone="subtle"

Quiet metadata. Dates, byline, footnotes.

family="serif" · italic

"Pulled quotes and emphasized prose use the display serif in italic."

family="mono" · tone="accent"

$ deploy --quiet 2026-05-27

<Text size="lg" leading="relaxed" measure>...</Text>
<Text tone="muted">...</Text>
<Text family="serif" italic>...</Text>

// Props
//   as:      'p' | 'span' | 'div'           (default: p)
//   size:    'xs' | 'sm' | 'base' | 'md' | 'lg'
//   tone:    'default' | 'muted' | 'subtle' | 'accent'
//   weight:  'light' | 'regular' | 'medium'
//   family:  'sans' | 'serif' | 'mono'
//   italic:  boolean
//   leading: 'tight' | 'normal' | 'relaxed'
//   measure: boolean                         (caps at 62ch)
09 Tag

Eyebrows and status.

The repeated section-tag pattern, formalized. Optional numeric index, rule, uppercase tracked. Status tones for success/warning/danger.

index + rule 05 Projects
tone="accent" 05 In progress
no index Featured
tone="muted" Archive
size="md" 02 Now reading
tone="inverse" Highlighted
tone="success" Confirmed
tone="warning" Pending
tone="danger" Action required
<Tag index={5}>Projects</Tag>
<Tag tone="accent">In progress</Tag>
<Tag tone="success">Confirmed</Tag>
<Tag tone="danger">Action required</Tag>

// Props
//   index: string | number | undefined
//   tone:  'default' | 'accent' | 'muted' | 'inverse'
//          | 'success' | 'warning' | 'danger'
//   size:  'sm' | 'md'
//   rule:  boolean                           (default: true)
10 Button

Five variants, three sizes.

Renders as a <button> by default; pass href to render as an anchor with identical styling. Full state coverage: hover, active, focus-visible, disabled, loading.

primary
secondary
ghost
link
danger
states As anchor
<Button variant="primary" size="lg" arrow>Continue</Button>
<Button variant="secondary" href="/work">View work</Button>
<Button variant="link" arrow>Read more</Button>
<Button variant="danger">Delete</Button>
<Button variant="primary" loading>Submitting</Button>

// Props
//   variant:   'primary' | 'secondary' | 'ghost' | 'link' | 'danger'
//   size:      'sm' | 'md' | 'lg'
//   href:      string                        (renders as <a>)
//   target:    '_self' | '_blank'
//   type:      'button' | 'submit' | 'reset'
//   disabled:  boolean
//   loading:   boolean
//   arrow:     boolean
//   fullWidth: boolean
11 Card

Surfaces with intent.

Default is flat — soft tinted surface, the workhorse for grouping content. Use outlined when you need a hairline boundary, elevated when something genuinely lifts off the page, grain when texture earns its keep.

01 Default

Flat surface

Soft background tint, no border. The neutral workhorse for grouping content blocks.

02 Outlined

Hairline border

Transparent surface with hairline boundary. Use for definition without lift.

03 Elevated

Genuinely lifted

Tinted surface plus layered drop shadow. Restrained, never neon — in dark mode the surface tint carries most of the lift.

04 Grain

Textured surface ↗

Film grain overlay. Intensifies on hover. Interactive.

01 Header slot

2026.05.27

Composed example

Header, body, and footer compose via Svelte snippets. The card handles its own internal rhythm — consumers just supply content. The footer uses a short accent hairline at the top-left to signal section break without dividing the card heavily.

<Card padding="md">...</Card>                       <!-- default: flat -->
<Card variant="outlined" padding="md">...</Card>
<Card variant="elevated" padding="lg" href="/journal">...</Card>

<Card variant="elevated" padding="lg">
  {#snippet header()}<Tag>Header</Tag>{/snippet}
  {#snippet footer()}<Button variant="link" arrow>Action</Button>{/snippet}
  <Heading>Body content</Heading>
</Card>

// Props
//   variant: 'flat' | 'outlined' | 'elevated' | 'grain'  (default: flat)
//   padding: 'none' | 'sm' | 'md' | 'lg'
//   href:    string                          (renders as <a>)
//   target:  '_self' | '_blank'
//   header / media / footer:  Snippet
12 Field

Input, with a line animation.

No boxes, no chrome — just an underline that grows on focus from a teal-to-amber gradient. md is the default form scale; lg is the tool-wizard scale, big and editorial, for marquee questions that deserve the spotlight.

LG Large variant — tool-wizard scale

Press Enter to continue.

Default (md) — form scale

We'll never share this.

<Field label="Email" name="email" type="email" required />
<Field label="Stage" name="stage" type="select" options={[
  { value: 'seed', label: 'Seed' },
  { value: 'a',    label: 'Series A' },
]} />
<Field label="Notes" name="notes" type="textarea" rows={4} />
<Field label="Email" name="bad" error="Invalid address" />

// Props
//   type:         'text' | 'email' | 'tel' | 'url' | 'password'
//                 | 'search' | 'number' | 'textarea' | 'select'
//   name:         string                     (required for forms)
//   id:           string                     (required for label/control link)
//   label:        string
//   placeholder:  string
//   value:        string                     (bindable)
//   helper:       string
//   error:        string                     (replaces helper, sets aria-invalid)
//   required:     boolean
//   disabled:     boolean
//   size:         'md' | 'lg'
//   rows:         number                     (textarea)
//   options:      { value, label }[]         (select)
//   autocomplete: string
13 Stack

Rhythm, made obvious.

The vertical-rhythm primitive. Numeric gaps (16) for component-internal spacing on a 4pt scale; token-named gaps for layout-level rhythm.

vertical · gap="4"

First child

Second child. Spacing comes from the Stack, never the children. Margins on Heading/Text remain zero.

horizontal · gap="3" · align="center"
Draft
<Stack gap="4">
  <Heading>Title</Heading>
  <Text>Body</Text>
  <Button>Action</Button>
</Stack>

<Stack direction="horizontal" gap="3" align="center">
  <Button>Save</Button>
  <Button variant="ghost">Cancel</Button>
</Stack>

// Props
//   direction: 'vertical' | 'horizontal'
//   gap:       '0'..'6' (4pt scale)
//              | 'element' | 'card' | 'block' | 'block-lg' | 'section' (tokens)
//   align:     'start' | 'center' | 'end' | 'stretch' | 'baseline'
//   justify:   'start' | 'center' | 'end' | 'between' | 'around'
//   wrap:      boolean
14 Section

Page-level containers.

The container primitive. Handles horizontal page gutter, max-width, and vertical block spacing in one place. Use it instead of writing padding-inline on every page section.

---
import Section from '../components/ui/Section.astro';
---

<Section spacing="loose" width="normal">
  <Heading level={1} size="4xl">Quiet capital</Heading>
</Section>

<Section spacing="normal" width="narrow">
  <Text measure>Long-form prose at narrow width.</Text>
</Section>

// Props
//   spacing: 'compact' | 'normal' | 'loose'
//   width:   'narrow' | 'normal' | 'wide' | 'full'
//   bleed:   boolean                         (no horizontal padding)
//   as:      string                          (default: 'section')