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.

- 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.
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
Twelve pieces are glazed and waiting for the kiln. Book a shelf before Wednesday noon.
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.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" } }
}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 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-…)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
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.
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
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.
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.
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
