Sweet Shutter

Design system

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.

workbench-bg

bg-workbench-bg

workbench-bg-deep

bg-workbench-bg-deep

paper-bg

bg-paper-bg

paper-bg-cover

bg-paper-bg-cover

paper-border

bg-paper-border

rail-bg

bg-rail-bg

rail-bg-hover

bg-rail-bg-hover

inspector-bg

bg-inspector-bg

inspector-tab-active

bg-inspector-tab-active

Surface

UI surfaces inside Tier 3 page bodies. Warm-tinted neutrals.

background

bg-background

surface

bg-surface

surface-2

bg-surface-2

surface-3

bg-surface-3

row-hover

bg-row-hover

hover

bg-hover

nav-selected

bg-nav-selected

nav-hover

bg-nav-hover

nav-selected-hover

bg-nav-selected-hover

border

bg-border

border-strong

bg-border-strong

Foreground

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.

SectionLabel — uppercase kicker

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.

NavPill — Tier 1 primary nav

Selected pill uses chrome-pill; inactive pills lift on hover via chrome-bg-hover.

UnderlineTabs — Tier 2 sub-tabs

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.

Projects(8)

Client information

  • Emailadriana@example.com
  • Phone(415) 555-1212
  • LocationBoulder, CO
  • Referred byThe Knot
  • Client sinceMay 12, 2026

BackLink — detail-page return link

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).

ProfileHeader — profile-page identity block

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.

Back to clients
AC

Adriana Costa

Active
adriana@example.com·(415) 555-1212

Paper — document canvas

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.

Day 1: Tokyo
  • Arrive at Narita Airport
  • Hotel check-in

Components: EditorWorkbench · EditorTitleBar · Paper · OutlineRail · Inspector + InspectorSection, InsertList.

Patterns

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

    Wrapper owns the divider; inner row gets rounded hover. Canonical: ListView.tsx.

  • Floating side-nav card with nested groups

    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.

  • Profile pages — ProfileHeader + ProfileSection grid

    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.

FF Studio