A custom design system for a cardiac health app — built from scratch to unify a product that had outgrown itself.
Kardia by AliveCor is a consumer cardiac health app — patients record ECGs, share results with a cardiologist, and track heart rhythm over time. The interface has to feel calm for an anxious patient, while staying precise enough for a clinical read.
A design system for a cardiac health app has to earn trust twice: once with the patient who needs to feel calm, and again with the clinician who needs the data to be unambiguous.
Team
Scope
The Kardia Consumer App grew screen by screen, with no shared component library. It worked — patients used it, cardiologists relied on it — but the debt was real and it was accumulating.
An audit that catalogued every workflow into one file made the inconsistency impossible to ignore — six typefaces in active use across 15 workflows, none of them cross-platform, and six header patterns, none sharing a component or token.
The onboarding tutorial popup showed the same debt from a different angle — eight variants of the same component turned up side by side in the file, each one solving step navigation on its own.
Half use a chevron-and-slider pattern with a live step counter; the other half collapse it to a single button still labeled TEXT — a placeholder that was never filled in. Whether the close affordance or the illustration block shows up depends on which copy of the component someone happened to duplicate.
Variants found in the file: 8 Navigation patterns: 2 — chevron+slider vs single button Button label reading literal "TEXT": 4 of 8 Close (X) affordance: present on 4 of 8, missing on the rest
Off-the-shelf kits don't fit cardiac health — color semantics can't conflict with accessibility rules for error and warning states, and the tone has to read as both calm and precise.
Foundations first. You cannot build stable components on top of an unstable token system.
What shipped
Sprint timeline
The old system used loosely defined semantic tokens named after moods — Mindful Blue — with no primitive scale underneath. The new system starts from a structured 0–900 primitive ramp with AA contrast ratios at every stop, before any semantic meaning is assigned.
Before / after — the color system
Before — old DS
Semantic tokens named after colors. No primitive scale. No contrast ratios documented.
After — Kardia DS
0–900 primitive scale. AA contrast ratios at every stop. Semantic tokens reference primitives.
Before — premium theme
Flat semantic tokens. Premium Purple #885FA5 with opacity variants. No scale.
After — premium palette
Full 0–900 primitive scale. Premium features draw from the same token architecture as brand.
Primitive → semantic → component
brand/600
#113A69
Raw value. Never used directly in components.
color.primary.bg
→ brand/600
Assigns meaning to a primitive. Component-agnostic.
button.primary.bg
→ color.primary.bg
Wires semantic to a specific component context.
Brand — primitive scale
#f8fbfe
#f2f7fd
#b9d4f3
#478fe1
#206dc5
#174e8c
#113a69
#0c2746
#061323
#030a12
Premium — primitive scale
#f8f4fa
#ebe4f3
#d9c7e6
#c2a0d9
#a77bca
#885fa5
#6b4983
#573d6a
#3e2550
#1b1023
Designers reached for Apple system fonts because Figma bundles them and they preview well on a Mac — but SF Pro doesn't exist on Android or Web. Engineers improvised, and the product looked different on every platform.
Type ramp — DM Sans, cross-platform
Display · 40 / 700
Track your heart health, every day.
H1 · 28 / 700
Recent Recordings
H2 · 22 / 600
Account Settings
H3 · 18 / 600
Notification Preferences
Body · 16 / 400
Your ECG results will be reviewed by a certified cardiologist within 24 hours.
Label · 13 / 500
NORMAL SINUS RHYTHM
Caption · 12 / 400
Recorded 14 Jul 2025 · 9:41 AM
Buttons came first because they carry the highest semantic weight in the product. The system defines four roles — Brand, Destructive, Success, Neutral — each with three hierarchy levels and five documented states.
After — Kardia DS, full specification
Every role across solid, tinted, outline, and disabled states.
Before — Action_Button, pre-DS
Hand-recolored per screen, no semantic naming behind the fills.
Do
Don't
Before the DS, the same color was defined three times — a Figma fill, a Swift constant, an Android XML value — and they drifted apart with no shared reference.
color.brand.600#113A69Primary CTA backgroundcolor.error.600#cc3d3fDestructive action, error statecolor.neutral.900#182c3dBody text, headingscolor.neutral.300#c6ccd2Borders, dividerscolor.premium.600#6B4983Premium feature surfacesradius.sm4pxButton, input, small cardradius.md8pxModal, sheet, overlayshadow.elevation.10 1px 4px rgba(0,0,0,.10)Card lift, floating elementfont.heading1DM Sans / 700 / 28pxPage title, screen headerfont.bodyDM Sans / 400 / 16pxBody copy, descriptionsSwiftUI · iOS
extension Color {
static let brandPrimary =
Color(hex: "#113A69")
// color.brand.600
static let destructive =
Color(hex: "#cc3d3f")
// color.error.600
static let premium =
Color(hex: "#6B4983")
// color.premium.600
}Android · Compose
val KardiaColors =
lightColorScheme(
primary =
Color(0xFF113A69),
// color.brand.600
error =
Color(0xFFCC3D3F),
// color.error.600
tertiary =
Color(0xFF6B4983),
// color.premium.600
)React · CSS Tokens
:root {
--color-brand-600: #113a69;
--color-error-600: #cc3d3f;
--color-neutral-900: #182c3d;
--color-premium-600: #6b4983;
--radius-sm: 4px;
--font-body: 'DM Sans', sans-serif;
}The sprint rule was strict: no component work until color, type, and spacing were locked. Starting components before the token system is stable means either building twice or shipping components that reference values that change underneath them.
Phase 3 is ongoing. The system is built; adoption — engineering token implementation, app redesign rollout, contribution model — is where the ROI materializes.
Weeks 1–2
Weeks 3–4
Ongoing
What the system enables, by function.
What this sprint taught me about building a design system inside an org.
Bring more stakeholders in from the beginning so everyone is on the same page before any decisions are made.
Marketing has to be involved from the start — brand color and voice decisions made without them create re-work later.
Engineering brings essential context for decision-making. Token naming, platform constraints, and feasibility calls are better made together, not handed over.
Have clear, agreed-upon goals from Business, Product, and Engineering before the system work begins. A design system without a shared north star serves no one.
Chase the north star. The system is infrastructure, not a deliverable. Keep the long-term product goal visible so every foundation and component decision points toward it.