Skip to content

DNA — the helix-ui theme engine

DNA — Design Nucleotide Allele. It’s how helix-ui names its theme system.

A DNA is a complete theme genome — 23 genes, one allele each. You can clone DNAs, mutate them, breed two parents into a child, or run generations of breeding over a population. Dominant alleles win in crossover; recessive alleles still surface occasionally. That’s where surprise looks come from across generations.

Live demo: /showcase/dna-lab — every component on the page renders under a DNA you can mutate live.

Genes (23)

GeneTraitSample alleles
accentBrand hue (drives the OKLCH 50–900 ramp)blue, violet, crimson, system-blue, fluent-blue…
chromaSaturation multiplier across the brand rampmuted, standard, vibrant
lightnessLight / dark / autolight, dark, auto
neutralTiltWhich way the greys leantrue, cool, graphite, warm, brown, steel
successHueHue of the success rampemerald, lime, teal, mint
warningHueHue of the warning rampamber, orange, yellow, gold
dangerHueHue of the danger rampred, crimson, rose, pink
infoHueHue of the info rampsky, cyan, blue, indigo
contrastForeground / background separationsubtle, normal, high
typographyBody font family + size scalesans, system, grotesk, rounded, serif, mono
headingFamilyFont for headings, separate from bodysans, system, grotesk, serif, display, mono
monoFamilyFont for code surfacesmono, rounded, serif
typeScaleHeading-to-body ratiominor, standard, major, fourth, golden
lineHeightBody + heading line-heighttight, cozy, loose
trackingLetter-spacing, by size bandnormal, tight, flat, airy
radiusCorner-radius scale factorsharp, crisp, subtle, soft, standard, rounded…
radiusCurveUniform vs. progressive radius scalinguniform, swell, gentle, progressive, dramatic
borderWidthStroke weightshairline, thin, normal, thick
surfaceDepth treatmentflat, elevated, glassy, vibrancy, tonal, acrylic
densitySpacing scale factorcompact, comfortable, spacious
paddingScaleComponent padding + control heightdense, tight, normal, generous
motionAnimation duration scalesubtle, standard, lively
easingMotion curve identitystandard, smooth, snappy, ios, material3, fluent

Each allele has a dominance score (1–10) and an optional recessive flag. The full registry lives in @helix-ui/dna/src/alleles.ts.

Genotype and environment

A genome is not the whole picture. The same genotype expressed in a different environment produces a different phenotype, and a design system has exactly one environment that matters: the operating system it is being looked at on.

That is DNA.platform, and it is deliberately not a gene — it is not inherited, because breeding an iOS theme with a Windows one should not produce something half-native to each.

type PlatformId = 'auto' | 'web' | 'ios' | 'macos' | 'android' | 'windows';

It carries the decisions each platform treats as a rule rather than a preference — the ones no amount of gene-tuning can express:

PlatformFocus ringHit targetDark surfaceMotion
ios3px @ 144ptnear-black (OLED)×1.15
android3px @ 248dpdark, tinted×1.10
macos3px @ 128pxmid-dark×0.90
windows2px @ 1, double stroke32pxmid-dark×0.80
web2px @ 224pxramp default×1.00

'auto' is the default, including on the wildtype: it resolves against the real device on the client, so a build that does nothing already respects Android’s 48dp target and iOS’s near-black dark mode. Anything unrecognised — Linux, a television, a CI browser — lands on web, which is helix-ui’s own house style and a guaranteed no-op.

import { PRESETS, PLATFORM_PRESET_KEYS, express, platformVars } from '@helix-ui/dna';
PRESETS.ios();
PRESETS.macos();
PRESETS.android();
PRESETS.windows();
// Force a platform onto any genome — what a page showing all four does.
express(PRESETS.noir(), { platform: 'windows' });
// No DNA at all? `<ThemeProvider platform>` uses this under the hood.
platformVars('macos');

There are four presets, and there used to be three. apple covered iOS and macOS together, and that is the one merge the metrics do not survive: the HIG asks for a 44pt touch target and AppKit ships a 32px push button. apple, material and fluent still resolve as aliases.

Each platform preset also adopts its platform’s accent, which is what makes it read as native at a glance. That is one gene to put back: mutate(PRESETS.ios(), { gene: 'accent', allele: 'blue' }).

API

import {
wildtype,
PRESETS,
clone,
mutate,
compose,
evolve,
express,
type DNA,
} from '@helix-ui/dna';
const a = wildtype(); // base DNA
const b = PRESETS.botanic(); // a preset
const c = mutate(a, { gene: 'accent', allele: 'crimson' });
const child = compose(a, b); // cross two DNAs
const next = evolve([a, b, c, child]); // run a generation
const { cssVars, dataTheme } = express(child); // → CSS custom properties

Apply a DNA to a subtree via <HelixUIDNAProvider> (from @helix-ui/core):

import { HelixUIDNAProvider, Button } from '@helix-ui/core';
<HelixUIDNAProvider dna={child}>
<Button>This button renders under the bred DNA.</Button>
</HelixUIDNAProvider>;

Inheritance rules

When two DNAs cross, for each gene:

  1. Higher dominance wins. Ties are decided by coin flip.
  2. Recessive surfacing. If the loser allele is flagged recessive, it takes the locus ~15% of the time anyway. That’s how visual surprises emerge.
  3. Mutation. A small per-gene mutation rate (default ~4%) replaces the winner with a random other allele.

Tune all three per call (compose(a, b, { mutationRate: 0.2 }) or evolve(pop, { fitness: contrastScore })).

Why DNA changes are instant

helix-ui tokens ship in two layers:

  1. Primitives (color.brand.500, space.4, radius.md, font.size.md, shadow.md) — concrete CSS variable values.
  2. Semantic aliases (color.bg.action.brand.default = {color.brand.500}) — emitted as var(--helix-ui-color-brand-500) in the built CSS.

When DNA’s express() overrides a primitive (e.g. --helix-ui-color-brand-500), every semantic that references it follows through the cascade automatically. No re-render. No recomputation. Just a style mutation on the DNA provider’s wrapping div.

Phenotype

express(dna) produces:

  • Colour ramps generated with oklch(L% C H) for brand, neutral, success, warning, danger and info — 10 shades each, from the hue genes and chroma.
  • Every semantic alias that chains through one of those ramps, re-emitted on the wrapper so the cascade resolves against this genome.
  • Spacing tokens scaled by density.spacing.
  • Radius tokens scaled by radius.factor and shaped by radiusCurve.
  • Font families, a size scale derived from typeScale, line heights and tracking.
  • Control heights from paddingScale, clamped to a 24px floor.
  • Shadow set and material channel per surface.style.
  • Motion durations and easing curves.
  • A data-theme attribute when lightness is light or dark, and a platform when one was resolved.
{
'--helix-ui-color-brand-500': 'oklch(56% 0.16 234)',
'--helix-ui-color-brand-600': 'oklch(47% 0.155 234)',
'--helix-ui-radius-md': '11.20px',
'--helix-ui-space-4': '20.00px',
'--helix-ui-font-family-sans': "'Geist', 'Space Grotesk', …",
'--helix-ui-font-tracking-heading': '-0.011em',
'--helix-ui-control-height-md': '48px',
'--helix-ui-shadow-md': '0 4px 6px -1px rgb(0 0 0 / 0.1), …',
'--helix-ui-motion-duration-normal': '200ms',
/* …~170 vars total, plus 3 more when a platform is named… */
}

Persistence

A DNA is a plain JSON-serialisable object. To persist a user’s chosen theme, JSON.stringify(dna) to localStorage / your backend; JSON.parse to restore. Allele values are inert data — no functions or class instances.