Skip to main content
Case StudiesProjectsAbout
Case StudiesProjectsAbout
Project

Design System Foundation

Accessible component library with built-in dark mode and composable primitives.

2 min read
ReactTypeScriptRadix UI
WCAG 2.1 AA
Compliance
keyboard + screen reader
0 JS
Dark mode
CSS custom properties only
Tree-shaken
Bundle
per-component exports
My Role
Solo: research, design, engineering
Timeline
2024
Team
1 engineer
Platform
Web
Stack
React · TypeScript · Radix UI · Tailwind CSS · Storybook · Vite
Status
Shipped

Overview

A comprehensive design system built on Radix UI primitives, providing accessible, themeable components that work seamlessly in light and dark modes. The system follows compound component patterns for maximum flexibility while maintaining consistency across applications.

Approach

Radix UI for accessibility primitives: keyboard navigation, ARIA attributes, and focus management handled by Radix. No reinventing the wheel for complex widgets (dialogs, dropdowns, tooltips).

Tailwind for styling: utility classes scoped to component internals. Variants are class-map objects, not runtime conditional logic.

CSS custom properties for theming: all colours and spacing tokens are CSS variables that cascade. Dark mode is a data-theme="dark" attribute swap, zero JS required for the switch, no FOUC.

Tree-shakeable exports: each component is a separate export. No import * from 'design-system' importing everything. Bundle impact matches usage.

Component API

compound-component-example.tsx

Compound components allow flexible composition without prop explosion:

// Rather than a single Dialog with 12 props:
<Dialog
  title="Confirm"
  description="Are you sure?"
  primaryLabel="Delete"
  secondaryLabel="Cancel"
  onPrimary={handleDelete}
  onSecondary={handleCancel}
  // ... 6 more props
/>

// Compound components let callers own the structure:
<Dialog>
  <Dialog.Trigger asChild>
    <Button variant="destructive">Delete</Button>
  </Dialog.Trigger>
  <Dialog.Content>
    <Dialog.Title>Confirm deletion</Dialog.Title>
    <Dialog.Description>This cannot be undone.</Dialog.Description>
    <Dialog.Footer>
      <Dialog.Close asChild><Button variant="ghost">Cancel</Button></Dialog.Close>
      <Button variant="destructive" onClick={handleDelete}>Delete</Button>
    </Dialog.Footer>
  </Dialog.Content>
</Dialog>

Challenges

◆Flexibility vs Consistency Tension

The main design challenge: making components flexible enough to cover real use cases without fragmenting into inconsistent one-offs. The solution is sensible defaults overridable at three levels: global theme, component variant, and instance props. Callers override only what differs.

Animation timing across colour modes was a specific edge case. Some animations (e.g. skeleton loaders) need different speeds in dark mode. This is handled via CSS custom properties for --duration-X tokens that can be redefined per theme, no JS involved.

Tech Stack

React · TypeScript · Radix UI · Tailwind CSS · Storybook · Vite

PreviousCaloVoice: Voice-First Calorie Tracker
NextPerformance Monitor

Built with intention, not templates. If you made it this far, thank you for reading thoughtfully.

© 2026 · Designed and built with care