Skip to content

UI Component Library

Design system

shadcn/ui, configured via components.json:

{ "style": "default", "rsc": false, "tsx": true, "tailwind": { ... }, "aliases": { ... } }

shadcn isn't a library you install — it's a CLI that copies component source into your repo, so each component is editable. The 50 primitives under src/components/ui/ are all generated this way, then customised in place.

CSS approach

  • Tailwind 3 as the primary styling layer
  • Design tokens via CSS variables (HSL palette in src/index.css), wired into tailwind.config.ts as colors: { background: "hsl(var(--background))", ... }
  • class-variance-authority (cva) + tailwind-merge (cn(...)) for variant-based component APIs (e.g., <Button variant="destructive">)
  • tailwindcss-animate for keyframe animations
  • Two .css files outside Tailwind: App.css (largely empty), pages/Accounts.css (page-specific overrides)
  • Dark mode: darkMode: ["class"] configured; no theme toggle in the UI at audit time

Font

fontFamily: { sans: ["SF Pro Display", "-apple-system", "BlinkMacSystemFont", "system-ui", "sans-serif"] }

SF Pro Display is Apple's system font. It is not bundled — there's no @font-face rule in the repo. The browser uses it on macOS / iOS only; on other systems it falls back to the next entries.

Component inventory

Primitives (src/components/ui/ — 50 files)

accordion       alert-dialog    alert           aspect-ratio    avatar
badge           breadcrumb      button          calendar        card
carousel        chart           checkbox        collapsible     command
context-menu    custom-button   dialog          drawer          dropdown-menu
form            hover-card      input-otp       input           label
menubar         navigation-menu pagination      popover         progress
radio-group     resizable       scroll-area     select          separator
sheet           sidebar         skeleton        slider          sonner
switch          table           tabs            textarea        toaster
toast           toggle-group    toggle          tooltip         use-toast

custom-button.tsx is the only non-default — a project-specific extension on top of shadcn's button.

App components

src/components/
├── auth/
│   ├── LoginForm.tsx
│   └── ProtectedRoute.tsx
├── layout/
│   ├── AppLayout.tsx        # sidebar + main area shell
│   └── Sidebar.tsx          # nav + logout + account switcher (uses lucide icons, shadcn Popover/Tooltip)
└── shared/                  # small reusable bits

This is a small, healthy component tree. No prop-drilling-from-hell observed in spot-checks.

Forms

  • react-hook-form for form state
  • @hookform/resolvers + zod for validation
  • shadcn <Form> (which composes FormProvider + FormField + FormLabel + FormMessage) as the canonical pattern

Adoption is partial at audit time — verify whether every form (Login, profile edit, account create/edit, etc.) goes through react-hook-form + zod. Some pages may still use raw useState + manual validation.

Theming

  • Light theme defined via --background, --foreground, --primary, etc. CSS vars in src/index.css
  • Dark theme class support is wired up (darkMode: ["class"] in Tailwind config) but no theme switcher exists in the UI
  • next-themes is in package.json (^0.3.0) but not yet mounted anywhere I can see at audit time — declared, not used

Storybook / sandbox

None. There is no isolated component-development environment. Engineers iterate on components by mounting them in a page and refreshing.

Patterns to follow

When adding a new UI component:

  1. Prefer extending an existing shadcn primitive over writing from scratch (custom-button.tsx is the pattern)
  2. Variants via cva rather than per-className props
  3. Always wrap with cn(...) for className composition
  4. Use lucide icons (already imported throughout) rather than adding a new icon library
  5. For new forms, use react-hook-form + zod + shadcn Form — don't roll your own

Recommendations

  1. Add a theme toggle (or remove next-themes if dark mode isn't planned)
  2. Self-host SF Pro Display OR change the default to something predictable on non-Apple platforms (e.g., Inter)
  3. Set up Storybook when the component count exceeds ~75 (currently ~55 — getting close)
  4. Audit form consistency — pick one pattern (react-hook-form + zod) and apply universally
  5. Reconsider Accounts.css — page-specific stylesheets defeat Tailwind's atomic approach. Inline the styles as Tailwind classes if possible.