Design system

KuKi

Intelligent health companion for your furbabies β€” a token-driven system for Android, iOS, and the web (PWA), built from the KuKi brand mark and the app's own screens. This page mirrors the Figma library 1:1; component classes/props here are exactly what ships in react/src.

191
primitive tokens
97
semantic tokens (light+dark)
15
text styles
33
icons
120
Button variants
240
Icon Button variants

How this is organized

Three tiers, same as Figma: primitives (raw ramps) β†’ semantic (role-based, light+dark) β†’ components (consume semantic only). Nothing in react/src touches a primitive or a raw hex value directly.

tokens/generate_tokens.py ← single source of truth, asserts WCAG AA on save tokens/css/tokens.css ← generated CSS custom properties (light + dark) react/src/components/*.tsx ← typed components, semantic tokens only react/styles/components.css ← component CSS assets/ai-sparkle-animated.svg ← KuKi's real animated Pet AI icon

Usage guidelines

Do

  • Use semantic tokens (surface/*, text/*) β€” never raw hex or primitives β€” in components.
  • Reserve status colors for symptom-free streaks only.
  • Keep buttons as pills; keep cards at radius/container with smoothing on.

Don't

  • Don't introduce a second typeface β€” Google Sans Flex only, vary by weight.
  • Don't use pure black for high-emphasis fills β€” use chocolate ink.
  • Don't invent new status hues β€” extend the existing five-state model.
Foundations

Color

Derived from the KuKi mark and login screen: terracotta replaces black as the primary action color, golden cream is the accent, and chocolate ink stands in for black on high-emphasis surfaces. Status colors carry specific meaning tied to symptom-free streaks.

Wellness status meaning

The core semantic mapping behind every Wellness Card.

Primitive ramps

50–950 step scales. Semantic tokens alias these; components never reference them directly.

Semantic surfaces (current theme)

background: var(--kuki-sem-surface-brand); color: var(--kuki-sem-text-on-brand);
Foundations

Typography

Google Sans Flex, one family across weights ExtraLight–Bold. No secondary typeface, no code font β€” this is a pet health app, not a code editor. This section is theme-aware; toggle dark mode to see both.

.kuki-wellness__value { font-size: var(--kuki-font-size-6xl); font-weight: var(--kuki-font-weight-extralight); } .kuki-metric-card__value { font-size: var(--kuki-font-size-xl); font-weight: var(--kuki-font-weight-semibold); }
Foundations

Iconography

Material Symbols (Outlined), matching the icon language already used across the app screens.

Components

Buttons

One unified Button component β€” the old separate IconButton is now just iconOnly. 5 appearances Γ— 2 tones Γ— 6 states (incl. loading) Γ— 3 sizes Γ— icon-only Γ— selected = 720 variants in Figma. A representative set below; full matrix on the Button page.

Appearance

<Button appearance="primary" size="lg">Start tracking</Button> <Button appearance="secondary">View as Guest</Button> <Button appearance="ghost">Skip</Button> <Button appearance="link">Learn more</Button> <Button appearance="emphasis">Get started</Button>

Icon slots

4 independent slots: outer leadingIcon/trailingIcon sit at the button's own edge padding; inner leadingAdornment/trailingAdornment hug the label inside their own content group, at a gap that's independent of the outer icons.

<Button appearance="emphasis" size="lg" leadingIcon={<HomeIcon/>} leadingAdornment={<CheckIcon/>} trailingAdornment={<CheckIcon/>} trailingIcon={<HomeIcon/>}> Content </Button>

Icon scaling by size

Icons are bound to distinct size tokens per button size (12 / 16 / 20px labeled, 16 / 20 / 24px icon-only) β€” not a single fixed size stretched to fit.

Tone, disabled & loading

loading is never an overlay on the normal appearance β€” it forces the same blocked look as disabled, with a spinner as the leading adornment next to editable text.

<Button appearance="primary" tone="destructive">Log seizure</Button> <Button appearance="primary" loading>Please wait</Button> <Button appearance="primary" disabled>Disabled</Button>

Icon-only (iconOnly)

Same appearance/tone/state/loading system as labeled buttons β€” just iconOnly forcing a fixed circular shape instead of a hug-padded pill. This replaces the old separate IconButton.

<Button appearance="emphasis" size="lg" iconOnly label="Add" icon={<AddIcon/>} /> <Button appearance="ghost" iconOnly selected label="Favourite" icon={<HeartIcon/>} />
Components

Badges & Chips

Badges are tight rectangular tags (sex, spayed/neutered, due/overdue status). Chips are pill-shaped with an optional icon, used on card headers.

Badge

Female Spayed Male Overdue Up to date Due soon
<Badge tone="pink" emphasis="bold">Female</Badge> <Badge tone="critical" emphasis="subtle">Overdue</Badge>

Chip

${'{{timer}}'} Seizure Monitor ${'{{scale}}'} Weight ${'{{syringe}}'} Next Vaccine ${'{{run}}'} Activity ${'{{restaurant}}'} Nutrition ${'{{bedtime}}'} Sleep ${'{{pill}}'} Medication
<Chip tone="turquoise" icon={<ScaleIcon />}>Weight</Chip>
Components

Cards

Every card corner uses the squircle utility (preview/squircle.js) β€” a true superellipse clip-path approximating Figma's native corner smoothing, since CSS border-radius alone has no smoothing control.

Wellness Card β€” the five states

Gradient is centered and symmetric; the pastel edge color reaches all four corners evenly.

Seizure Streak
–
No seizures have been recorded
Seizure Streak
0 days
2 grand mal seizures today
Seizure Streak
2 days
1 focal seizure this week
Seizure Streak
5 days
No seizures in the last 5 days
Seizure Streak
14 days
No seizures in the last 14 days
<WellnessCard status="critical" title="Seizure Streak" value="0 days" subtitle="2 grand mal seizures today" />

Metric Cards

Weight
2.8 kg
Last weighed today
Next Vaccine
12 Days
DHPP Booster
Sleep
No logs
Log today's sleep
Medication
12:00 PM
3 medicines
<MetricCard metric="sleep" label="Sleep" value="No logs" sub="Log today's sleep" empty />

Card with list rows

Health Summary
Leviteracetam 1.8mL
8 AM, 4 PM, 12 AM
3x Daily
Combined
8 Jul '26
Overdue by 3 days
<SectionHeader title="Health Summary" action="Edit" /> <ListRow title="Combined" value="8 Jul '26" valueSub="Overdue by 3 days" valueSubTone="critical" />
Components

Navigation

Segmented top nav (5 tabs, one expanded) plus a bottom bar with pet switcher, add FAB, and Pet AI entry point.

Top Nav

<TopNav tabs={tabs} active="home" onTabChange={setActive} /> /* real :hover / :focus-visible / :active states β€” try tabbing to the nav above */

Nav Tab β€” interaction states

Documented explicitly (Figma has a matching "Nav Tab" component with the same four states).

.kuki-topnav__tab:hover, :focus-visible, :active { /* see components.css */ }

Bottom Bar

<BottomBar petName="Kingini" onSwitchPet={...} onAdd={...} onAI={...} />
Components

Pet AI icon

The swirling background is a CSS approximation of a shader β€” a blurred, rotating conic gradient screen-blended over a radial base (no WebGL). The sparkle burst on top is KuKi's real animated asset, inlined at runtime with uniquified ids (preview/ai-orb.js) so its CSS keyframes actually run β€” browsers freeze animated SVGs loaded via plain <img> at their first frame β€” and so multiple orbs on one page never collide.

Three sparkles fire in sequence on a 1.9s loop, per the source animation's own embedded @keyframes. Respects prefers-reduced-motion for the shader background.
<AIOrb sparkleSrc="/assets/ai-sparkle-animated.svg" label="Pet AI" onClick={openAssistant} />