Skip to content

@ornie/react · Riverbed theme

A design system where calm is a constraint, not a style.

Ornie is a personal life-OS built for A(u)DHD users, and this is the single source of UI behind it — web, desktop and mobile. The audience sets the rules: muted surfaces, no alarm colours, no streaks, no confetti. Lateness is never red. The component APIs deliberately cannot express the patterns that would make the product shout.

57 components
4 tiers
1 token source of truth
0 dark-mode CSS rules

Non-negotiables

Six rules, enforced rather than documented

Every one of these is a decision in a locked ledger, and most are checked by a script in CI. A guideline nobody can break is an architecture.

Calm

Nothing pulses, bounces, or counts at you

Muted palette, low elevation, no attention-grabbing motion, no red guilt mechanics. There is no streak component and no confetti — not hidden behind a flag, simply absent. Copy names what is: “Inbox is clear”, never an exclamation mark.

Tokens

Semantic tokens only — no hex, ever

Component CSS reads Layer 2 role tokens (--ornie-surface, --ornie-accent). Raw palette ramps need a written justification comment on the line above. An audit script fails the build otherwise.

Theming

Dark mode is free, or the tokens are wrong

data-ornie-theme="dark" on any subtree re-themes everything inside it. Not one component contains a dark-specific rule; if something looks wrong in dark, the fix goes in tokens.json. That discipline is what makes user themes shippable later as pure token-overlay packages.

Naming

Plain daily nouns

Sheet, Dock, Chip, TaskRow — names a non-designer understands. No BottomDrawer2, no FABContainer. If a name needs a glossary, it is the wrong name.

Access

AA contrast on every pair, both themes

Full keyboard operation, a visible focus ring, 44px touch targets, and prefers-reduced-motion collapsing motion in the token layer so no component ever writes that media query itself.

Scope

The system ships parts; the app ships screens

No onboarding flow, no focus-session component. Anything that feels like a screen is split into shells and patterns. A component with only one consumer gets inlined in the app instead of living here.

Architecture

One token file, four targets

tokens.json is the only place a value is authored. Everything else is generated, and CI fails if a generated file drifts from its source.

// tokens.json — Layer 1 primitive
"primary-600": "…",

// Layer 2 semantic role, light / dark
"accent": {
  light: primary-600,
  dark:  primary-400
}

// component CSS only ever reads Layer 2
.ornie-btn--primary {
  background: var(--ornie-accent);
  color: var(--ornie-text-on-accent);
}
tokens.json single source of truth
tokens.css custom properties, light + dark blocks
Tokens.swift iOS widgets & watch faces
Tokens.kt Android surfaces
Tailwind glue app repo, locked to these values

Proof

This page is the dark-mode test

Every colour, space and radius on this landing page is a semantic token — there is no hex value in its stylesheet and no theme selector. Flip the toggle in the header and watch the page re-theme the same way a component does.

The library

Four tiers, 57 components

The tier tells you how much a component is allowed to know. Each one ships a React implementation, hand-written CSS, exact types, a variant grid rendered in both themes, and a written brief covering when not to use it.

Continuous integration

What has to pass before anything merges

  • Tokens in syncRegenerates from tokens.json and fails on any diff.
  • Token disciplineNo hex, no unjustified raw scales, no theme selectors in component CSS.
  • ContrastEvery token pair checked against WCAG AA in light and dark.
  • Accessibilityaxe runs over every component’s both-themes variant grid.
  • TypesStrict TypeScript across the package, no implicit any.
  • SemverChangesets on every user-visible change; breaking changes warn one minor first.