Skip to main content

Design System Reference

Non-negotiable rules

  1. All UI copy must use Text (semantic roles) or a composition primitive that uses Text internally.
  2. Direct MUI Typography imports are banned outside src/components/text/**. Enforced by ESLint no-restricted-imports.
  3. Blog and photography are not design-system exceptions. Blog uses prose context. Photography uses inverse-tone overlay context.
  4. When a text role is missing, add it to the typeset registry — do not work around the system with local sx overrides.

Text authoring decision tree

  • UI chrome (nav, button labels, chips) → Text role="inlineLabel" or Text role="label"
  • Structural UI (section/card headings, meta, body) → Text role="..." with the appropriate role
  • Long-form reading (blog articles) → Text with prose roles (proseParagraph, proseHeading, etc.)
  • Text on imagery (photography overlays) → Text tone="inverse" context="overlay" with the appropriate role
  • Exception boundary (IDE hero, CV story mode) → UnsafeTypography with required _unsafe metadata

How to extend roles and typesets

When a needed role does not exist:

  1. Add the role to the TextRole union in src/types/text.ts
  2. Add the typeset definition in src/styles/textStyleBuilders.ts
  3. Add a default semantic element mapping in src/components/text/Text.tsx
  4. Update the role table in this document
  5. Add at least one usage example

Enforcement mechanisms

  • ESLint no-restricted-imports: Blocks @mui/material/Typography and Typography barrel import from @mui/material. Override only in src/components/text/**.
  • UnsafeTypography wrapper: Requires _unsafe metadata with reason, owner, and expiresBy. Only used in explicitly sanctioned exception modules.
  • Typeset builder: Typography styling is centralized in src/styles/textStyleBuilders.ts, not in ad-hoc sx on text consumers.

This is the concrete catalog of established UI surfaces, primitives, and patterns. Before introducing a new pattern, start with the existing foundations below and adapt the narrowest existing layer that fits.

For architecture-level context, see Component architecture and Theme and styling.

Core Rules

  • Prefer shared route scaffolds before page-local layout.
  • Prefer semantic Text roles before styling raw Typography.
  • Prefer shared card and panel surfaces before inventing a new container.
  • Prefer Stack, shared gap helpers, and shared padding profiles before ad hoc spacing.
  • Prefer the existing animated list and chip-list primitives for repeated content.
  • Treat the Home IDE hero as an intentional design-system exception; CV story mode is a bounded exception via UnsafeTypography.

Foundations

AreaPreferred patternPrimary source
ThemeResponsive type scale, palette, rounded surfaces, chip and button defaultssrc/theme/createAppTheme.ts
Appearance presetsBody and heading font families, surface treatments, motion scalingsrc/theme/appAppearance.ts
Shared component stylesCard, panel, chip, tab, list, and CV-specific surface stylingsrc/styles/componentStyleBuilders.ts
Shared page stylesRoute container spacing, backdrop shells, photography, CV, and DataGrid wrapperssrc/styles/appStyleBuilders.ts

Typography

1. Text component — canonical text authoring API

All UI text is authored through the Text component using semantic role, tone, and context props. The typeset builder in src/styles/textStyleBuilders.ts maps role×tone×context to styled output. Direct MUI Typography imports are banned outside src/components/text/** (enforced by ESLint).

Defined in src/components/text/Text.tsx. Shared types in src/types/text.ts.

Common UI roles:

  • Section/page headings: sectionEyebrow, sectionTitle, sectionSubtitle, subsectionTitle, pageTitle, settingsSectionLabel
  • Entry and metadata text: cardTitle, cardSubtitle, meta, metaStrong, body, bodyMuted, caption
  • Metrics and compact labels: metricValue, metricLabel, inlineLabel, label

Semantic text styling lives in src/styles/textStyleBuilders.ts. src/styles/componentStyleBuilders.ts only owns layout, surfaces, and decorative motion helpers that sit around text, such as heading-breathe animation hooks.

Blog uses prose roles (proseParagraph, proseHeading, etc.) via Text — not a separate editorial typography system.

Photography overlays use Text tone="inverse" context="overlay" — not local sx overrides on raw Typography.

Escape hatch: UnsafeTypography in src/components/text/UNSAFE_Typography.tsx requires _unsafe metadata (reason, owner, expiresBy). Used only in explicitly sanctioned exception modules (IDE hero, CV story mode).

Common consumers:

Spacing

1. Page-frame spacing

Most route pages should live inside PageFrame, which provides the standard centered container and responsive route padding.

Defined in src/components/layout/PageFrame.tsx and src/styles/appStyleBuilders.ts.

Common consumers:

2. Stack-driven vertical rhythm

Prefer Stack spacing for vertical composition. Repeated values already cluster around a few stable rhythms.

  • Major section spacing: 2.5 to 3.5
  • Local content grouping: 1 to 2
  • Compact metadata and chip rows: 0.5 to 0.75

Common consumers:

3. Shared padding and gap profiles

Prefer existing shared padding and gap profiles over inventing a new card inset or wrap gap.

Primary sources:

Useful examples:

  • contentCardSx and contentCardInsetSx
  • skillsWrapSx
  • getWrapListSx
  • getDetailListSx

Cards

1. ContentCard

This is the base reusable frosted card surface: rounded corners, gradient-backed panel treatment, border, blur, and shared shadow.

Defined in src/components/ContentCard.tsx.

Common consumers:

2. SectionCard

Use for standard route sections and standalone content blocks that should reveal with the default card entrance animation.

Defined in src/components/layout/SectionCard.tsx via src/components/AnimatedContentCard.tsx.

Common consumers:

3. CVSectionCard

Use for top-level /cv sections. It extends SectionCard with the more decorative CV-specific surface treatment.

Defined in src/components/cv/CVSectionCard.tsx.

Common consumers:

4. SectionPanel

Use for nested dense data inside a larger card. This is the flatter inset panel, not a top-level section surface.

Defined in src/components/layout/SectionPanel.tsx.

Common consumers:

5. Intentional card exceptions

Section Containers

1. BackgroundPaper

Use for full-height scenic backdrops with content alignment and optional shell treatment.

Defined in src/components/BackgroundPaper.tsx.

Primary consumers:

2. PageFrame

Use for standard content routes that should sit inside the site’s shared scenic backdrop and constrained container.

Defined in src/components/layout/PageFrame.tsx.

3. SectionHeading

Use for standard section intros. It composes the canonical Text roles instead of asking each page to build its own overline/title/subtitle stack.

Defined in src/components/layout/SectionHeading.tsx.

Common consumers:

4. CVSectionStack

Use for vertical stacks of top-level CV sections inside each desktop or mobile region.

Defined in src/components/cv/CVSectionStack.tsx.

Primary consumer:

Lists

1. AnimatedContentList

Preferred pattern for viewport-triggered repeated CV content blocks. Supports stack or wrap layout and card, panel, or plain item surfaces.

Defined in src/components/AnimatedContentList.tsx.

Primary consumers:

2. AnimatedSlideList

Primary controlled repeated-item animation primitive for supplemental detail lists inside tabs, drawers, and similar state-driven reveals.

Defined in src/components/AnimatedSlideList.tsx.

Primary consumers:

3. SkillsChipList

Preferred pattern for skill, tool, and opportunity chips.

Defined in src/components/SkillsChipList.tsx.

Primary consumers:

4. Grid and media lists

Data Display

1. TabPanel

Preferred pattern for dense supplemental data that should stay collapsible and keyboard-navigable.

Defined in src/components/TabPanel.tsx.

Primary consumers:

2. Structured data and analytics surfaces

3. Editorial content renderer

Blog content is displayed through typed content blocks, not page-local ad hoc markup.

Primary files:

4. Recovery and utility panels

Motion and Interaction Wrappers

Use these as wrappers around existing surfaces, not as substitutes for them.

Common consumers:

Selection Guide

When adding or changing UI, prefer this order:

Priority order:

  1. Route scaffold: PageFrame or BackgroundPaper
  2. Section intro: SectionHeading
  3. Top-level surface: SectionCard or CVSectionCard
  4. Nested dense surface: SectionPanel
  5. Text: primitives from src/components/text
  6. Repeated items: AnimatedContentList, AnimatedSlideList, SkillsChipList, or an existing grid/list component
  7. Data-heavy disclosure: TabPanel
  8. Motion: wrap the existing surface with a motion primitive only if that pattern already exists nearby

If the UI you want does not fit one of the intentional exceptions above, it should usually be adapted from one of these existing patterns instead of introduced as a new one.

Further reading