Living catalog of every token, primitive, and pattern Sweet Shutter surfaces consume. Edit globals.css → swatches update.
docs/design-patterns.md
Colors
Two neutral systems: sky-blue chrome for the navigation shell, zero-chroma photo neutrals for image adjacency. Accent is the only chromatic moment — used sparingly.
Chrome
Sky-blue wash shared by Tier 1 (TitleBar) and Tier 2 (Toolbar). The wash itself is the `chrome-wash` gradient applied to the shell wrapper; these solid tokens are used for hovers and the active pill that sit on top.
chrome-bg
bg-chrome-bg
chrome-bg-hover
bg-chrome-bg-hover
chrome-pill
bg-chrome-pill
chrome-pill-hover
bg-chrome-pill-hover
chrome-border
bg-chrome-border
Editor workbench
Surfaces for the document editor — warm wash for the desk, cream paper for the doc, glassy translucent rails on top.
Text and icon colors. Use foreground for primary copy, muted for secondary, dim for placeholders.
foreground
bg-foreground
foreground-2
bg-foreground-2
muted
bg-muted
dim
bg-dim
Accent
Photographer-driven warm tan. Used sparingly — hairlines, focus rings, small markers.
accent
bg-accent
accent-hover
bg-accent-hover
Status
Semantic system colors. Pair with a dot or pill, not full-color fills.
positive
bg-positive
warning
bg-warning
danger
bg-danger
Selection
Vivid blue UI signal for selected items — grid cards, thumbnails, multi-select rows. Distinct from accent so it never collides with the photographer's brand color.
selection
bg-selection
Board canvas
Moodboard infinite-canvas chrome — the world-layer dot grid, frame plates, and connector strokes. Selection overlays on the canvas use the selection family.
canvas-dot
bg-canvas-dot
frame-bg
bg-frame-bg
frame-border
bg-frame-border
connector-stroke
bg-connector-stroke
Photo neutrals
Zero-chroma neutrals reserved for surfaces adjacent to photographs — nothing color-casts the work.
photo-bg
bg-photo-bg
photo-surface
bg-photo-surface
photo-bg-deep
bg-photo-bg-deep
photo-border
bg-photo-border
Typography
Inter for everything — body and display. Display surfaces differentiate via the `.font-display` utility class (tighter tracking, display-tuned OpenType features). Fixed-rem scale for UI; fluid clamp for hero/cover surfaces.
Families
font-body — Inter
The quick brown fox jumps over the lazy dog
Used for every UI element, control, and body copy.
font-display — Inter (tight tracking)
The quick brown fox jumps over the lazy dog
Reserved for page titles, editorial covers, and section headings.
UI scale
Fixed rem values, optimized for dense interface surfaces.
text-3xs
Sweet Shutter — the quick brown fox
10px
text-2xs
Sweet Shutter — the quick brown fox
11px
text-xs
Sweet Shutter — the quick brown fox
12px
text-sm
Sweet Shutter — the quick brown fox
13px
text-base
Sweet Shutter — the quick brown fox
15px
text-lg
Sweet Shutter — the quick brown fox
18px
text-xl
Sweet Shutter — the quick brown fox
23px
text-2xl
Sweet Shutter — the quick brown fox
30px
text-3xl
Sweet Shutter — the quick brown fox
38px
Display (fluid)
Clamp-based fluid type for hero/cover surfaces only. Width-responsive.
display-sm
Sweet Shutter
display-md
Sweet Shutter
display-lg
Sweet Shutter
Spacing
4-pt base scale, semantic names. Tailwind's numeric scale aligns to these tokens.
space-3xs
2px
space-2xs
4px
space-xs
8px
space-sm
12px
space-md
16px
space-lg
24px
space-xl
32px
space-2xl
48px
space-3xl
64px
space-4xl
96px
space-5xl
128px
Radii
Wired into Tailwind's rounded-* utilities — consume via class, not raw CSS variable. Tailwind's 2xl/3xl defaults (16px / 24px) still apply for softer card corners.
rounded-xs
2px
rounded-sm
4px
rounded-md
8px
rounded-lg
12px
rounded-xl
20px
rounded-full
9999px
Shadows
Semantic elevation tiers. Light-mode shadows are quiet; dark-mode shadows lift further for legibility. Always consume via class — never inline.
shadow-card-sm
Quiet card elevation — lists, inline panels
shadow-card-md
Standard card — surfaces that lift on hover
shadow-popover
Floating menus, dropdowns, tooltips
shadow-modal
Modal dialogs, drawers
shadow-paper
Document paper canvas — long soft shadow with inset highlight
shadow-rail
Editor outline + inspector rails — subtle lift on the workbench wash
Motion
Custom easings tuned for editorial reveals (expo) and UI transitions (quart). Durations escalate from fast UI to slow cover entrances.
Easings
ease-out-expo
cubic-bezier curve — applied via var(--ease-out-expo)
ease-out-quart
cubic-bezier curve — applied via var(--ease-out-quart)
ease-out-quint
cubic-bezier curve — applied via var(--ease-out-quint)
ease-in-out-quart
cubic-bezier curve — applied via var(--ease-in-out-quart)
Durations
dur-fast
150ms
dur-base
250ms
dur-slow
450ms
dur-hero
750ms
dur-cover
1100ms
Z-layers
Named stacking contexts. Use the smallest layer that does the job — escalating only when overlap demands it.
z-base
lowest
0
z-sticky
10
z-dock
20
z-overlay
30
z-drawer
40
z-lightbox
50
z-modal
60
z-toast
highest
70
Primitives
Components that ship with the chrome shell. Always consume tokens — never inline values.
Button — variants × sizes
Pill-shaped, semantic variants. Use leadingIcon for "+ New X" calls to action. primary + outline is the canonical pairing for the Page headerRight slot — never ship an icon-only auxiliary button there. tonal is the quieter filled CTA — use for section-level "+ New X" actions inside cards where an outline would feel too tentative. xs is the chip-scale size for inline actions in rail/card section headers where the neighboring label is text-2xs uppercase — pair the icon at h-3 w-3 so it stays proportional. 2xs is the micro size for hover-revealed row actions (e.g. an inline "Insert" button that fades in on row hover); pair the icon at h-3 w-3 with the 10px label.
variant="primary"
variant="tonal"
variant="outline"
variant="secondary"
variant="ghost"
variant="danger"
headerRight pairing — primary + outline
The shape every page header takes: one primary CTA on the right edge, optional outline auxiliaries to its left. Outline auxiliaries always carry a label (use leadingIcon to add iconography — never icon-only). Wrap the cluster in <Page.Actions> — it owns the canonical flex items-center gap-2 rhythm so call sites don't hand-roll the wrapper.
Menu trigger — trailingIcon
Pair Button with a trailingIcon chevron when the button opens a Menu. secondary is the canonical variant for a sole-action page header (no primary CTA to pair with).
IconButton — square, icon-only
Use for toolbar overflow triggers (kebab), fullscreen/expand toggles, sort-direction affordances — anywhere a label would be redundant. Shares Button's variant and size tokens but renders rounded-md as a square. The default ghost flavor starts muted and lifts to foreground + bg-surface-2 on hover. aria-label is required.
LinkButton
Same shape as Button, renders as <Link>. Use for navigational primary actions in the Page headerRight slot.
The canonical small-uppercase-muted label that introduces a region — settings groups (“WORKSPACE”), metric eyebrows (“STORAGE USED”), inspector subsections. Pass as to pick the semantic element (h2 for top-level page groups, default h3 for nested, p/span for non-heading uses); pass mb-3 / mb-2 via className when vertical rhythm is needed.
Workspace
…region content follows…
NavTile — categorical drill-down
Navigable list-item card with leading icon + title + description. Use for screens that present a list of categories to drill into — settings landing, account overview, future “more” pages. Lay out in a 1- or 2-column grid; the card's row shape stays consistent at either width.
Active tab gets a chrome-foreground 2px underline at the toolbar’s bottom edge. In production it sits directly on the chrome wash; the swatch below uses solid `bg-chrome-bg` for legibility.
Status pills
Tiny colored dot + label in a soft pill — never full-color fills. Used in tables and detail cards.
ActiveInvitedOnboardingReady to startBlocked
Avatar
Neutral by default — grayish fill, dark text, dark ring. Pass a curated color key for a soft tint, or src for a photo. Three sizes.
ACBBBH
BOGTATVFPF
EmptyCell — missing-value placeholder
One faint em-dash (text-dim, a step lighter than text-muted) for any empty column. Only the color is fixed — the dash inherits the cell's font size, so drop it inside the column's existing styled span. For a person/contact column, pass an empty name to PersonCell instead: it drops the avatar and reserves its footprint so the dash lines up with the names in populated rows.
Plain column: —
AC
Adriana Costa
—
FilterChip
Pill button with optional count badge and clearable x. Active state echoes the Tier-1 nav pill.
SelectChip — inline value editor
Pill trigger + dropdown menu in one — for in-table cells that double as one-click editors (project stage, session type, license scope). Pass leadingIcon for a colored dot, label for the current value, and Menu.Item children for the options. Safe inside a row <Link> — Menu.toggle stops the click from triggering row navigation.
MenuButton + ViewMenu — toolbar menu controls
Labeled toolbar buttons that open a menu — the canonical Filter / Sort / View controls above a DataTable, in the same family as DataTable.FilterButton (rounded-md, ghost → surface-2). MenuButton takes an icon + label (plus optional count / active state) and Menu.Item children. ViewMenu is the appearance/view popover — a drop-in for ViewToggle on tables (same value/onChange/options shape).
FilterBar
Filter row that sits above a DataTable. Left side carries search + chips; right side carries view-level actions.
DataTable
Data-dense table built on the canonical list-row pattern (rounded inset hover + straight dividers). Compound component — define columns once, render <DataTable.Row> with positional cells. Wrap in <DataTable.Filters> to opt into the popover-menu + chip filter system: declare a schema once, drop <DataTable.FilterButton /> + <DataTable.FilterChips /> in the toolbar, and the cascading filter menu + chip surfaces wire themselves up. Pass visibleColumnKeys/onVisibleColumnsChange to expose column show/hide — <DataTable.ActionsMenu> auto-prepends a “Configure columns” entry when those props are wired up. Mark identity columns with required: true so they can't be toggled off. Try the filter icon and the kebab (both top-right) in the toolbar below.
Person
Country
Worker type
Worker status
Start date
AC
Adriana Costa
Regional Web Admin
Taiwan
Person without a contract
Active
Jul 4 2025
AG
Alverta Gauvin
Designer
Japan
Direct Employee Payroll
Active
Nov 12 2025
AO
Anna Olsson
Manager
United States
PEO employee
Active
Nov 24 2025
BB
Bernardo Brakus
Supply Chain Analyst
United States
Contractor
Active
Aug 4 2025
BP
Billie Padberg
Legal Assistant
United States
Direct Employee Payroll
Invited
Jan 1 2025
BD
Bianca Doyle
Sustainability Officer
Svalbard
Direct Employee Payroll
Onboarding
Feb 6 2025
Spinner
The canonical loading spinner. Use <Spinner /> inline (with optional label) or <SpinnerArea /> for centered full-area loading states.
size variants
with label / area
Loading clients…
Input + Label
Form primitives. Input accepts a leadingIcon, error state via invalid, and forwards refs to the underlying element.
Select
Field-style select that opens a portaled, height-capped, scrollable listbox instead of the native OS menu — so a long list (96 time slots, every IANA zone) never balloons to the full viewport. Keyboard-accessible (arrow keys, Home/End, Enter/Space, Esc, type-ahead) with listbox/combobox semantics. Pass searchable for long lists, invalid for error state. Same portal + viewport-clamp contract as Menu.
Checkbox
16×16 tri-state box: unchecked / checked / indeterminate (the “Select all” with partial selection). Native input under the hood — owns focus and screen-reader semantics. Used inside the DataTable filter drawer's multi-select editor.
Switch
Instant-apply on/off toggle (role="switch"). The canonical settings control for boolean state that takes effect immediately — platform branding, watermark on downloads, cookie banner. Use Checkbox when the value saves on form submit instead. Name it via aria-label or aria-labelledby.
OnOffDisabled onDisabled off
FormField + FormSection
The form aesthetic for create/edit pages: stacked-label bordered shells grouped under FormSection headings. Pair with FormPage for the full surface. Apply formFieldInputClass to the inner input so it renders transparently inside the shell.
Personal
Address
Used for shipping prints and physical deliverables.
Dense
Compact density — pair `<FormField dense>` with `formFieldInputClassDense`. Use for forms with many fields where the default shell adds too much vertical weight.
FormPage — create/edit page layout
Page-level shell for forms: back link, title, description, centered single-column body (~36rem), and a footer action row. Pass onSubmit to wrap the body in a <form>; the primary action takes type="submit". Replaces ad-hoc modals once a form outgrows the modal budget — see docs/design-patterns.md → “Form pages”.
Clients
Add client
Save a new contact to your client list.
EmptyState
Icon + heading + subhead + optional action. Use for any list/grid/table that has nothing to show.
No clients yet
Add your first client to get started — they'll show up here with their tags and recent activity.
Menu
Headless popover menu — click-outside dismiss, Esc to close, selected check, optional separator. Trigger is a render prop so the consumer controls what opens it. The popover portals to document.body and tracks the trigger's bounding rect on scroll/resize, so it never gets clipped by ancestor overflow or transforms — see docs/design-patterns.md → “Popover surfaces: always portal, never clip.” Items support a leadingIcon, a description sub-line, and a right-aligned trailing affordance; group items under a <Menu.Label>. Pass iconGutter={false} for label-led list menus whose rows carry no icon (e.g. a recipient / share list) so they sit flush-left.
action / picker menu
list menu — Label + description + trailing
OptionMenu
The controlled, anchored sibling of Menu — for when the consumer owns the open state and the anchor element (e.g. an editor node view that auto-opens the picker on insert) rather than driving it from a render-prop trigger. Rows are richer than Menu.Item: a circular icon/avatar chip, a title, a wrapping description, an active-row highlight, and a trailing check. Pass selected (a boolean) for single-select option rows; omit it for a plain action row. Same portal + viewport-clamp contract as Menu (it reuses computePopoverPos). Selection does not auto-close — the consumer drives open. Canonical consumer: the contract signature picker.
controlled / anchored — rich rows
Pagination
Three-region paging controls: range readout on the left, numbered pages with prev/next arrows and ellipsis windowing in the middle, and an optional Show N page-size selector on the right. Drop it into the footer slot of a DataTable to render as a connected footer with a top divider — or use it standalone beneath a grid.
Showing 21 to 30 of 2,177
…
Show
Tabs
Controlled in-page tab bar (underline variant). Use for status / scope filters within a page — distinct from the Tier-2 UnderlineTabs which is route-driven.
Tabs — pill variant
Segmented-control style. The four labels live inside a single rounded white card; the active label gets a black background. Use when the bar floats on a wash (no anchoring divider) and you want the selection to read as a clear, contained state. Canonical: the gallery editor secondary bar.
SideNav
Floating sidebar card with hierarchical nav — top-level links, section labels, and collapsible groups whose children indent under a guide rail. Used by Settings and any page with deep sub-navigation.
Modal — overlay decision card
Three idiomatic shapes from one primitive: acknowledgement (single OK), confirm (cancel + confirm), choice (pick from a list, then confirm). Centered for sm/md, left-aligned for lg pickers. Esc and backdrop close by default; opt out for irreversible actions.
Drawer — slide-in side panel
Right- or left-anchored slide-in panel for surfaces that need their own scroll context but shouldn’t take over the viewport. Use for “See all” lists, contextual property inspectors, anything that spills out of a section card. Esc + backdrop close by default.
Toast — bottom-center confirmation
Transient, bottom-center feedback after an action — fired from anywhere with useToast(). Auto-dismisses (pausing on hover), supports an optional inline action like Undo, and stacks newest-at-bottom. Use for ephemeral success; keep persistent errors as the inline danger banner.
Calendar — inline date picker
One tokenized month grid — header + weekday row + a 6×7 day grid — with a selected day, disabled min/max bounds, and a quiet ring on today. Embed it directly instead of a native <input type="date"> wherever a date is chosen; the contract-expiry and reminder pickers both render it, so they stay visually identical. The selected day uses the primary foreground fill.
June 2026
S
M
T
W
T
F
S
No date selected yet.
ScrollFade — scroll affordance wrapper
Wrap any tall content that needs to scroll inside a bounded slot. Two faint gradient bands at the top/bottom fade in only when there’s more content in that direction — a quiet “keep scrolling” hint. Pass surface to match the wash behind the scroller so the fade reads as a vignette. Default fills its flex slot (min-h-0 flex-1); override className for fixed-height contexts.
Row 1 — scroll to see the fades soften at the top and bottom edges.
Row 2 — scroll to see the fades soften at the top and bottom edges.
Row 3 — scroll to see the fades soften at the top and bottom edges.
Row 4 — scroll to see the fades soften at the top and bottom edges.
Row 5 — scroll to see the fades soften at the top and bottom edges.
Row 6 — scroll to see the fades soften at the top and bottom edges.
Row 7 — scroll to see the fades soften at the top and bottom edges.
Row 8 — scroll to see the fades soften at the top and bottom edges.
Row 9 — scroll to see the fades soften at the top and bottom edges.
Row 10 — scroll to see the fades soften at the top and bottom edges.
Row 11 — scroll to see the fades soften at the top and bottom edges.
Row 12 — scroll to see the fades soften at the top and bottom edges.
Row 13 — scroll to see the fades soften at the top and bottom edges.
Row 14 — scroll to see the fades soften at the top and bottom edges.
Row 15 — scroll to see the fades soften at the top and bottom edges.
Row 16 — scroll to see the fades soften at the top and bottom edges.
Row 17 — scroll to see the fades soften at the top and bottom edges.
Row 18 — scroll to see the fades soften at the top and bottom edges.
Row 19 — scroll to see the fades soften at the top and bottom edges.
Row 20 — scroll to see the fades soften at the top and bottom edges.
Row 21 — scroll to see the fades soften at the top and bottom edges.
Row 22 — scroll to see the fades soften at the top and bottom edges.
Row 23 — scroll to see the fades soften at the top and bottom edges.
Row 24 — scroll to see the fades soften at the top and bottom edges.
ProfileSection — titled list card
The shape every section on a profile-style page consumes. Title (with optional count) + headerAction + a divided body of rows + optional footer. Companion row primitives: LinkRow (clickable), Row (static), LabelValueRow (factual pairs — top-level chrome export, not a ProfileSection subcomponent). For capped lists, render SeeAllRow as the trailing list item — it keeps the divider rhythm and reads as the (N+1)th row. SeeAllFooter is the legacy variant in the section's footer slot.
The compact “‹ Back to X” link that sits above a detail-page title. ProfileHeader renders this internally for its back prop — use BackLink directly on detail pages that build their own title block (project, contract, quote, invoice, …). Layout space lives at the call site via className (typically mb-4 or mb-6).
Large avatar + name + meta line + optional status + right-side action slot, with an optional back link (canonical) or breadcrumb above. Sits at the top of any profile-style page (Client profile, future User profile) above the section grid.
The document canvas: a fixed-width, content-height card holding one continuous stream. Width variants are narrow / default / wide. A manual page break (typing === on a blank line) lives inside the content and renders as stacked cards — see the contract editor.
default width (816px)
Sample Document Title
The canvas grows as tall as its content — one continuous column. Used by contracts, contract templates, and any document the photographer reads on screen.
Insert a page break by typing three equals signs on a blank line; the content below it renders as the next stacked card.
EditorWorkbench — Craft-style document chrome
The shell every doc-style editor (contracts, contract templates, future: questionnaires) composes. Three regions on a warm-tan workbench wash: a slim titlebar across the top, an outline rail (auto TOC) on the left, the floating paper canvas in the center, and a tabbed inspector on the right. Open a contract draft to see it live.
BIU
Trip Plan for 7-Day Cultural Trip
Tomorrow · 9:30 AM
Overview
This itinerary focuses on experiencing rich cultural heritage, blending traditional temples and authentic local experiences.
Reusable structural patterns. Documented in docs/design-patterns.md — link there for the canonical implementations.
Three-tier chrome — TitleBar → Toolbar → Page
The shell every dashboard surface composes. Tier 1 persists across navigations; Tier 2 is contextual to the active primary; Tier 3 is the spacious body.
List view — rounded hover row with straight dividers
Long-tail sub-navigation idiom for surfaces like Settings. Section labels · collapsible groups · indented sub-items under a guide rail. Canonical: SideNav.tsx.
Modal — overlay decision card
One primitive, three shapes: acknowledgement (single OK), confirm (cancel + confirm), choice (pick + confirm). Centered for sm/md, left-aligned for lg pickers. Canonical: Modal.tsx.
Drawer — slide-in side panel
Right- or left-anchored panel for surfaces that need their own scroll context (e.g. “See all” lists) without taking over the viewport like a modal. Canonical: Drawer.tsx.
The shape every profile-style page (Client profile, future User profile) composes. Identity block at top, then a two-column grid of titled section cards holding resource lists, with the canonical “cap at N, See all expands inline” pattern. Canonical: ProfileHeader.tsx + ProfileSection.tsx.
Form pages — FormPage + FormSection + FormField
Stacked-label bordered fields grouped under titled sections, hosted on a centered single-column page with a back link and inline footer actions. The default for any create/edit surface — modals are reserved for short, simple confirms. Canonical: FormPage.tsx.
Editor workbench — Craft-style document chrome
Three-region shell (outline rail · paper canvas · tabbed inspector) for document editors. The Craft-inspired idiom that powers the contract and contract-template editors. Canonical: chrome/editor/*.tsx.