Doro Mind · Design system · v0.1

A calm, warm, deeply-accessible product surface.

Four independently-versioned packages under core/design/ — tokens, icons, primitives, and this documentation site — shared by member-app, workbench, and every future Doro surface.

Introduction

The system is lifted directly from member-app/app/globals.css so there is zero drift between the runtime theme and design review. Tokens are HSL CSS custom properties; components are shadcn primitives wrapped in Doro-specific behavior. Everything flows through a single Tailwind preset.

Principles

  • Calm over clever. Warm cream grounds, one accent, breathing room.
  • Accessible by default. WCAG AA on every token pair; focus visible everywhere.
  • Shadcn-native. Upgradeable primitives; Doro customizations live in wrappers.
  • One source of truth. Tokens live in one file, consumed by app & docs alike.

Versioning

Each package in core/design/* is versioned independently through release-please, identical to every other package under core/. Consumers link via file: dependencies — same pattern as @doro/core-data and @doro/core-lib.

Packages

Four sibling packages. Dependency order flows left to right: tokens → icons → ui → docs.

@doro/design-tokens
Foundations
HSL CSS variables, Tailwind preset, typed TS exports. No internal dependencies.
Library · not deployed
@doro/design-icons
Iconography
Tree-shakeable React SVG icons. Themed via currentColor. Depends on tokens.
Library · not deployed
@doro/design-ui
Primitives
shadcn-compatible component library plus Doro wrappers. Storybook included.
Storybook → Cloud Run
@doro/design-docs
This site
Static documentation surface. References all three sibling packages at runtime.
HTML → Cloud Run

Color

All surfaces are driven by HSL CSS custom properties declared in @doro/design-tokens/tokens.css. Toggle light/dark in the sidebar — every swatch below reflects the current theme.

Semantic tokens

Chart palette

Ordered categorical colors for data viz. Avoid using --primary for decorative non-interactive chart elements.

Typography

Fraunces for editorial display. Inter Tight for interface. JetBrains Mono for code. Fluid size ramp, never smaller than 13px in product UI.

DisplayFraunces · 44/1.05 · 600
A warm, honest surface
Heading 1Fraunces · 30/1.15 · 600
This week's check-in
Heading 2Inter Tight · 20/1.3 · 600
Your coach has a new question
BodyInter Tight · 15/1.55 · 400
How did sleep feel over the past three nights? Your answers help your care team tune the protocol we started last week.
SmallInter Tight · 13/1.5 · 400
Updated 3 minutes ago by Dr. Okafor
LabelInter Tight · 11/1 · 600 · uppercase
Member dashboard
CodeJetBrains Mono · 13/1.5 · 400
hsl(var(--primary))

Spacing & radius

4px base grid, Tailwind-default. Radius tokens flow from --radius (0.75rem) — bump that one variable to reshape the system.

Spacing scale

14px
28px
312px
416px
624px
832px
1248px
1664px
2496px

Radius

sm
0.5rem
md
0.625rem
lg
0.75rem

Safe-area spacing

Device-safe insets exposed as Tailwind utilities — pt-safe-top, pb-safe-bottom, etc. Critical for member-app running on notched devices.

Motion

Short, purposeful motion. Duration under 300ms unless there's narrative reason. All motion is auto-disabled by prefers-reduced-motion.

TokenDurationEasingUse
motion.instant80msease-outHover color shifts, focus
motion.snap140msease-outButton states, tab indicator
motion.swift200mscubic-bezier(.33,1,.68,1)Card & popover reveal
motion.settle300mscubic-bezier(.33,1,.68,1)Sheet, dialog
accordion-down/up200msease-outDisclosure
bottom-nav-pulse1350msease-outMobile tab attention peak (once per session)

Buttons

Primary, secondary, outline, ghost, destructive, link. Size ramp: sm, default, lg, icon.

Forms

Input, Textarea, Label, Checkbox, Radio, Switch. All bound to --ring for focus outline — WCAG AA against the warm background.

Check-in cadence

Feedback

Alerts, badges, progress. Low-noise states — we don't surface "success" toasts for everyday writes.

New coaching guide
Your care team shared a sleep protocol for this week.
Check-in overdue
You haven't answered this week's check-in yet.
Connection lost
Retrying in 10 seconds.

Badges

New Beta Draft Overdue Removed

Progress

Week 3 of 838%

Data display

Cards, avatars, simple tables. The dashboard layout in member-app is composed entirely from these primitives.

This week
Check-in · sleep protocol
3 / 7
DaySleep scoreNotesStatus
Mon82Woke at 06:40, no midnight wakingsLogged
Tue71Late mealLogged
Wed88Felt restedLogged
ThuNot yet submittedPending
FriPending

shadcn primitives

The full roster exported from @doro/design-ui/components/ui. Each has a matching Storybook story; tokens flow through the same HSL variables.

Overlays

  • Dialog · modal + form confirmations
  • Sheet · side drawer (nav, filters)
  • Popover · lightweight inline panels
  • Tooltip · hover hints only
  • DropdownMenu · actions on a trigger
  • Command · ⌘K palette

Form

  • Input · Textarea · Label
  • Select (Radix, keyboard-full)
  • Checkbox · RadioGroup · Switch
  • Slider · Calendar + DoroDatePicker

Structure

  • Card · Separator · ScrollArea
  • Tabs · Accordion
  • Table primitives
  • Breadcrumb · Pagination

Status

  • Alert · Badge · Progress
  • Toast + Toaster runtime
  • Skeleton loading
  • Avatar with fallback

Accordion

What's included in a check-in?
Three prompts tuned to your current protocol, plus optional metrics. Takes under two minutes.
Can I change cadence? +
Who can see my responses? +

Command palette

Jump to anything…
⌘K
Pages
Dashboard
This week's check-in
Message care team
Settings
Toggle theme

Calendar & Date picker

April 2026
S
M
T
W
T
F
S
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1
2

Built on react-day-picker. The DoroDatePicker wrapper composes Popover + Calendar for the common single-date case.

import {DoroDatePicker} from '@doro/design-ui';

<DoroDatePicker
  value={date}
  onChange={setDate}
  placeholder="Appointment date"
/>

Breadcrumb & Pagination

Home / Protocols / Sleep · Week 3

Sheet (side drawer)

Filters
Narrow the protocol list
Nav, filters, and long-form forms drop into Sheet on mobile.

Slider & Separator

Energy today7 / 10

Use Separator between clusters of content, not as a decorative line inside cards.

Adding more primitives

cd core/design/ui
npx shadcn@latest add <primitive>        # writes components/ui/<name>.tsx

# Then:
# 1. add to components/ui/index.ts
# 2. write a story under stories/
# 3. bump package.json deps if the primitive pulls new Radix packages

Monorepo structure

The design system lives alongside other core/* packages. Apps are siblings of core/, not nested.

# Workspace root doro/ ├── member-app/ # Next.js — consumer ├── … # additional apps (workbench, etc.) └── core/ ├── core-data/ # @doro/core-data ├── core-lib/ # @doro/core-lib ├── ui/ # existing shared ui └── design/ ├── tokens/ # @doro/design-tokens ├── icons/ # @doro/design-icons ├── ui/ # @doro/design-ui ← Storybook here └── docs/ # @doro/design-docs (this site)

Consuming packages

Three lines of wiring — package.json, next.config.mjs, tailwind.config.ts.

1. Link as file: dependencies

// member-app/package.json
{
  "dependencies": {
    "@doro/design-tokens": "file:../core/design/tokens",
    "@doro/design-icons":  "file:../core/design/icons",
    "@doro/design-ui":     "file:../core/design/ui"
  }
}

2. Transpile in Next

// member-app/next.config.mjs
export default {
  transpilePackages: [
    '@doro/design-tokens',
    '@doro/design-icons',
    '@doro/design-ui',
  ],
};

3. Extend Tailwind with the preset

// member-app/tailwind.config.ts
import designPreset from '@doro/design-tokens/tailwind-preset';

export default {
  presets: [designPreset],
  content: [
    './app/**/*.{ts,tsx}',
    '../core/design/ui/src/**/*.{ts,tsx}',
  ],
};

4. Import the base CSS once

// member-app/app/layout.tsx
import '@doro/design-tokens/tokens.css';
import '@doro/design-tokens/styles.css';

5. Use primitives

import {Button, Card, CardTitle} from '@doro/design-ui';
import {IconCheck} from '@doro/design-icons';

export function Confirm() {
  return (
    <Card>
      <CardTitle>All set</CardTitle>
      <Button><IconCheck /> Done</Button>
    </Card>
  );
}

shadcn upgrade strategy

shadcn components live inside @doro/design-ui, not vendored into every app. The rules below keep them upgradeable.

Where files live

# Inside core/design/ui/src/components ui/ # shadcn primitives — DO NOT edit. # Regenerated via `npx shadcn add …`. doro-button.tsx # Your wrappers around ui/button.tsx. # Safe to edit — survives upgrades.

Add a primitive

cd core/design/ui
npx shadcn@latest add dropdown-menu

Then export from components/ui/index.ts and add a Storybook story.

Customize without forking

// components/doro-button.tsx
import {Button, type ButtonProps} from './ui/button.js';

export function DoroButton(props: ButtonProps) {
  return <Button className="doro-warm-shadow" {...props} />;
}

When shadcn updates

Re-run npx shadcn@latest add <component>. The primitive file is replaced; your doro-* wrapper stays intact. Review the diff, bump the design-ui package version, and merge.

Release flow

Independent versioning via release-please. Identical to every other core/* package.

  1. Commit with conventional-commit messages targeting a design package: feat(design-ui): add DropdownMenu primitive
  2. release-please opens a PR bumping that package's version and updating its CHANGELOG.md.
  3. Merging tags a release — design-ui-v0.3.0, design-tokens-v0.2.1, etc.
  4. Cloud Build triggers on the tag pattern and deploys the package's artifact (Storybook for ui, static site for docs). Token and icon releases are library-only — no deploy.
  5. Consuming apps update their file: link and re-run npm install. Build-gating in the app's CI catches breaking API drift before merge.

Accessibility

WCAG 2.2 AA is the floor, not the ceiling. Every token pair below is computed live from @doro/design-tokens. Pass / fail is marked against WCAG AA for normal text (4.5:1).

Key contrast pairs (computed live)

PairLightDarkRequired

Known issue · primary button contrast

White text on the current warm primary (hsl(32 65% 48%)) measures 3.17:1 in light and 2.74:1 in dark — below the 4.5 AA threshold for normal text. It passes the 3.0 bar for large text (18pt+ or 14pt bold), so headline-scale usage is compliant, but default 14px button labels are not. Three options on the table, ranked by disruption:

  1. Darken primary to hsl(28 70% 36%) (≈4.7:1 with white). Preserves warmth; shifts slightly redder.
  2. Swap foreground to near-blackhsl(28 40% 12%) on primary reads ≈8.2:1. Breaks the "colored button = white text" convention.
  3. Restrict primary to large text only; use --secondary for default-size CTAs. Smallest visual change; requires lint rule in design-ui.

Pending design decision. Ticket: DORO-TBD.

Rules we enforce

  • Focus ring uses --ring with outline-offset; never outline: none without a replacement.
  • All interactive elements reach 44px hit targets on mobile.
  • Motion longer than 300ms is gated behind prefers-reduced-motion: no-preference.
  • Labels are associated with controls via htmlFor, never proximity-only.
  • Icon-only buttons carry aria-label; Storybook has an a11y addon that fails stories missing one.

Contributing

See core/design/CONTRIBUTING.md for the long version. The short version:

  1. Does the change need new tokens? If yes, PR tokens/ first.
  2. Is this an unmodified shadcn primitive? Use the CLI — don't hand-author.
  3. Is this a Doro-specific tweak to a shadcn primitive? Wrap it in a doro-* component.
  4. Every new primitive or wrapper ships with a Storybook story and, where it affects composition, a section here in design-docs.
  5. Run task verify from core/design/ before opening the PR.