Skip to content

Instantly share code, notes, and snippets.

@snarktank
Created February 22, 2026 12:03
Show Gist options
  • Select an option

  • Save snarktank/9fda00d58357d7b6efba5c5d53e1b243 to your computer and use it in GitHub Desktop.

Select an option

Save snarktank/9fda00d58357d7b6efba5c5d53e1b243 to your computer and use it in GitHub Desktop.
Untangle Design System — Claude Code skill + canonical design docs (visual language, design rules, interaction patterns)

Design Rules

These rules are distilled from a systematic critique of the current interface and the redesign direction established for Untangle. They are non-negotiable — every UI change should be checked against them.

Emotional context

Divorce is one of the most stressful experiences in a person's life. Users come to Untangle during a time of confusion, anxiety, and emotional volatility. The interface has a responsibility to make things feel more manageable, not more overwhelming. It should feel like a calm, competent guide — not a government form at the DMV.

DO

  1. Use warm neutrals everywhere. Every color in the palette should feel warm and grounded. The beige background (#E7E0D3), warm dark text (#363330), and sage accents (#A7A488) create a sense of calm.

  2. Reserve the gold accent for one primary action per screen. The warm gold (#F5C45B) is the brightest element in the palette. Using it sparingly gives it meaning — it says "this is the thing to do next."

  3. Use progressive disclosure. Reveal complexity gradually. Show the current phase's tasks, not all 40+ tasks at once. Show task details in a sheet, not inline. Let users drill deeper when they're ready.

  4. Collapse completed work. Once a phase is done, it shouldn't dominate the screen. Minimize it — hint at its presence (e.g., a subtle card stack behind the current phase) but give focus to what's next.

  5. Use human, approachable language. "Prepare Your Paperwork" instead of "Form Preparation." "Create your Summons" instead of "Generate Summons (JD-FM-3)." The interface should sound like a helpful friend, not a legal filing system.

  6. Set expectations. Include time estimates on phases and tasks. Provide "What you'll need" checklists before a task begins. Give brief context explaining what each phase is about and why it matters. Reducing unknowns reduces anxiety.

  7. Let the UI speak for itself. Minimize labels, annotations, and explanatory text when the visual design communicates the same thing. Clean interfaces feel more trustworthy.

  8. Make primary actions visually obvious. There should be no ambiguity about what to do next. Primary buttons are prominent (gold, full-width or near it). Secondary actions are quieter. Tertiary actions are text links.

  9. Create clear visual hierarchy. In any view, the user should be able to instantly identify: where they are, what to do next, and how much is left. Size, weight, color, and spacing should all reinforce this hierarchy.

  10. Differentiate containers with fill, not stroke. Cards sit on the warm beige background as white surfaces. Sections use #F8F6F1 for subtle differentiation. No heavy borders or shadows needed.

  11. Preserve dashboard stack hierarchy. For the active phase card stack, use three distinct fills and offsets: back #F2EEE7 (taller), middle #F6F5EF (shorter), front #FFFFFF. If stack colors blend or heights invert, treat as a bug.

  12. Keep the estimate pill and phase-number badge the same height. On mobile this is 40px for both, so the header row reads as a balanced pair.

  13. Keep mobile composer width intentionally narrower than the main card. The floating “Ask anything” bar should not span card edge-to-edge; it should read as a separate control.

  14. Use semantic spacing tokens, not numeric spacing classes. Default to xxxs, xxs, xs, s, m, l, xl, xxl for layout rhythm (m*, p*, gap*, space-*, positioning offsets).

  15. For forms, keep label/help text close to inputs and separate fields from each other. Intra-field spacing should be tighter than inter-field spacing so users can immediately see which description belongs to which field.

  16. For dynamic list sections, keep heading/error/action rhythm deliberate. Use a compact outer stack, show array-level errors directly above the add action, and add explicit separation before the action in empty-error states.

  17. Use standard control heights. Default to h-10 for standard controls, h-9 for compact controls, and h-11 for prominent CTAs; keep controls in the same action row the same height.

  18. Keep skeleton loaders on the same palette as dashboard. Use bg-muted for soft blocks and bg-muted-foreground/25 for primary line placeholders; update loaders whenever the corresponding surface styling changes.

  19. Use shared form wrappers for user-facing form controls. On app surfaces, use components/forms/* wrappers (for example FormTextField, FormMoneyField, FormTextarea, FormSelectField, FormRadioGroup, FormCheckbox) instead of importing raw field primitives directly.

  20. Use shared destructive button variants. Use Button variants (destructive for high-emphasis confirms, destructive-ghost for inline delete/remove actions) instead of ad-hoc destructive class combinations.

  21. Keep styling directives in design-system docs only. Put visual rules in docs/design-system/* and keep AGENTS.md files focused on workflow, validation, and architecture guidance.

  22. Use outlined secondary buttons as the standard secondary action treatment. Secondary actions should use a subtle warm border (rgba(54, 51, 48, 0.1)), while primary and tertiary actions stay borderless.

  23. Keep form controls borderless. Inputs, textareas, and select triggers should use fill-based surfaces rather than outlined borders.

  24. Treat shared form container spacing as canonical. FORM_FIELD_CONTAINER_CLASS is the default field rhythm owner. Do not override it in feature components with ad-hoc containerClassName margin resets unless the exception is explicitly documented.

NEVER

  1. Never use pure black or cool grays. No #000000, no gray-500, no blue-tinted neutrals. Every dark should be warm (#363330 for primary, #6B6862 for muted). Cool tones feel clinical and detached.

  2. Never use blue as a primary color. The current default blue is generic and cold. It communicates nothing about the brand or the emotional context. The warm gold accent replaces it.

  3. Never use purple for AI elements. Purple is the default "AI generated" color across the industry. Using it immediately signals that the interface was made by AI, which undermines the trust and expertise the design needs to convey.

  4. Never apply shadows to cards or containers. Shadows feel muddy against the warm palette and add visual noise. Use fill color layering instead — white cards on beige backgrounds create natural elevation without shadow.

  5. Never show all tasks at once. A flat list of 40+ items is demoralizing. Always group tasks into phases. Show only the current phase's tasks by default. Let users explore other phases if they want, but don't force the full scope on them.

  6. Never use strokes as primary container differentiation. Heavy borders add visual weight and noise. When a border is absolutely needed, use rgba(54, 51, 48, 0.1) — barely visible, warm-toned.

  7. Never make completed items visually prominent. Completed phases/tasks should recede. No bright checkmarks, no heavy styling. A muted sage indicator and collapsed layout is sufficient. The user's attention belongs on what's next.

  8. Never use clinical or legal terminology in user-facing text when human language works. "JD-FM-3" means nothing to a user. "Your financial affidavit" does. Legal form numbers can appear in metadata or tooltips, never as the primary label.

  9. Never show raw task counts like "10/47 tasks complete." This is demoralizing. Instead, communicate progress through phases: "Phase 2 of 5" or a simple progress bar scoped to the current phase.

  10. Never animate to auto values. Framer Motion cannot interpolate height: "auto" or width: "auto" — it will snap at the end of the animation. Always use fixed pixel values for animated dimensions.

  11. Never show both chat composers at once. If desktop side chat is visible, hide the floating dashboard “Ask anything” composer.

  12. Never rely on emulated viewport-only browser runs for responsive QA. Visual verification must use a normal headed window so manual resizing reflects real responsive behavior.

  13. Never introduce new raw numeric spacing utilities by default. Avoid classes like mt-3, px-4, space-y-1; use semantic tokens unless a one-off calibration is explicitly documented.

  14. Never let label padding create loose label-to-input spacing. If a field feels disconnected, tighten the label/help-to-input stack first and only then adjust field-to-field spacing.

  15. Never stack container space-y-* spacing and extra add-button mt-* spacing without intent. In list-empty states this creates oversized gaps and inconsistent form rhythm.

  16. Never add decorative icons by default. Icons should appear only when they provide functional meaning.

  17. Never import raw form primitives directly on app surfaces. Avoid direct imports of @/components/ui/input, @/components/ui/textarea, @/components/ui/select, @/components/ui/checkbox, and @/components/ui/radio-group outside shared wrapper/component files.

  18. Never hand-roll destructive button styling on app surfaces. Do not compose one-off destructive button classes when Button destructive variants already cover the pattern.

  19. Never add UI styling directives to AGENTS.md files. If a visual rule changes, update docs/design-system/design-rules.md and/or docs/design-system/visual-language.md instead.

  20. Never use ad-hoc button outlines outside the standard secondary treatment. Do not introduce one-off border colors/widths for button variants when secondary actions already use the canonical warm outline pattern.

  21. Never use outlined form-field treatments. Do not add border-based styling as the default for user-facing input/select/textarea controls.

  22. Never neutralize shared form container rhythm by default. Avoid local overrides like containerClassName=\"mb-0\" that compete with FORM_FIELD_CONTAINER_CLASS; if an override is truly required, document and annotate it explicitly.

Machine Enforcement

The following commands are the canonical enforcement contract for these rules:

  • npm run lint:design-system (changed files + global shared-constant checks)
  • npm run lint:design-system:all (full-repo sweep)
  • npm run lint:spacing-tokens
  • npm run lint:form-components
  • npm run lint:app-page-styles
  • npm run lint:heading-typography
  • npm run lint:agents-no-style-directives

CI runs npm run lint:design-system -- --against-ref <base-sha> so design drift fails pull requests deterministically.

Interaction Patterns

These patterns define how Untangle should feel to use — not just how it looks, but how information is structured, how users navigate, and how the interface communicates. Every pattern here exists to reduce anxiety and keep users focused on what matters right now.

Phase-based architecture

The core filing process has 40+ tasks. Showing them in a flat list is overwhelming and demoralizing. Instead, group tasks into phases — like chapters in a story.

Structure

  • 5–7 phases covering the full divorce filing process
  • Each phase has a number, a human-readable title, and 3–8 tasks within it
  • Phases are the root-level navigation: users see all phases first, then drill into tasks

Display behavior

  • Current phase: Expanded, showing its task list in a white card. Phase number displayed in a badge, title is large serif text. Brief description explains what this phase is about and roughly how long it takes.
  • Completed/previous phase previews: Shown as a subtle two-layer stack behind the current phase: back layer #F2EEE7 (intentionally taller), middle layer #F6F5EF (intentionally shorter), front layer #FFFFFF.
  • Future phases: Visible below the current phase in minimal form: compact number chip + title, nothing else. Tappable to preview, but not expanded by default.

Why this works

  • Users always know where they are (current phase is prominent)
  • They can see overall progress at a glance (completed stack + future list)
  • They're never confronted with the full scope of 40+ tasks
  • The metaphor is "chapters in a guided journey" rather than "bureaucratic checklist"

Progressive disclosure

Information should be revealed when the user is ready for it, not all at once.

Layers of detail

  1. Phase list (root view) — Phase names, numbers, overall progress
  2. Task list (phase expanded) — Task names with completion state, within the current phase
  3. Task detail (bottom sheet) — Full description, "What you'll need" checklist, time estimate, action buttons
  4. In-progress work (within sheet) — Forms, document downloads, external links

Bottom sheets for task details

When a user taps a task, a bottom sheet slides up from below rather than navigating to a new page. This is intentional:

  • User stays anchored in the phase context — they can still see where they are behind the overlay
  • Sheet has a grab indicator at top, signaling it can be dismissed with a swipe
  • When they complete the task and the sheet dismisses, they immediately see the result in the task list — tighter feedback loop
  • No navigation stack to manage, no "back button" cognitive overhead

Task detail sheet structure

From top to bottom:

  1. Grab indicator — centered bar, subtle
  2. Task title — large serif text (22px, weight 600)
  3. Description — 1–3 sentences explaining what this task involves
  4. "What you'll need" section — Bulleted checklist of materials/information to gather upfront (reduces anxiety — users know exactly what they're getting into)
  5. Time estimate — Badge showing approximate duration (e.g., "~10 min")
  6. Actions — Buttons with clear visual hierarchy (see below)

Action hierarchy

Every screen should have at most one primary action. Visual weight tells users what to do in what order.

Level Style Example
Primary Gold bg (#F5C45B), dark text, prominent size "Download Document", "Continue"
Secondary Outlined, warm border, same size "Visit Court Website"
Tertiary Text link, muted color, smaller "Mark as Done", "Skip for now"

The hierarchy guides users through the natural sequence: get the document, do what's needed with it, confirm completion.

Progress communication

Current approach (avoid)

  • "10 / 47 tasks complete" — demoralizing flat count
  • Persistent progress bar across all tasks — makes scope feel enormous

Better approach

  • Phase-level progress: "Phase 2 of 5" or a segmented progress indicator showing phases as distinct chunks
  • Within-phase progress: Simple count or progress bar scoped to the current phase only (e.g., "3 of 6 tasks")
  • Completed phases stack: The visual card-stack of completed phases behind the current one communicates progress without numbers
  • Never show the total task count across all phases

Chat integration

Chat (the AI assistant) is a supporting modality, not a peer destination. It helps users understand their tasks and answers legal questions, but it doesn't replace the structured task flow.

Architecture

  • Main view (mobile): A floating text input/composer sits at the bottom of the phase view, inviting questions.
  • Main view (desktop with side chat): The floating composer is hidden; only the side chat composer is shown.
  • Tapping the composer: Expands chat into a bottom sheet (or full overlay) — a focused conversational space.
  • Dismissing chat: Returns to the phase view. Chat history persists.

Chat visual treatment

Element Style
User messages Sage (#A7A488) background, white text, fully rounded bubbles
AI responses Warm beige (#E7E0D3) background, dark text, fully rounded bubbles
Legal citations Muted text (#6B6862), progressively disclosed (collapsed by default)
Composer bg White (#FFFFFF) floating shell, fully rounded
Send button Gold (#F5C45B), dark icon, rounded

Key principles

  • AI responses should be scannable — use clear visual hierarchy, not walls of text
  • Legal research and statute references should be progressively disclosed — show the answer first, let users expand to see supporting citations
  • Don't dump raw legal text. Users aren't lawyers. Summarize, then offer the detail.

Dashboard spacing baseline

Use these values as the implementation baseline for /dashboard:

  • Active card padding: pt-10, pb-6, px-8.
  • Header row (number + estimate): mb-3.
  • Number badge and estimate pill: both 40px tall on mobile.
  • Description spacing: mt-3 mb-4.
  • Focused checklist rows: py-3.
  • Future-phase list spacing: container space-y-5; each row gap-4, py-2.

Language & tone

The tone should feel like a calm, knowledgeable friend walking you through the process — not a legal filing system.

Phase titles

Phase titles should feel like chapters in a guided journey:

Before (clinical) After (human)
Information Gathering Build Your Picture
Form Preparation Prepare Your Paperwork
Court Filing File With the Court
Service of Process Notify Your Spouse
Financial Disclosure Share Financial Details

Task names

Tasks should be action-oriented and free of legal form numbers:

Before (clinical) After (human)
Generate Summons (JD-FM-3) Create your Summons
Complete Financial Affidavit (JD-FM-6) Fill out your Financial Affidavit
File Appearance (JD-CL-12) File your Court Appearance
Service of Process Deliver papers to your spouse

Legal form numbers (JD-FM-3, etc.) can appear in metadata, tooltips, or the document itself — never as the primary label a user reads.

Phase descriptions

Each phase should include a brief 1–2 sentence description that answers "what is this and why does it matter?":

Prepare Your Paperwork We'll help you fill out the forms the court needs. Most people complete this in 2–3 sessions.

File With the Court Time to submit your paperwork. We'll walk you through exactly what to bring and where to go.

General copy guidelines

  • Use "you" and "your" — speak directly to the user
  • Use action verbs: "Create", "Review", "Submit" — not "Generation", "Completion", "Submission"
  • Include time estimates: "~10 min", "Most people finish in 2–3 sessions"
  • Acknowledge difficulty when appropriate: "This is the longest form, but we'll walk you through it step by step"
  • Never assume the user understands legal terminology — explain in plain language first
name description version tags dependencies
untangle-design-system
Entry-point skill for Untangle design-system work. Use this skill to locate and apply the canonical design-system documents in the repo (visual language, design rules, interaction patterns) without duplicating those standards here.
1.3.0
theme
style
redesign
visual
color
typography
layout
UI
component
design-system
navigation
copy
tone

Untangle Design System

This skill is a router, not a second source of truth.

Canonical sources (read first)

  • docs/design-system/visual-language.md
  • docs/design-system/design-rules.md
  • docs/design-system/interaction-patterns.md

How to use this skill

  1. Identify what changed (theme tokens, component styling, copy, navigation, flow, etc.).
  2. Read only the relevant sections in the canonical docs above.
  3. Implement changes from those docs directly.
  4. If docs conflict, treat the canonical docs as source of truth and flag drift.

Scope triggers

Use this skill when work involves:

  • Theme/CSS token updates
  • Component visual styling decisions
  • Form rhythm and typography decisions
  • Dashboard/phase/task interaction patterns
  • User-facing UI copy and tone

Validation

Follow repository validation requirements in AGENTS.md files for the touched paths.

Visual Language

The visual language for Untangle creates a warm, grounded, trustworthy aesthetic. It pairs a modern serif display font with a clean sans-serif body font, uses a warm neutral palette with a single bright accent, and relies primarily on fill color (with a subtle outlined secondary action treatment) to create hierarchy.

Typography

Display font: Source Serif 4

Install: Google Fonts — weights 400, 500, 600.

Source Serif 4 is a modern serif with subtle slab-serif characteristics. It conveys stability, competence, and sincerity without feeling old-fashioned. It was chosen over EB Garamond (too rustic/austere) and Libre Baskerville (too ornate/traditional) as the right bridge between old-world authority and modern digital clarity.

Use for headlines, page titles, phase titles, and any display-sized text.

Usage Size Weight Letter-spacing Line-height
Page title 22–24px 600 -0.3px to -0.5px 1.2
Phase/section header 18px 500 -0.25px 1.3
Sheet title 22px 600 -0.3px 1.25
Phase badge number 20px 500

Body font: Inter

Install: Google Fonts — weights 400, 500, 600.

Inter is optimized for screen readability. It feels familiar and doesn't force brand personality onto detailed form content where legibility matters most. It replaces the current Arial/Helvetica stack.

Usage Size Weight Notes
Body text 15–16px 400 Line-height 1.4–1.5
Task names 16px 500 Line-height 1.4
Descriptions 14–15px 400 Line-height ~20–22px
Buttons 15–16px 500–600
Labels / overlines 13px 600 Uppercase, tracking-wide
Time estimates 14px 500

App heading constants (required)

For logged-in app surfaces, use shared heading constants from components/ui/heading-styles.ts:

  • APP_HEADING_H1_CLASS (semantic <h1>): text-mobile-h3 desktop:text-h3
  • APP_HEADING_H2_CLASS (semantic <h2>): text-mobile-h4 desktop:text-h4
  • APP_HEADING_H3_CLASS (semantic <h3>): text-mobile-small desktop:text-small
  • APP_HEADING_H4_CLASS (semantic <h4>): text-mobile-tiny desktop:text-tiny

Do not hardcode raw text-mobile-h* / desktop:text-h* utility classes on shared app heading components.

For page-level title + description blocks on logged-in app routes, use components/layout/AppPageIntro.tsx so spacing remains consistent.

For logged-in page-shell rhythm and width, use shared constants from components/layout/app-page-styles.ts:

  • APP_PAGE_BACKGROUND_CLASS
  • APP_PAGE_TOP_SPACING_CLASS
  • APP_PAGE_CONTENT_X_PADDING_CLASS
  • APP_PAGE_MAX_CONTENT_WIDTH_CLASS
  • APP_PAGE_CARD_BACKGROUND_CLASS
  • APP_PAGE_INTRO_STACK_CLASS

CI enforcement:

  • npm run lint:heading-typography
  • npm run lint:app-page-styles
  • npm run lint:design-system

Form typography calibration

Use these defaults for form-heavy screens (especially case-details):

  • Field labels: text-mobile-small desktop:text-small font-medium leading-tight
  • Field descriptions/help: text-mobile-tiny desktop:text-tiny leading-relaxed text-muted-foreground
  • Validation errors: text-mobile-tiny desktop:text-tiny font-medium leading-tight
  • Checkbox/radio option labels: text-mobile-small desktop:text-small leading-relaxed

Form labels and helper text should stay visually linked to their input; hierarchy comes from size/weight/color, not large vertical gaps.

Color palette

The palette is warm and restrained. Heavy use of off-whites and warm beige with a single bright accent color — a warm, sun-like gold that adds a touch of hope to an otherwise understated design.

Core colors

Name Hex Usage
Warm Beige #E7E0D3 Main app background
Soft Neutral #F8F6F1 Secondary surfaces, estimate badges
White #FFFFFF Focused content areas, cards, sheets
Dark Warm #363330 Primary text, primary button text
Muted #6B6862 Secondary/muted text
Light #9C9890 Tertiary text, disabled states
Placeholder #B5B1AA Input placeholder text
Gold Accent #F5C45B Primary actions, send button, highlights
Sage #A7A488 Completed states, user chat bubbles, secondary actions
Stack Preview 1 #F2EEE7 Back-most dashboard card preview layer
Stack Preview 2 #F6F5EF Middle dashboard card preview layer
Future Phase Dot rgb(156, 154, 138) Collapsed phase number chip
Border rgba(54, 51, 48, 0.1) Subtle dividers and borders when needed
Light Border rgba(54, 51, 48, 0.05) Very subtle separation
Overlay rgba(0, 0, 0, 0.3) Sheet/modal backdrop

Shadcn CSS variable mapping

Update these in your globals.css (light mode). Values are in HSL format to match shadcn conventions, with hex equivalents noted:

:root {
  /* #E7E0D3 — warm beige */
  --background: 39 29% 87%;
  /* #363330 — warm dark */
  --foreground: 30 5% 20%;

  /* #FFFFFF */
  --card: 0 0% 100%;
  /* #363330 */
  --card-foreground: 30 5% 20%;

  /* #FFFFFF */
  --popover: 0 0% 100%;
  /* #363330 */
  --popover-foreground: 30 5% 20%;

  /* #F5C45B — warm gold accent */
  --primary: 42 89% 66%;
  /* #363330 — dark text on gold */
  --primary-foreground: 30 5% 20%;

  /* #F8F6F1 — soft neutral */
  --secondary: 40 30% 96%;
  /* #363330 */
  --secondary-foreground: 30 5% 20%;

  /* #F8F6F1 */
  --muted: 40 30% 96%;
  /* #6B6862 */
  --muted-foreground: 30 4% 40%;

  /* #A7A488 — sage */
  --accent: 68 12% 59%;
  /* #FFFFFF */
  --accent-foreground: 0 0% 100%;

  /* Keep your existing destructive red */
  --destructive: 0 84% 60%;
  --destructive-foreground: 0 0% 100%;

  /* rgba(54, 51, 48, 0.1) approximated */
  --border: 30 5% 20% / 0.1;
  --input: 30 5% 20% / 0.1;

  /* #F5C45B — gold ring */
  --ring: 42 89% 66%;

  /* Border radius */
  --radius: 0.75rem;

  /* Sidebar (if using shadcn sidebar) */
  --sidebar: 40 30% 96%;
  --sidebar-foreground: 30 5% 20%;
  --sidebar-primary: 42 89% 66%;
  --sidebar-primary-foreground: 30 5% 20%;
  --sidebar-accent: 34 33% 89%;
  --sidebar-accent-foreground: 30 5% 20%;
  --sidebar-border: 30 5% 20% / 0.1;
  --sidebar-ring: 42 89% 66%;
}

Note: The HSL values above are approximate conversions. Fine-tune by eye in your app. Current production calibration uses --background: 39 29% 87% (maps to rgb(231, 224, 211)).

Corners & shapes

The corner strategy balances friendliness with precision:

Element Radius Reasoning
Buttons, pills, badges Fully rounded (9999px) Friendly, approachable, inviting to tap
Chat bubbles, composer Fully rounded Conversational, warm
Cards, content panels 16–20px Soft but structured
Sheets, modals (top) 28px Smooth, natural feel sliding up
Inputs, small containers 12px Subtle rounding, not pill-shaped
Icons Sharp / square-capped Precision and authority contrasting soft containers

The contrast between rounded containers and sharp icons is intentional — soft shapes feel approachable and safe, while crisp icons convey precision and competence.

Icons

Use bold, confident icons with square-capped strokes (not rounded). This creates a sense of precision and authority that balances the soft, rounded containers. Stroke width should be 2px default, 1.5px for smaller contexts.

Use icons only when they add functional meaning (status, affordance, orientation). Do not add decorative icons by default.

The current icon library (Lucide) can stay — just ensure:

  • Stroke linecap is square where possible (override at the SVG level if needed)
  • Icons are sized consistently: 20px (w-5) default, 24px (w-6) for nav, 16px (w-4) for inline

Containers

The primary method for creating visual hierarchy is fill color, not borders or shadows.

Layer Color Usage
Page background #E7E0D3 The warm base everything sits on
Secondary surface #F8F6F1 Estimate badges, subtle insets
Card / content #FFFFFF Active task lists, sheets, modals
Preview layer 1 #F2EEE7 Back-most dashboard preview card
Preview layer 2 #F6F5EF Middle dashboard preview card
Inset within card #F8F6F1 Nested sections within white content

No shadows. Shadows against the warm beige background look muddy. The fill color contrast between #E7E0D3#FFFFFF creates sufficient elevation.

Minimal borders. When a divider or border is needed, use rgba(54, 51, 48, 0.1) — a 10% opacity warm dark. For very subtle separation, rgba(54, 51, 48, 0.05). Never use gray-200 or similar cool defaults.

Component guidance

How key shadcn components should be styled:

Button

Variant Background Text Border Radius
Primary (default) #F5C45B (gold) #363330 none fully rounded
Secondary transparent #363330 rgba(54, 51, 48, 0.1) fully rounded
Ghost / tertiary transparent #6B6862 none fully rounded
Destructive keep existing red white none fully rounded
Destructive ghost destructive-tint surface destructive token none fully rounded

Destructive action standard:

  • Use variant="destructive" for high-emphasis irreversible actions in confirmations.
  • Use variant="destructive-ghost" for inline/list-row remove actions (for example Delete expense).
  • Do not hand-roll destructive button classes on app surfaces; use shared Button variants.
  • destructive-ghost must remain visibly styled at rest (default state), not hover-only, so touch/mobile users see a clear destructive affordance.
  • Secondary actions use the canonical warm outline (rgba(54, 51, 48, 0.1)).
  • outline is the canonical secondary variant name and should render with that warm border.

Control height standards:

  • Compact controls: 36px (h-9) for dense secondary actions.
  • Standard controls: 40px (h-10) for default buttons and input/select triggers.
  • Prominent controls: 44px (h-11) for CTA or high-emphasis actions.
  • Icon buttons should match the paired text-control height within the same action row.
  • Mixed-height action rows are a visual bug unless explicitly intentional.

Card

  • Background: #FFFFFF
  • Border: none (remove default shadcn border)
  • Shadow: none (remove default shadow)
  • Radius: 16–20px
  • The contrast against the #E7E0D3 page background is the elevation

Skeleton loaders

  • Base skeleton blocks should use bg-muted.
  • Primary text-line skeletons should use bg-muted-foreground/25.
  • For stacked dashboard-style preview layers, preserve #F2EEE7 (back) and #F6F5EF (middle).
  • Treat loader styling as part of page polish: when a surface is redesigned, update its loading/skeleton state in the same pass.

Dialog / Sheet

  • Background: #FFFFFF
  • Top border-radius: 28px (for bottom sheets)
  • Overlay: rgba(0, 0, 0, 0.3)
  • Include a grab indicator bar at top of sheets: w-9 h-[5px] rounded-full bg-[rgba(54,51,48,0.15)]

Input / Textarea

  • Background: secondary surface fill (#F8F6F1)
  • Border: none
  • Radius: 12px
  • Placeholder color: #B5B1AA
  • Focus state: keep borderless, increase contrast with fill/ring only
  • Implementation token: FORM_FIELD_SURFACE_CLASS in components/ui/form-styles.ts (use this instead of hardcoded field fill classes)

Select triggers should follow the same visual treatment as text inputs: borderless, fill-based surfaces.

Shared form-control wrappers (required)

User-facing form surfaces should use shared wrappers in components/forms/:

  • Text input: FormTextField (or specialized wrappers like FormPhoneNumberField, FormZipCodeField)
  • Money/currency input: FormMoneyField
  • Textarea: FormTextarea
  • Select/dropdown: FormSelectField
  • Radio group: FormRadioGroup
  • Checkbox: FormCheckbox

Raw primitives from components/ui should be used to build shared wrappers/components, not directly in route-level form UI. CI enforcement:

  • npm run lint:form-components (changed files)
  • npm run lint:form-components:all (full repo)
  • npm run lint:design-system

Badge

  • Completed/success: #A7A488 bg, white text
  • Default/inactive: #F8F6F1 bg, #6B6862 text
  • Radius: fully rounded

Progress

  • Track: #E7E0D3 or #F8F6F1
  • Fill: #A7A488 (sage) for general progress, #F5C45B (gold) for active/current

Trial banner

  • Container: bg-secondary (#F8F6F1) with bottom border using border-border.
  • Vertical rhythm: py-2.5 mobile, py-3 desktop.
  • Width: use APP_PAGE_MAX_CONTENT_WIDTH_CLASS so banner width matches app form/detail page content width.
  • Copy + CTA layout:
    • Centered stack on narrow screens, inline row on larger screens.
    • Text and link both use warm foreground color (no default blue).
  • Typography:
    • Banner text size 0.9375rem, line-height 1.4.
    • Link weight 600; body copy regular with bold day count.

Checkbox

  • Size: 22px
  • Border: 1.5px warm border
  • Checked: #A7A488 (sage) background with white checkmark
  • Radius: fully rounded (circle)
  • Checkmark stroke: 3px, white

Separator / Divider

  • Color: rgba(54, 51, 48, 0.1)
  • Height: 0.5px or 1px
  • No margin-heavy separators — let spacing do the work when possible

Marketing asset styles

These standards apply to generated/static marketing visuals and marketing flow-chart components.

Sketch illustration style (scripts/marketing-images)

  • Style: hand-drawn sketchnote with stick-figure characters.
  • Core accent colors: lime green (#6FCF97), violet (#9B51E0), cyan (#2D9CDB), with black linework.
  • Keep illustrations simple and high-contrast; avoid photoreal rendering and heavy gradients.

Flow chart style (components/marketing)

  • Rendering target: 1080x1080px.
  • Node types:
    • flow: 180x180px, rounded-square shape.
    • center: 240x240px, circular gradient focal node.
  • Color-role palette:
Token Usage
hotPink Final results
electricBlue Primary actions
vibrantPurple Spouse/other party
sunOrange Guidelines
limeGreen Calculations
cyan Add-ons
coral Time/schedules
gold Money

Social video animation style (workflows/social-atomizer)

For generated motion clips that animate sketchnote portraits:

  • Stick figures can move subtly (gesture, nod, wave).
  • Arrows can animate with a draw-on or light swoosh.
  • Icons/symbols can use gentle pulse, bounce, or wiggle motion.
  • Text stays static (no text movement or text animation).
  • No camera pan/zoom; animate elements in place.

Spacing tokens

Use semantic spacing tokens as the default spacing API across the app:

Token Value
xxxs 2px
xxs 4px
xs 8px
s 16px
m 24px
l 32px
xl 48px
xxl 80px

Apply these tokens to spacing utilities (m*, p*, gap*, space-*, inset*, top/right/bottom/left).

Preferred examples:

  • mt-s, mb-m, px-s, py-m
  • gap-xs, space-y-s
  • top-l, inset-x-s

Avoid raw numeric spacing classes like mt-3, px-4, space-y-1 unless there is a documented one-off calibration requirement. For required exceptions, add a short inline comment.

Form spacing rhythm

For readable field grouping:

  • Keep intra-field spacing tight (label/description/error/input stack).
  • Keep inter-field spacing larger than intra-field spacing.
  • Avoid adding extra bottom padding directly on labels when the goal is tighter label-to-input proximity.

Current case-details/shared-form calibration:

  • Small field stacks use space-y-0 to space-y-xxxs.
  • Base FormItem and shared wrappers use space-y-xxxs for consistent label-to-field spacing.
  • Typical text/textarea/phone blocks add bottom separation (mb-s) so each field group is clearly separated.
  • Shared implementation tokens live in components/ui/form-styles.ts:
    • FORM_FIELD_CONTAINER_CLASS for standard field blocks
    • FORM_FIELD_STACK_CLASS for label/help/error/input stacks
    • FORM_FIELD_CONTROL_TOP_SPACING_CLASS for case-details calibrated control offset (data-slot inputs/select/textarea)
    • FORM_FIELD_LABEL_CLASS and FORM_FIELD_DESCRIPTION_CLASS for field typography
  • Address field groups use larger inter-field separation (space-y-m) with tight label-to-input proximity inside each field.
  • Dynamic list sections (for example Add case / Add person) should use a compact outer stack (space-y-s) and render the repeated card list in a nested space-y-m wrapper only when items exist.
  • If a top-level list validation error appears above an add button while the list is empty, add mt-s to the add button so error text and action are clearly separated.

Dashboard calibration (current)

Use these values as the implementation baseline for /dashboard:

  • Main card: #FFFFFF, rounded-[1.375rem], px-8, pt-10, pb-6.
  • Preview stack colors:
    • Layer 1 (back): #F2EEE7
    • Layer 2 (middle): #F6F5EF
  • Preview stack sizing:
    • Layer 1 uses inset-x-6 -top-2 bottom-0 (intentionally taller).
    • Layer 2 uses inset-x-3 top-0 bottom-3 (intentionally shorter).
  • Header row in card: mb-3.
  • Number badge: h-10 w-10 with number text 1.5rem.
  • Estimate pill: h-10, uppercase, must visually match number-badge height.
  • Description spacing: mt-3 mb-4.
  • Focused task row spacing: py-3.
  • Collapsed future phase row spacing: space-y-5, each row py-2, gap-4.
  • Collapsed future phase typography:
    • Number dot: h-6 w-6, text-[0.8125rem], bg rgb(156,154,138).
    • Title: text-[1.1875rem], font-display.
  • Mobile floating composer:
    • Width: calc(100vw - 5rem) to stay slightly thinner than the main card.
    • Background: rgb(255,255,255).
    • Hidden whenever desktop side chat is active.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment