Initial commit: FA 2.0 Design System foundation

Token pipeline (Style Dictionary v4, DTCG format):
- Primitive tokens: colour palettes (brand, sage, neutral, feedback),
  typography (3 font families, 21-variant type scale), spacing (4px grid),
  border radius, shadows, opacity
- Semantic tokens: text, surface, border, interactive, feedback colours;
  typography roles; layout spacing
- Component tokens: Button (4 sizes), Input (2 sizes)
- Generated outputs: CSS custom properties, JS ES6 module, flat JSON

Atoms (3 components):
- Button: contained/soft/outlined/text × primary/secondary, 4 sizes,
  loading state, underline for text variant
- Typography: 21 variants across display/heading/body/label/caption/overline,
  maxLines truncation
- Input: external label, helper text, error/success validation,
  start/end icons, required indicator, 2 sizes, multiline support

Infrastructure:
- MUI v5 theme with full token mapping
- Storybook 8 with autodocs
- Claude Code agents and skills for token/component workflows
- Design system documentation and cross-session memory

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 15:08:15 +11:00
commit 732c872576
56 changed files with 12690 additions and 0 deletions

View File

@@ -0,0 +1,221 @@
# Design decisions log
Every design decision that affects tokens, components, or system architecture
MUST be logged here with rationale. This ensures consistency across sessions
and agents. Agents MUST check this file before making any decision that could
contradict a previous one.
## Format
```
### [Decision ID] — [Short title]
**Date:** [date]
**Category:** token | component | architecture | convention
**Decision:** [What was decided]
**Rationale:** [Why this decision was made]
**Affects:** [Which tokens/components/files this impacts]
**Alternatives considered:** [What else was considered and why it was rejected]
```
---
## Decisions
### D001 — Primary brand colour is warm gold #BA834E
**Date:** 2025-03-25
**Category:** token
**Decision:** Primary brand colour (color.brand.500) is #BA834E — warm gold.
**Rationale:** Extracted from Parsons brand swatches. Matches the existing FA 1.0 CTA button colour ("Add to package"). Warm gold conveys trust and professionalism appropriate for funeral services. The colour has a 3.7:1 contrast ratio on white, suitable for large text and interactive elements (buttons with white text).
**Affects:** color.brand.500, color.interactive.default, MUI palette.primary.main
**Alternatives considered:** #B0610F (copper) was considered for primary but it's darker and more aggressive. Placed at brand.600 for hover/emphasis instead.
### D002 — Primary text colour is charcoal #2C2E35 (not pure black)
**Date:** 2025-03-25
**Category:** token
**Decision:** Primary text colour is #2C2E35 (color.neutral.800) — charcoal with cool blue undertone.
**Rationale:** From brand swatch. Softer than pure black for extended reading comfort while maintaining 13.2:1 contrast ratio on white (well exceeds WCAG AA). The cool undertone complements the sage secondary palette.
**Affects:** color.text.primary, MUI palette.text.primary
**Alternatives considered:** Pure black (#000000) — higher contrast but causes eye fatigue on white backgrounds. Added as color.black for rare use.
### D003 — Brand link/accent colour uses copper #B0610F
**Date:** 2025-03-25
**Category:** token
**Decision:** Brand-coloured text (links, inline emphasis) uses #B0610F (color.brand.600 / copper) not the base gold.
**Rationale:** The base gold #BA834E has only 3.7:1 contrast on white which doesn't meet WCAG AA for normal text (4.5:1 required). The copper #B0610F provides 4.8:1 contrast, meeting AA. It's also visually more assertive for link text.
**Affects:** color.text.brand, color.interactive.hover
**Alternatives considered:** brand.700 (#8B4E0D) at 6.7:1 — meets AAA but is too dark and loses the warm "brand" feel.
### D004 — Sage palette as secondary colour family
**Date:** 2025-03-25
**Category:** token
**Decision:** Secondary palette uses cool sage/slate tones (#4C5B6B as sage.700) rather than a second warm colour.
**Rationale:** From brand swatches (cool row). The sage provides visual contrast against the warm brand palette and adds a calming, professional quality appropriate for the funeral services context. Used for secondary buttons and less prominent actions.
**Affects:** color.sage.*, color.interactive.secondary, MUI palette.secondary
**Alternatives considered:** Using a lighter warm tone for secondary — rejected as it wouldn't provide enough visual distinction from primary.
### D005 — Display font is Noto Serif SC for H1-H2 headings
**Date:** 2025-03-25
**Category:** token
**Decision:** Display/heading font is Noto Serif SC for Display, H1, and H2 levels. H3+ use Montserrat (body font).
**Rationale:** User specified Noto Serif SC as display font and Montserrat as primary. The serif at H1-H2 adds warmth and gravitas suitable for funeral services. H3+ switches to Montserrat to create a clear hierarchy break.
**Affects:** fontFamily.display, fontFamily.body, typography.display/h1/h2/h3/h4
**Alternatives considered:** Using serif for all headings — rejected as it would make H3/H4 feel too formal at smaller sizes.
### D006 — Warning text uses amber.700 for AA compliance
**Date:** 2025-03-25
**Category:** token
**Decision:** Warning text colour is amber.700 (#A36B00) not amber.600 (#CC8500).
**Rationale:** amber.600 (3.6:1 contrast on white) only passes AA for large text. For warning text that must be readable at body size, amber.700 at 5.1:1 meets WCAG AA for normal text.
**Affects:** color.text.warning
**Alternatives considered:** amber.800 — passes AAA but is too dark to read as "amber/warning".
### D007 — Style Dictionary requires usesDtcg: true
**Date:** 2025-03-25
**Category:** architecture
**Decision:** Added `usesDtcg: true` to Style Dictionary v4.4.0 config.
**Rationale:** Without this flag, SD v4 doesn't properly populate `allTokens` for the CSS `css/variables` format, resulting in empty CSS output. JS and JSON formats work without it but CSS requires it for DTCG token format.
**Affects:** style-dictionary/config.js, CSS output
**Alternatives considered:** Custom format — rejected as the built-in format works correctly with the flag.
### D008 — Responsive typography via @media in MUI theme
**Date:** 2025-03-25
**Category:** architecture
**Decision:** Mobile font sizes are handled via `@media (max-width:600px)` queries in the MUI theme typography config, not via `responsiveFontSizes()`.
**Rationale:** Explicit media queries allow precise control over mobile sizes (28px, 24px, 20px, 18px) matching the token definitions. MUI's `responsiveFontSizes()` uses its own scaling algorithm that can't be configured to match our exact values.
**Affects:** src/theme/index.ts, typography.*.fontSizeMobile tokens
**Alternatives considered:** `responsiveFontSizes()` utility — rejected due to lack of precise size control.
### D009 — Merge Text Button into Button atom
**Date:** 2026-03-25
**Category:** component
**Decision:** Text Button is not a separate atom. It is implemented as `<Button variant="text">` with an optional `underline` prop for link-style appearance.
**Rationale:** In MUI, text buttons are a variant of Button (`variant="text"`), not a separate component. Having both Button and TextButton creates API confusion. The Figma "ghost" variant and "text button" both map to MUI's text variant. The `underline` prop handles the design's "default.underline" state.
**Affects:** Button component API, Storybook story structure
**Alternatives considered:** Separate TextButton component — rejected as it duplicates Button logic and creates ambiguity for developers.
### D010 — Button sizes adjusted for touch targets
**Date:** 2026-03-25
**Category:** component
**Decision:** Button sizes are xs: 28px, sm: 32px, md: 40px, lg: 48px — all multiples of 4px. Large size (48px) meets the 44px minimum touch target for mobile.
**Rationale:** Original Figma sizes (sm: 26px, md: 37px) don't meet the 44px minimum touch target specified in the design system conventions. Adjusted to a clean 4px-grid scale that includes a large size suitable for mobile CTAs. FA's audience may be older or in emotional distress — generous touch targets are important.
**Affects:** tokens/component/button.json, MUI theme MuiButton overrides
**Alternatives considered:** Keeping original Figma sizes — rejected because neither met the 44px touch target minimum.
### D011 — Use @mui/icons-material over Font Awesome
**Date:** 2026-03-25
**Category:** architecture
**Decision:** Primary icon source is `@mui/icons-material`. Custom SVGs wrapped in MUI's `SvgIcon` for any FA-specific icons.
**Rationale:** MUI icons extend `SvgIcon`, so they work natively with MUI Button's `startIcon`/`endIcon`, inherit theme colours, and are tree-shakeable. Font Awesome requires an adapter layer to work with MUI's icon API. All standard UI icons from the Figma designs exist in the MUI icon set.
**Affects:** Icon approach, Button icon integration, dependencies
**Alternatives considered:** `@fortawesome/react-fontawesome` — rejected due to adapter complexity and extra dependency. User was using FA icons in Figma but agreed MUI icons are more pragmatic for the code implementation.
### D012 — Button responsive sizing is a composition concern
**Date:** 2026-03-25
**Category:** architecture
**Decision:** Button size is an explicit prop (`size="xs" | "small" | "medium" | "large"`), not responsive by default. Layout components choose the appropriate size per breakpoint.
**Rationale:** Keeps the Button component simple and predictable. Responsive behaviour is a layout decision — a card might use `size="large"` on mobile for touch targets and `size="medium"` on desktop. This avoids the complexity of Figma variable modes for mobile/desktop scaling inside components.
**Affects:** Button component API, consumer usage patterns
**Alternatives considered:** Figma variable modes for responsive scaling within the component — rejected as over-engineered. MUI's `useMediaQuery` or responsive `sx` props handle this at the composition level.
### D013 — Soft variant for tonal/muted buttons
**Date:** 2026-03-25
**Category:** component
**Decision:** Added `variant="soft"` custom MUI Button variant for tonal/muted fill buttons. Maps to Figma's "Secondary/Brand" and "Secondary/Grey" columns.
**Rationale:** Figma review revealed that "Secondary/Brand" (bg #e4cdb3, text #845830) and "Secondary/Grey" (bg #dcdde0, text #434b52) are NOT outlined buttons — they are filled buttons with softer, tonal colours. This pattern is common in modern design systems (Material Design 3 calls it "tonal", Radix calls it "soft"). Primary soft uses brand.200 bg + brand.700 text. Secondary soft uses neutral.200 bg + neutral.700 text.
**Affects:** MUI theme MuiButton variants, Button component API, Storybook stories
**Alternatives considered:** Separate color options for each Figma column — rejected as it doesn't map to MUI's color+variant model. The soft variant is more extensible — any future color automatically gets a soft treatment.
### D014 — Secondary palette changed from sage to neutral grey
**Date:** 2026-03-25
**Category:** token
**Decision:** MUI secondary palette changed from sage (#4C5B6B sage.700) to neutral grey (#525252 neutral.600). Sage remains available in the token system for surfaces but is no longer the secondary button colour.
**Rationale:** User feedback that secondary buttons should match Figma's "Secondary/Grey" which uses neutral tones, not the blue-green sage. The sage palette is still valuable for cool surfaces (color.surface.cool) but shouldn't be the primary secondary interactive colour.
**Affects:** MUI palette.secondary, all MuiButton secondary overrides (outlined, text, soft)
**Alternatives considered:** Adding a third "neutral" custom colour and keeping sage as secondary — rejected as unnecessarily complex when no other components use the sage secondary yet.
### D015 — Loading spinner positioned on the right
**Date:** 2026-03-25
**Category:** component
**Decision:** Button loading spinner (CircularProgress) appears after the label text, on the right side of the button.
**Rationale:** User preference. Right-side positioning follows the convention of trailing indicators (like endIcon) and feels more natural for async state feedback.
**Affects:** Button component render order
**Alternatives considered:** Left-side spinner (before text) — rejected per user feedback.
### D016 — Primary button colour confirmed as copper (brand.600)
**Date:** 2026-03-25
**Category:** token
**Decision:** Primary button fill (palette.primary.main) stays as brand.600 copper (#B0610F), not brand.500 warm gold (#BA834E). User confirmed.
**Rationale:** Brand.600 provides 4.8:1 contrast ratio with white text, meeting WCAG AA for all text sizes. Brand.500 only achieves 3.7:1 (AA for large text only). Since button labels can vary in size, the stronger contrast is preferred. The visual warmth is preserved through the soft variant (brand.200 bg) and hover states.
**Affects:** palette.primary.main, all contained primary buttons, interactive token
**Alternatives considered:** Brand.500 warm gold — rejected as it doesn't meet AA for normal-sized text.
### D017 — Headings use Montserrat Bold, not Noto Serif SC
**Date:** 2026-03-25
**Category:** token
**Decision:** All headings (h1-h6) use Montserrat Bold (700), not Noto Serif SC. Noto Serif SC is reserved exclusively for display variants (displayHero through displaySm).
**Rationale:** User's FA 2.0 Figma design separates display (serif, for marketing/hero) from headings (sans-serif, for content structure). This is cleaner than the previous approach where h1-h2 used serif. Montserrat Bold provides strong hierarchy without the formality of serif at content-level sizes.
**Affects:** MUI theme h1-h6 fontFamily, semantic typography tokens. Supersedes D005.
**Alternatives considered:** Keep D005 (serif for h1-h2) — rejected per user's updated Figma design.
### D018 — Display weight is Regular (400), not Bold
**Date:** 2026-03-25
**Category:** token
**Decision:** Display text (Noto Serif SC) uses Regular weight (400) across all 5 display levels, not Bold (700).
**Rationale:** Serif fonts carry inherent visual weight. At 40-80px, Regular Noto Serif SC has enormous presence. Bold at these sizes would be overwhelming and less elegant.
**Affects:** typography.displayHero/1/2/3/Sm fontWeight tokens
**Alternatives considered:** Bold (700) — the previous implementation. Rejected per user's Figma which uses Regular.
### D019 — Body text weight is Medium (500)
**Date:** 2026-03-25
**Category:** token
**Decision:** Body text uses Montserrat Medium (500) instead of Regular (400). User found Regular too light for comfortable reading.
**Rationale:** User feedback that Montserrat Regular is "just a little bit too light." The Figma specified weight 450 which isn't available in Google Fonts, so 500 (Medium) is the closest available weight that provides the desired visual density.
**Affects:** typography.body/bodyLg/bodySm/bodyXs fontWeight tokens
**Alternatives considered:** 400 Regular — too light per user. 450 — not available in Google Fonts Montserrat.
### D020 — Expanded type scale: 21 variants across 6 categories
**Date:** 2026-03-25
**Category:** token
**Decision:** Typography system expanded from ~12 to 21 variants organized in 6 categories: Display (5 levels), Heading (6 levels), Body (4 sizes), Label (3 sizes), Caption (2 sizes), Overline (2 sizes). Caption/sm and overline/sm floored at 11px for accessibility.
**Affects:** primitive typography tokens, semantic typography tokens, MUI theme, Typography component
**Alternatives considered:** Keeping the simpler scale — rejected as user explicitly wanted more variety matching their FA 2.0 Figma type system.
### D021 — Input uses external label pattern, not MUI floating label
**Date:** 2026-03-25
**Category:** component
**Decision:** Input label sits above the input field as a separate element, not as MUI's floating/outlined label that sits on the border. Uses MUI's `InputLabel` with `position: static` and `shrink: true`.
**Rationale:** The Figma design shows the label as a distinct element above the input with a 6px gap. The floating label pattern is harder to read and interact with, especially for FA's audience who may be elderly or in distress. External labels have better usability research support for form comprehension.
**Affects:** Input component structure, MUI InputLabel styling, no notch in OutlinedInput
**Alternatives considered:** MUI's default floating label (outlined variant) — rejected as it doesn't match the Figma design and has usability concerns for the target audience.
### D022 — Input sizes: medium (48px) and small (40px)
**Date:** 2026-03-25
**Category:** component
**Decision:** Input has two sizes: medium (48px, default) and small (40px). Medium aligns with Button large (48px) for search bar pairing. Small aligns with Button medium (40px) for compact layouts.
**Rationale:** Figma designed inputs at 64px which is too tall relative to other components and creates misalignment with buttons. 48px meets the 44px touch target minimum and aligns with our Button large. Both sizes sit on the 4px grid.
**Affects:** tokens/component/input.json, MUI theme MuiOutlinedInput size overrides
**Alternatives considered:** Single size at 64px (Figma original) — rejected as it dwarfs adjacent buttons. Three sizes (sm/md/lg) — rejected as premature; two sizes cover all current form patterns.
### D023 — Input focus ring uses brand.500 warm gold, not brand.600 copper
**Date:** 2026-03-25
**Category:** component
**Decision:** Input focus ring uses brand.500 (#BA834E warm gold) via a double box-shadow (2px white + 4px brand), not the brand.600 copper used for Button focus outlines.
**Rationale:** The Figma FA 2.0 design explicitly specifies brand.500 for the input focus ring as an `elevation-special/focus-ring` effect token. The warm gold is softer and more appropriate for form inputs where the focus indicator should be visible but not aggressive. Buttons use brand.600 outline because they need to stand out against coloured backgrounds.
**Affects:** MUI theme MuiOutlinedInput focus styles
**Alternatives considered:** Using brand.600 (same as Button) — rejected as the Figma explicitly uses brand.500 for inputs.
### D024 — Input label stays neutral on error/success states
**Date:** 2026-03-25
**Category:** component
**Decision:** Input label colour remains `text.secondary` regardless of error/success state. Only the border and helper text change colour.
**Rationale:** Per the Figma design. A full-red label would feel alarming, which is inappropriate for FA's sensitive context. The subtle approach (border + helper text colour change) communicates the issue without distress. Users who are already in an emotional state don't need aggressive visual feedback.
**Affects:** InputLabel Mui-error and Mui-focused style overrides
**Alternatives considered:** Colouring the label red on error — rejected as too aggressive for the audience.
### D025 — Added startIcon/endIcon convenience props and success state
**Date:** 2026-03-25
**Category:** component
**Decision:** Input component adds three features beyond the Figma design: (1) `startIcon` prop for leading icons, (2) `endIcon` prop for trailing icons (convenience wrapper around MUI adornments), (3) `success` boolean prop for validation success state (green border + green helper text). Error-state icons (ErrorOutline) and success-state icons (CheckCircleOutline) are recommended patterns in stories.
**Rationale:** Leading icons (email, phone, dollar) are essential for FA's arrangement forms. Success state provides positive feedback after validation. The Figma only had trailing icon, but leading icons are a near-universal production need. The `startIcon`/`endIcon` props are simpler than MUI's `InputAdornment` pattern while remaining compatible with raw adornments via `startAdornment`/`endAdornment`.
**Affects:** Input component API, InputAdornment usage, Storybook stories
**Alternatives considered:** Only supporting MUI's raw adornment API — rejected as too verbose for the common case. The convenience props are ergonomic while the raw props remain available for complex cases (e.g., password toggle with IconButton).