First molecule component. Listing card for funeral providers on the provider select screen (map + scrollable list layout). - Verified providers: hero image, 48px logo overlay, "Trusted Partner" badge, name, location, reviews, capability badge, footer with price - Unverified providers: text-only with same content alignment - 7 component tokens (image height, logo size, footer/content spacing) - Composes Card (interactive, padding="none") + Badge + Typography - Capability badges accept arbitrary label + colour (categories may change) - Footer bar with warm brand.100 bg, "Packages from $X >" - 9 Storybook stories including mixed list layout demo - Decisions D026-D030 logged Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
22 KiB
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).
D026 — ProviderCard establishes the molecule pattern
Date: 2026-03-25
Category: architecture
Decision: ProviderCard is the first molecule. Molecules compose existing atoms via sx props — no MUI theme overrides. All styling from token CSS variables and theme accessors.
Rationale: Keeps molecules lightweight and composable. Theme overrides are reserved for atoms where global consistency matters. Molecules are higher-level compositions with more context-specific layouts.
Affects: All future molecules (VenueCard, MapCard, ServiceOption, etc.)
Alternatives considered: Adding MuiProviderCard theme overrides — rejected as molecules are page-specific compositions, not reusable primitives.
D027 — ProviderCard image is a URL string, not a ReactNode
Date: 2026-03-25
Category: component
Decision: imageUrl prop accepts a string rendered as CSS background-image with cover. Logo is also a URL string rendered as <img>.
Rationale: Simpler API for the common case (CDN URLs). CSS background-image handles aspect ratio cropping automatically. <img> for logo supports alt text natively for accessibility.
Affects: ProviderCard, VenueCard, MapCard APIs
Alternatives considered: ReactNode for image — rejected as over-flexible. A ReactNode slot would allow video/carousel but adds API complexity for a feature we don't need yet.
D028 — Logo is 48px (not 75px from Figma)
Date: 2026-03-25 Category: component Decision: Logo overlay is 48px diameter, not the 75px shown in Figma. Rationale: On a ~340-400px card in a list panel, a 75px logo dominates the content area. 48px provides clear brand visibility while leaving sufficient space for the provider name and meta row. Affects: providerCard.logo.size token Alternatives considered: 75px from Figma — too large for the card width. 64px — still large relative to content.
D029 — Footer is built into ProviderCard, not a slot
Date: 2026-03-25
Category: component
Decision: The "Packages from $X >" footer is rendered via a startingPrice number prop, not a children/slot pattern.
Rationale: The footer is structurally identical across all provider cards — warm background, "Packages from" text, price, chevron. Only the price value changes. A slot would add API complexity for no flexibility gain. If footer is omitted (no startingPrice), the bar is simply absent.
Affects: ProviderCard API, VenueCard may use a similar pattern
Alternatives considered: children/render prop for footer — rejected as over-engineered for a fixed layout.
D030 — Verified is an explicit boolean, not derived from imageUrl
Date: 2026-03-25
Category: component
Decision: verified boolean prop controls the visual treatment independently from imageUrl/logoUrl.
Rationale: A provider could be verified but have a broken/missing image URL. The boolean is the source of truth from the business layer. Image and logo are only rendered when verified is also true, preventing accidental display of unverified providers with images.
Affects: ProviderCard API, data model expectations
Alternatives considered: Deriving verified from imageUrl presence — rejected as it couples business logic (partner status) to content availability (image uploaded).