Skip to main content

Design system

The building blocks every Front Door screen is made from: the tokens in DESIGN.md and the shadcn components on Base UI that carry them. Build new screens from these.

Overview

A calm service page in every state: plain words, one big box, people to call, and nothing that looks like a startup landing page or a government website.

Whatever you need, start here

A picture of the big box, for this page only.

  • One box. Every public page offers one large box to describe what you need.

  • People to call are always close. Every footer says: In danger, call 911. In crisis, call or text 988.

  • One primary action per screen. Everything else is outline, quiet, or a link.

  • Tell the truth about state. Submitted only when the agency has it. Say what we cannot check.

  • Whitespace first, then hairlines, then cards. Cards only for things you can act on.

  • Safety by default. No sensitive text in addresses. Masked values.

Foundations

The tokens in app/globals.css, by what they are for. Every component on this page is built from these and nothing else.

Color roles

Each surface with its text, measured in your browser. Body text needs 4.5:1; every pair here passes.

Aa
Canvas and ink--background / --foreground
Aa
Card--card / --card-foreground
Aa
Menus and dialogs--popover / --popover-foreground
Aa
The one solid action--primary / --primary-foreground
Aa
Primary on hover--pine-2 / --primary-foreground
Aa
Quiet fill--secondary / --secondary-foreground
Aa
Well and muted text--muted / --muted-foreground
Aa
Highlighted row--accent / --accent-foreground
Aa
Bad news on the canvas--background / --destructive
Aa
Body text--paper / --ink-2
Aa
Muted text--paper / --ink-3
Aa
Muted text in the gray panel--frame / --ink-3
Aa
Good outcome--moss / --leaf
Aa
Needs attention--marigold-soft / --amber
Aa
Bad news--brick-soft / --brick
Aa
Neutral information--sky-soft / --ink

Palette

Pine is the only solid color. Status colors appear only where status is the point, and always with words.

Surfaces

  • Paper, the canvaspaper · #ffffff
  • Paper 2, wells and the footerpaper-2 · #f3f4f6
  • Frame, the gray hero panelframe · #eef0f3

Text

  • Ink, titlesink · #14171c
  • Ink 2, bodyink-2 · #2f3747
  • Ink 3, muted (never lighter)ink-3 · #5b6271

Action

  • Pine, the one solid colorpine · #0b2c63
  • Pine 2, its hoverpine-2 · #133a7c

Lines and focus

  • Line, hairlines between contentline · rgb(20 23 28 / 0.1)
  • Field, the edge of what you type infield · #7c828c
  • Ring, the focus outlinering · #3b6fd8

Status

  • Leaf on moss, goodmoss · #e4f2ea / #17603c
  • Amber on marigold soft, attentionmarigold-soft · #fdf1d8 / #7a4a00
  • Marigold, dots and the door markmarigold · #f0b23e
  • Brick on brick soft, bad newsbrick-soft · #fbe4dd / #a3341c
  • Sky soft, neutral chipssky-soft · #e8eef8

Charts, by kind of help

  • Chart 1chart-1 · #2a78d6
  • Chart 2chart-2 · #eb6834
  • Chart 3chart-3 · #1baf7a
  • Chart 4chart-4 · #eda100
  • Chart 5chart-5 · #e87ba4
  • Chart 6chart-6 · #008300
  • Chart 7chart-7 · #4a3aa7
  • Otherchart-other · #a3a19a
  • Muted, while one series is litchart-muted · #e3e5e9

Typography

Newsreader at weight 400 for titles, Inter for everything you read and press, Plex Mono for what you copy. Sentence case. No italics in headings, no uppercase labels.

TokenSpecUseSpecimen
home-titleNewsreader 400, 36 to 80px, 1.05, -0.02emThe national home's titleWhatever you need, start here
display-xlNewsreader 400, 0.95, -0.05emBig titles on the landingsHelp that fits your life
PageHeaderNewsreader 400, 44px, 60px from 640pxResident and public page titlesRenewal emails
ConsoleHeaderNewsreader 400, 40px, 48px from 640pxAgency console titlesInbox
SectionNewsreader 400, 24px, 28px from 640pxSection titles on resident pagesComing up
Card nameNewsreader 400, 22pxA place or program named on a cardNorthside Community Pantry
LeadInter 400, 18px, 20px from 640px, ink-2The line under a page titleWe fill in the agency's form. You read it and sign.
BodyInter 400, 16px, 15px from 640px, ink-2Running textYour Basic Food renewal is due November 22, 2026.
LabelInter 500, 15px, inkField names and buttonsEmail address
SmallInter 400, 14px, ink-3Help under a field, table cellsWe only use it to send renewal reminders.
CaptionInter 400, 13px, ink-3Sources and checked datesListed on wa211.org, checked on October 10, 2026
MonoIBM Plex Mono 400, 15pxMasked numbers, reference numbers, links to copy***-**-4821 · localcarehub.org/r/maria
Running text at body size, with a link and inline code

We fill in the agency's form from your vault: your saved answers and documents. You read it and sign. When the agency has it, the status says Submitted, and not before. Every program page says where its answer comes from, like the Department of Social and Health Services, and when we last checked it. Reference numbers show in Plex Mono, like DSHS-2026-118204.

Spacing

Tailwind's 4px steps. Whitespace first, then hairlines, then cards.

  • 1 · 4px
  • 2 · 8px
  • 3 · 12px
  • 4 · 16px
  • 6 · 24px
  • 8 · 32px
  • 10 · 40px
  • 12 · 48px
  • 16 · 64px
  • Inside a component · 8px (gap-2)

    An icon and its label in a button

  • Between related items · 12 to 16px (gap-3, gap-4)

    A field's label, box and help line; buttons in a row

  • Between groups · 24 to 32px (gap-6, gap-8)

    Fields in a form; cards in a list

  • Between sections · 48px

    Sections in a resident page's column (Section in Column)

Radius

Pills for what you press, 16px for what you type in, 24px for cards. Pinned in app/globals.css at Tailwind's own values.

  • sm · 4pxCheckboxes, small marks in rows
  • lg · 8pxSmall buttons inside tables
  • xl · 12pxMenu and select rows, tooltips, sidebar rows
  • 2xl · 16pxText fields and textareas, menus, popovers, toasts, alerts
  • [1.25rem] · 20pxDialogs, sheets, the account sidebar
  • 3xl · 24pxCards and wells
  • [2.5rem] · 40pxHero frames
  • full · pillButtons, selects, chips, toggles, the big box

Elevation

Four levels. Most things are flat; only what floats over the page has a shadow.

  • FlatThe canvas, wells, the footer. Whitespace and hairlines do the work.
  • HairlineCards: a 1px ring at 10% ink, no shadow.
  • RaisedMenus, popovers, tooltips, toasts (shadow-lg).
  • OverlayDialogs and sheets (shadow-overlay) over the page lightly blurred.

Motion

Short and calm: 150 to 300 ms, ease-out, crossfades, no bouncing. Reduced motion stops all of it.

  • prompt-in240 msThe next example in the big box
  • scene-fade300 msThe hero card's screens crossfading
  • surface-in220 msA dialog, sheet or card fading in
  • message-in260 msA new turn or answer block
  • pill-in220, 50 apart msFollow-up pills
  • fd-rise200, 150 apart msProgram cards arriving in the conversation
  • Sheet, accordion200 msSliding in, opening a fold
  • Toast300 msSliding up from the bottom
  • Switch150 msThe knob moving across

Icons

A few inline marks in components/shadcn/icons.tsx, no icon package. 16px inside components, 20px in rows and the sidebar, always decorative.

16px ink

20px ink

20px ink-3

Actions

One primary action per screen. Everything else is outline, quiet, or a link.

Button

Pills. Pine is the only solid color. Button and LinkButton in components/ui.tsx draw from this same recipe, under their own names.

components/shadcn/button.tsx

Typical: one primary, then the rest outline
Every variant in every state. Pressed changes nothing: Front Door has no press state.
defaulthoverfocusactivedisabledloading
default
outline
secondary
ghost
destructive
link
Sizes: sm, default (44px at 16px text), lg
With an icon: leading, trailing, icon only (44px)
Long label on a phone: it wraps, it never scrolls sideways
components/ui.tsxshadcn ButtonWhen
primarydefaultThe one action on the screen
secondaryoutlineEvery other button
quietlinkAn action that reads as a link
dangerdestructiveReject, Deny, Withdraw
warn (deprecated)defaultOld code only; use primary or danger
sm, md, lgsm, default, lgSizes

Toggle and ToggleGroup

Two to five views of the same thing, like Weeks or Months. The picked one is white with a hairline ring.

components/shadcn/toggle-group.tsx

defaulthoverfocusactivedisabled
off
on

Badge

A word of status in its color: words and color together, never color alone.

components/shadcn/badge.tsx

LikelyDue in 14 daysIn reviewDemo dataDraftLocalNew
Food banks, shelters, clinics and legal aid groups

A long badge truncates; put the full words in its title or the row.

Inputs

Labels always show, in sentence case. The edge of every field is the 3:1 field gray, darkening on hover and a 2px pine ring on focus.

Field

Every form control sits in a Field with its label, help and error. Problems are said in plain words under the box.

components/shadcn/field.tsx

Typical

We only use it to send renewal reminders.

Change it in Settings.

Several lines

Please do not include Social Security, card or bank numbers. 0 of 2,000 characters.

Input in every state
defaulthoverfocusdisabledinvalid
input

Select and NativeSelect

NativeSelect for plain choices on phones first (the phone's own picker). Select when rows need more than text. Pass items so the box shows the label.

components/shadcn/select.tsx · native-select.tsx

Select
NativeSelect

Checkbox, RadioGroup and Switch

A checkbox for a yes you confirm, radios for one choice of a few, a switch for a setting that takes effect at once.

components/shadcn/checkbox.tsx · radio-group.tsx · switch.tsx

Checkbox
RadioGroup
How it travels
Switch
Choices as cards: a FieldLabel around each Field
Label on its own, for a control outside a Field

Data display

Cards only for things you can act on. Lists are rows on hairlines.

Card

A white card with a hairline ring and 24px corners, for a task, a program or a place you can act on.

components/shadcn/card.tsx

Basic Food
Money for groceries each month, from DSHS.
Likely
A household of 3. We checked every rule we could, so it says Likely.
Northside Community Pantry, a long name that runs onto a second line on a phone
Food pantry · also free meals · 0.4 miles from the center of Spokane
Open now until 1 PM. Bring an ID if you have one.

Table

From 768px; phones get rows on hairlines. Counts under 10 read Under 10.

components/shadcn/table.tsx

ProgramDueStatus
Basic FoodNov 22Due in 42 days
Apple HealthJan 3, 2027In review
Working Families Tax CreditDec 31Submitted

Avatar

A person is their initials on navy. A photo only when there is one.

components/shadcn/avatar.tsx

MGMGMG
MGJTAL
+4

No photo: the initials show. The marigold dot means something needs the person.

Separator

The hairline between content. Whitespace first, then hairlines, then cards.

components/shadcn/separator.tsx

Mail it for me

TermsPrivacyArt credits

Accordion

Folds on hairlines for what most people skip: the numbers behind a chart, the full calculator.

components/shadcn/accordion.tsx

A table of the same values as the chart, for screen readers and anyone who wants the numbers.

Feedback

Say what happened in plain words, in the place it happened. No shimmer, and never a spinner on its own.

Alert

A callout line on a soft well. The color says what kind of news; the words always say it too.

components/shadcn/alert.tsx

toast

Only to confirm something the person just did. Anything they must read stays on the page.

components/shadcn/toast.tsx

Spinner

Inside a button that is working, with words. Never on its own as the only sign.

components/shadcn/spinner.tsx

Skeleton

The still shape of a card while it loads. It does not pulse.

components/shadcn/skeleton.tsx

Empty

A page with nothing in it yet. Inside a section, use one line between hairlines instead (EmptyLine).

components/shadcn/empty.tsx

Nothing saved yet
Save a program from its page and it shows here.

Overlays

Each opens from a real button. Focus moves in, Escape closes, and focus goes back to the button.

Dialog

Sheet

Popover

Tooltip: names an icon button. Never the only place a fact lives.

DropdownMenu: 44px rows, Sign out in brick.

Do and don't

The rules in DESIGN.md that are easiest to break, side by side.

One primary action per screen. Everything else is outline, quiet, or a link.

Do
Don't

Labels are sentence case. No uppercase eyebrows, no italics in headings.

Do

For officials

Where people ask for help

Don't

For officials

Where people ask for help

Text plus color, never color alone.

Do
LikelyMaybeUnlikely
Don't

The edge of a field is the field gray, 3:1 on every surface, so people can see where to type.

Do
Don't

Recipes and an example screen

Front Door's own patterns, rebuilt from the components above.

Section with rows

Every /me page: a Newsreader heading, a link beside it, then rows on hairlines. Empty is one line.

components/ui.tsx: Section, SectionLink, Rows, EmptyLine

Coming up

All renewals
  • Basic Food renewalNov 22 · 42 days
  • Apple Health renewalJan 3, 2027

Inbox

Nothing to read right now.

Subject to funds

On a program page above the buttons: marigold while money lasts, brick when it is closed, with the agency's own words.

FundsLine in components/ProgramAnswer.tsx

Example screen: a resident's tasks

Built only from tokens and components/shadcn. Sample values: Maria is the demo's made-up person.

Example

Made-up person. Nothing here is sent.

Tasks

You have 2 things to do.

Due Nov 22 · 42 days
Send your pay stubs to DSHS
DSHS asked for your last 30 days of pay stubs in its letter of October 3, 2026.
From Apple Health
Sign your Apple Health renewal
We filled it in from your vault. Read it and sign before December 1.

In danger, call 911. In crisis, call or text 988.

What makes it Front Door: one column, one primary action per task, every status in words, the crisis line close, and nothing but tokens.

Inbox

Linked reports above a calm letter list. Sample letters, with no database or messages sent.

Empty

No letters in the current view, with a plain next step.

Letters

0 unread
No letters in this view
No mail yet. When an agency writes, it shows up here.

Open a sample letter to read it. These letters are examples, and nothing is sent.

Loading

Still shapes hold the letter rows while the data loads.

Letters

Loading letters

Open a sample letter to read it. These letters are examples, and nothing is sent.