TaperSafe Design System

A code-truthful spec for a native iOS medication taper tracker. The palette is "Clinical Cyan" — a Material 3 inspired bright teal on cyan-washed white surfaces. Every token here maps to a real value in lib/constants.ts; when code and this doc disagree, code wins.

1 primary accent 6 surface stops full pill radius v1.0
01 · Identity

Calm before clever.

TaperSafe is opened in anxious moments. The design is built around one question: "how long until my next dose?" Three principles hold the whole thing together.

The countdown is the brand.56pt tabular numerics — the single largest element on every patient screen. Nothing competes.
Everything is a pill.border-radius: 9999px on every button, chip, badge, and input. Deliberate softness for an anxious context.
Colour means something.Teal = action. Green = success. Red = overdue. Never decorative — always clinical meaning.
02 · Color

Bright teal primary. Washed-white surfaces. Distinct success green.

The palette is Material 3 inspired. One cyan-teal primary. One fresh green for success states. A six-stop surface scale that gets slightly darker as elements nest deeper. No grey — everything has a cyan tint.

Primary

primary
#1A9FB5
Buttons, focus rings, brand accent
primary-dim
#1589A0
Gradient end-stop, pressed state
primary-container
#B8F5FF
Selected chips, soft teal wash

Success & semantic

success
#2DB87A
Dose taken, step complete
success-container
#C2F5E0
Confirmed dose card bg
danger
#BA1A1A
Overdue countdown, errors
error-container
#FFDAD6
OVERDUE badge background
tertiary
#B8F04A
Lime — soft attention
warning
#F59E0B
Dose window open, caution

Surfaces — six-stop cyan-washed scale

surface-lowest
#FFFFFF
Cards, sheet bodies
background
#F0FAFB
Screen background
surface-low
#E6F6F8
Inputs, secondary buttons
surface-container
#DAF0F3
Progress bar tracks
surface-high
#CCE9ED
Disabled state, dividers
surface-highest
#BDE3E8
Section breaks

Text

on-surface
#001F24
Primary text, countdown digits
on-surface-variant
#3F575B
Secondary labels, body copy
text-muted
#6F797A
Captions, disabled labels
03 · Typography

System font. Wide scale. One element dominates.

The system font (-apple-system on iOS, Roboto on Android) keeps text legible and familiar. The scale is intentionally wide so the 56pt countdown can dominate without competing with body copy.

System 800 · TabularCountdown · 56pt · letter-spacing −3px
14:32:07

The only place we use font-variant-numeric: tabular-nums — fixed-width digits prevent layout shift every second.

DM Sans 800Screen titles · 64pt
Your plan
DM Sans 700Card headers · titles · 24pt
Today's dose: 10 mg
DM Sans 500Body · helper text · 16pt
Your next dose is scheduled for 8:00 AM tomorrow. If you miss a dose, do not double up — follow your provider's instructions.
JetBrains Mono 700All-caps badges · eyebrows · 10–11pt
OVERDUE · STEP 2 OF 4 · DAY 14 · TAKEN
04 · Spacing

A 4px base scale.

Six steps, doubling loosely from a 4px hairline to 48px for large-screen rhythm.

xs
4px
sm
8px
md
16px
lg
24px
xl
32px
xxl
48px
05 · Radius

Generous rounds. Everything interactive is a pill.

Sharp corners feel cold in a healthcare context. Five radius values; the full-pill (9999px) is non-negotiable for all interactive elements.

Pill All buttons, chips, badges, inputs
full · 9999px
Card XL Hero cards, bottom sheets
xl · 32px
Card Standard content cards
lg · 24px
Input Text inputs
md · 16px
Badge Progress fill, small badges
sm · 12px
06 · Elevation

One soft shadow. Used sparingly.

One real shadow for cards. A deeper shadow for bottom sheets. No layered glows, no inner shadows — one shadow per card, maximum. All shadows use a near-black teal alpha channel, not grey.

sh-sm
card
sheet
glow-teal
07 · Components

All built from the same teal + washed-white recipe.

Core components in components/ui/. Rendered on the clinical teal stage:

Buttons

Countdown timer

Next dose in
08:14:33
OVERDUE
00:00:00

Progress bar

Step 2 of 460% complete

Status chips

Taken Pending Overdue
08 · Motion

Signal, not decoration. Never startle.

This is a calm app. Motion must not alarm an already anxious user. With prefers-reduced-motion on, all animated demos collapse to a plain opacity fade.

Dose reminder Pill drops in from top, soft landing bounce
14:32
Countdown tick Digits update each second; scale 1→1.08→1
View plan
Screen push Scale 0.96→1, fade in, 300ms iOS default
Tap feedback activeOpacity 0.85 primary, 0.7 others — no scale change
09 · Voice & tone

Calm, plain, never urgent.

The copy speaks to someone who is managing a health condition. It is direct, reassuring, and never alarmist. It does not celebrate adherence in a way that would make missing a dose feel like failure.

We say

"Next dose in 8 hours."

"Dose confirmed. Well done."

"Your plan: step 2 of 4."

We don't say

"You missed a dose! Get back on track!"

"🔥 5-day streak — don't break it!"

"URGENT: Take your medication NOW"

10 · Doing it wrong

Five ways to break the feel.

If a new screen does any of these, it is no longer TaperSafe.

  • Don't shrink the countdown. The 56pt timer is always the largest element on the patient home screen. Nothing competes with it.
  • Don't use square corners on interactive elements. Radius full (9999px) on every button, chip, badge, and input is non-negotiable.
  • Don't use a saturated CTA colour for decoration. Orange, magenta, or any hue outside the system carries accidental clinical meaning. Teal for actions; green for success; red for overdue only.
  • Don't add more than one shadow per card. No layered glows, no inner shadows. One soft drop shadow per elevated element, in near-black teal alpha.
  • Don't alarm the user with motion on the overdue state. The colour switches instantly and silently. No shake, no flash, no haptic. The change must register — not startle.
11 · Source of truth

Where every token actually lives.

This page documents the system; the code defines it. When they disagree, the code wins.

Tokenslib/constants.ts
Componentscomponents/ui/
Patient flowcomponents/patient/
App iconassets/icon.png
Live design spectapersafe-design.vercel.app → App StoreTaperSafe / Serene Taper →