Axel Kummerlöwe
Design system2026

Ton: a design system against the AI average

An AI builds interfaces fast, but it builds the average. I wanted to know whether a design system can stop that without me reworking every result. Ton is my answer: warm, soft, with springs and sound.

Ton's bento screen: warm clay surfaces with soft shadows, a large card with the heading “Firing on Thursday” and a clay bowl rising out of its top, next to smaller cards with a slider and a switch.
Ton's reference screen. It runs live further down.
Type
Design system with checks in build and tests
Role
Concept, tokens, spring solver, sound, specs, check scripts and reference
Period
November 2026
Tech
W3C design tokens, OKLCH, CSS linear(), Tailwind v4, Radix, Web Audio, Figtree

Summary

AI interfaces often look the same: a purple gradient, glass, Inter, three equal cards. That is no accident. A model writes what is most likely, and the most likely is the average of what it has seen.

Ton is my second system after Kante. It is deliberately tactile: clay and ochre surfaces, soft shadows, grain, motion with springs and quiet sounds. The rules are not only written in an instruction file. The token build and a check script in the tests check them.

The problem

The average looks like nothing

On the left is a screen of the kind an AI likes to build without guidance. I recreated it myself, with everything that comes with it. On the right is the same content in Ton.

The left screen is not wrong. It is just interchangeable. Every brand that looks like this looks like all the others.

New

Firing on Thursday

Twelve pieces are glazed and waiting for the kiln. Book a shelf before Wednesday noon.

Shelves free

3 of 8

Peak

1240 °C

Slow cool

On

Book a shelf
Recreated, not an actual AI run
Firing on Thursday

Twelve pieces are glazed and waiting for the kiln. Book a shelf before Wednesday noon.

Shelves free3 of 8
Peak1240 °C, slow cool
The same content in Ton

Rules

Rules that are checked

A list of bans in a text file does little if nobody checks it. In Ton there are two places for that. The token build stops when a colour pair lacks contrast, a surface has the wrong hue or a spring takes too long to settle. A scan in the tests checks the reference code for gradients, glass, fixed timings, certain typefaces and borders.

The page reads every excerpt here from the real files when it is built. If a file changes, the page changes.

Hard rules in AGENTS.md

design-system-ton/AGENTS.md

## Hard rules
1. No colour literals: no hex, rgb(), hsl(), oklch().
2. No Tailwind palette colours, no `bg-white`, no `text-black`.
3. No gradients on surfaces.
4. No glass: no `backdrop-blur`, no semi-transparent surfaces over content.
5. No `duration-*`, no `ease-linear`, `ease-in`, `ease-out` or `ease-in-out`, no `transition: … s`. Motion only with `ease-ton-*` or `useSpring`.
6. No typefaces other than the system typeface (Figtree via `font.family.base`); explicitly no Inter, Inter Tight, Roboto, and no monospace.
7. No borders and no 1px lines for structure; structure comes from surface, shadow, spacing and font weight.
8. No `rounded-none`; corners only through `rounded-ton-*`.
9. No base tokens.
10. Sound only through the sound engine and only for events a spec names.
The file an AI reads first. The scan catches typical violations of most rules. For typefaces it looks for Inter, Roboto and monospace, for glass it looks for backdrop filters. The rule about sound has no check.

A spring as a token

design-system-ton/tokens/semantic/motion.json

"press": {
  "$value": { "mass": 1, "stiffness": 2000, "damping": 54 },
  "$description": "Press feedback. Under-damped on purpose so the surface visibly rebounds; settles inside 250 ms.",
  "$extensions": { "de.axkl.ton": { "role": "press" } }
}
Springs are tokens made of mass, stiffness and damping. The description says what the spring is for.

What the build makes of it

design-system-ton/build/tokens.css

--ton-motion-spring-press-easing: linear(0, 0.0122 1.5625%, 0.0456 3.125%, 0.1581 6.25%, 0.4661 12.5%, 0.7566 18.75%, 0.87 21.875%, 0.9579 25%, 1.0211 28.125%, 1.062 31.25%, 1.0924 37.5%, 1.0525 50%, 1.0054 62.5%, 0.9915 75%, 1);
--ton-motion-spring-press-duration: 231ms;
The build solves the equation of motion and writes a CSS curve with linear(). At the checked points it deviates less than 1 % from the exact solution. No animation library is needed for this.

The token build fails on a purple surface

design-system-ton/build/failure-sample.txt

Ton token gates failed:
  Gate 1: --ton-color-text-muted on --ton-color-surface-raised has contrast 3.41, needs 4.5 (oklch(40% 0.035 45) on oklch(70% 0.1 290))
  Gate 1: --ton-color-text-danger on --ton-color-surface-raised has contrast 3.88, needs 4.5 (oklch(38% 0.12 28) on oklch(70% 0.1 290))
  Gate 1: --ton-color-text-success on --ton-color-surface-raised has contrast 2.64, needs 4.5 (oklch(45% 0.06 150) on oklch(70% 0.1 290))
  Gate 2: surface --ton-color-surface-raised is oklch(70% 0.1 290), hue 290 with chroma 0.1 falls in the violet band 270-320
  Gate 6: semantic colour --ton-color-surface-raised is "oklch(70% 0.1 290)", it must reference another token with var(--ton-…)
Real console output from when I set a surface to violet as a test. The token build stops and names every rule it broke.

Colour and material

Warm surfaces, calculated instead of guessed

All colours are defined in OKLCH. The space is built so that equal lightness values also look roughly equally light. That makes steps predictable.

Every text colour names the surfaces it may sit on. The contrast below is calculated from the tokens when the page is built, not typed in.

Surfaces

  • surface.baseoklch(84% 0.035 68)
  • surface.raisedoklch(88% 0.025 70)
  • surface.sunkenoklch(76% 0.075 72)
  • surface.accentoklch(44% 0.13 28)
  • action.primary.bgoklch(44% 0.13 28)
  • action.primary.bg-pressedoklch(38% 0.12 28)

Text on its allowed surfaces, contrast calculated from the tokens

  • text.defaultoklch(26% 0.03 40)
    • 9.55 : 1on surface.base, needs 4.5
    • 10.89 : 1on surface.raised, needs 4.5
    • 7.23 : 1on surface.sunken, needs 4.5
  • text.mutedoklch(40% 0.035 45)
    • 5.69 : 1on surface.base, needs 4.5
    • 6.48 : 1on surface.raised, needs 4.5
  • text.on-accentoklch(88% 0.025 70)
    • 5.76 : 1on surface.accent, needs 4.5
    • 5.76 : 1on action.primary.bg, needs 4.5
    • 7.38 : 1on action.primary.bg-pressed, needs 4.5
  • text.dangeroklch(38% 0.12 28)
    • 6.47 : 1on surface.base, needs 4.5
    • 7.38 : 1on surface.raised, needs 4.5
  • text.successoklch(45% 0.06 150)
    • 5.02 : 1on surface.raised, needs 4.5
  • action.primary.textoklch(88% 0.025 70)
    • 5.76 : 1on action.primary.bg, needs 4.5
    • 7.38 : 1on action.primary.bg-pressed, needs 4.5
  • focusoklch(26% 0.03 40)
    • 9.55 : 1on surface.base, needs 3
    • 10.89 : 1on surface.raised, needs 3
    • 7.23 : 1on surface.sunken, needs 3

The same gradient, two colour spaces

surface.accent → text.success

in oklch
in srgb
On top the two tokens are mixed in OKLCH, below in sRGB. In sRGB the middle turns a muddy brown. In OKLCH it keeps its colour.

Motion

Springs instead of fixed timings

Most interfaces move with a fixed duration and a fixed curve, say 0.3 seconds ease-in-out. That feels smooth, but not like a material. Ton describes motion as a spring: mass, stiffness, damping.

Two values follow from that. The natural frequency ω₀ = √(k/m) says how fast the spring wants to swing. The damping ratio ζ = c / (2·√(k·m)) says how strongly it is slowed down. Below ζ = 1 it overshoots and swings at ω_d = ω₀·√(1−ζ²).

Many teams use Motion, formerly Framer Motion, for this. Ton deliberately uses no library. The build precomputes the springs for CSS, and a small hook handles dragging and releasing.

Mass m
Stiffness k
Damping c
useSpring with these values
transition: transform 0.3s ease-in-out

Solid: the spring. Dashed: 0.3 s ease-in-out. Time runs to the right.

ω₀ = √(k/m)
7.75 rad/s
ζ = c / (2·√(k·m))
0.581
ω_d = ω₀·√(1−ζ²)
6.30 rad/s
Settles in
1378 ms
CSS output
linear() with 16 points, 1378 ms
The sliders start at the drag spring. Try extreme values. With reduced motion the ball jumps without overshoot.

Layout

Density and emptiness instead of three equal cards

The bento is unequal on purpose: a dense cluster, an empty column and one large card. The bowl breaks out of it and sits on its own layer. It moves slightly against the pointer and scrolling.

With reduced motion it stays still. The “Wet clay” card can be pulled, with the arrow keys too.

Firing on Thursday

Twelve pieces are glazed and waiting for the kiln. Book a shelf before Wednesday noon.

Shelves free3 of 8
Wet clayPull this card. It springs back.
Peak temperature

Leave the lids off until the glaze is dry to the touch.

Sound

Sound only on request

Ton sounds quiet: a muffled click, two rising tones for success, a low tone for errors. Everything is made from oscillators, noise and filters. There are no audio files.

Sound is off until you switch it on. Only then, on your next gesture, is the audio context created. The sound only repeats what you can also see.

There is no media query for reduced sound. prefers-reduced-sound is a frequent wish, but not a standard. That is why sound is off by default here and stays off until you switch it on yourself.

Not clicked yet. Sound is off.

Limits

What Ton does not do

  • A list of bans alone only creates a new average. Soft surfaces, bento grids and 3D objects breaking out of cards are on their way to becoming the default themselves. What helps is a direction that comes from the brand, and checks that hold it.
  • I did not measure any AI runs with Ton. How well an AI works with a system like this, I measured with Kante.
  • The checks find values and patterns in code. Whether a page is well designed is not something a script decides.
  • Sound cannot adapt to preferences automatically, because browsers have no setting for it.

To the Kante case with the measured runs

What I take away

With AI, I design fewer individual screens and more the rules they are made from. Ton was my practice ground for that. Every decision that exists as a token or a check is held in place by the build. I then do not have to ask for it again in every result.

Next project

Kante

Design system