DDS 4 — Industrial Instrument
A fourth dani design system, and the first one with depth. It treats the app as a device: a warm-grey body you could hold, raised keys that sink when you press them, recessed wells, status LEDs and an amber LCD. It shipped first in dani.tools; this is it pulled out, named, and given forty widgets to build with. It is additive — DDS-1, DDS-2 and DDS-3 are not edited.
shared/design/dds4/ — the rules in DDS4.md, every value in dds4-tokens.yaml, the stylesheet in dds4.css, the Flutter package in dds4_flutter/. The system drawn is gallery/index.html — open it full-screen. Every widget in this post is that gallery, live: press them.01 · A fourth system, and the opposite of the third
DDS-3 is a system of refusals: 4px corners, hairline borders, no pills, no circles, no shadows. DDS-4 is built from exactly what DDS-3 forbids. Its corners are soft, its LEDs and knobs are round, and depth is not decoration but the whole grammar — a raised key, an inset well and the body they sit on are three different things, and you can tell them apart with your eyes closed to colour.
| System | Used by | Shape | Depth | Faces |
|---|---|---|---|---|
| DDS-1 · dot_flutter | dani.go, dani.scout | rectangular, 4px | flat | Space Grotesk / Space Mono |
| DDS-2 · dot.css | dani.subs, admin-console | rectangular, 4px | flat | Space Grotesk / Space Mono |
| DDS-3 · dds3/ | nothing yet | rectangular, 4px | flat, no shadows | Manrope / DM Mono |
| DDS-4 · dds4/ | dani.tools | soft 2–28px + round | physical, 4 layers | Space Grotesk / Space Mono |
Pick one per product. A DDS-4 key dropped into a flat DDS-2 screen reads as a bug, not a feature.
It declares its own names — .dds4-* in CSS, Dds4* in Dart — so it compiles beside every other system with no collision, the same discipline DDS-3 set.
02 · Five lanes
DDS-3 was three lanes. DDS-4 needs five, because it has two things no earlier system had: an instrument that owns its own colours, and motion that is part of the specification rather than a polish pass.
| Lane | From | What that means |
|---|---|---|
| FONTS | DDS 2 | Space Grotesk for UI; Space Mono for labels, data and the LCD |
| SURFACES | instrument | page → unit → raised / well — four layers and nothing else |
| COLOR | per app | Warm-grey neutrals and exactly one product accent, plus its edge |
| INSTRUMENT | fixed | LCD amber and LED green / amber / red, identical in every product |
| MOTION | springy | Overshoot curves, haptics and loops — every one behind reduce-motion |
Neutrals — warm, never blue
DDS-3 took Taegeuk's cool, faintly blue greys. DDS-4 goes the other way: a beige-grey enclosure in light mode, graphite in dark — the colour of a piece of hardware, not of a screen.
| Token | Light | Dark | Role |
|---|---|---|---|
| page | #DEDCD6 | #111110 | The desk the device sits on |
| body | #F1F0EB | #1C1C1A | The enclosure |
| key | #F7F6F2 | #262624 | Key caps and raised panels |
| key_edge | #C9C6BD | #0B0B0A | The hard 3px edge under every key |
| well | #E4E2DC | #151513 | Recessed: fields, grille, tray |
| hairline | #D6D3CB | #2E2E2B | Tracks and dividers |
| text | #2A2A28 | #EDECE6 | — |
| text_3 | #7A776F | #8C8980 | Labels, hints |
The accent — one token, plus its edge
The hue still comes from dot-tokens.yaml § brand_colors, which remains the registry for all four systems. DDS-4 adds one companion value, accent_edge: the hard bottom edge under an accent key. Without it the ActionKey is a flat pill and the whole illusion collapses.
| Token | tools light | tools dark |
|---|---|---|
| accent | #D6249F | #FF5CC8 |
| on_accent | #FFFFFF | #0B0B0B |
| accent_edge | #141896 | #3B40C4 |
| tile | #D6249F | #D6249F |
dani.tools' Fuchsia Pink. The indigo edge is deliberate — a cool shadow under a warm key.
Unlike DDS-3, the accent splits by mode. DDS-3 argued that a fill is what text sits on, not what it sits against, so one value serves both modes. That holds for a flat button. It does not hold for a key that is meant to look lit: a lit key reads brighter in the dark, and the instrument would look switched off without it. The logo tile still never inverts.
.dds4-subs.dds4-light { --accent:#007504; --on-accent:#FFF; --accent-edge:#003302; --tile:#007504; }
.dds4-subs.dds4-dark { --accent:#00D40A; --on-accent:#0B0B0B; --accent-edge:#005504; --tile:#007504; }Adding a product is two rules and never a new token. The gallery's product switch does exactly this — try subs and go in the foundations panel above.
03 · The instrument owns status
DDS-2 and DDS-3 both refuse a semantic palette: success is a ✓, warning is a ! and a border, and the accent means exactly one thing. DDS-4 has green, amber and red — and it can, because those colours do not belong to the brand. They belong to the device. An LED is green because LEDs are green; an LCD is amber because LCDs are amber. A pink product and a green product have the same amber display and the same green light.
| Constant | Value | Only on |
|---|---|---|
| lcd.glass / lcd.text | #1F1F1B / #F2B33D | The LCD, toasts, tooltips |
| lcd.ok | #7BC34E | LCD segments when a job completes |
| led.ok | #5E9E3A / glow #7BC34E | LEDs, done bars, the done ActionKey |
| led.warn | #E0A100 | LEDs |
| led.error | #D23C2C | LEDs, error bars, a field's error ring |
Three laws stop that from becoming a palette:
- · LEDs, the LCD, tray bars, the done ActionKey, a field's error ring
- · Never body text, never a panel fill
- · Every LED state repeats as a label, an icon or an LCD line
- · The LED is the glance; the label is the meaning
- · Identical in every product and both modes
- · If an accent collides with led.ok, the label disambiguates
| LED | Colour | Label |
|---|---|---|
| off | led_off | IDLE |
| on | accent | READY |
| busy | accent, blinks 320ms | WORKING |
| ok | led.ok_glow | DONE |
| warn | led.warn | CHECK |
| error | led.error | ERROR |
04 · Depth is four layers, exactly
Every surface is exactly one of these. A fifth is a bug. Raised sits on a unit; a well sits in a unit or a raised panel; nothing sits on a well except its content.
| Layer | Fill | Shadow |
|---|---|---|
| page | page | none |
| unit | body | 0 30 60 −30 soft drop + 0 2 6 contact; dark adds a hairline border |
| raised | key | 0 3 0 key_edge (the hard edge) + 0 4 10 soft; hover lifts 1px, pressed sinks 3px |
| well | well | inset 0 2 5 well_shadow + a 1.5px top lip |
Selected means sunk. A selected key sits 3px down with its edge gone, the way a latched hardware button does — never a border, never a fill swap on its own. And there is exactly one ActionKey per screen: the accent pill that starts the job. It hums a ±0.8px vibration while busy, sits pressed in, and turns green when done.
Radii — soft, and each one has a job
| Token | px | Used by |
|---|---|---|
| seg | 2 | LCD segments, progress bars |
| thumb | 6 | Tray thumbnails, fader cap |
| card | 10 | Tray card, small keys |
| field | 12 | Fields, segmented track, tabs, toast |
| key | 14 | HwKey |
| panel | 16 | Raised panel, well, LCD |
| unit | 22 | The device body, dialogs |
| pill | 28 | ActionKey — and nothing else |
| round | ∞ | LEDs, dial, mini keys, toggle, radio |
05 · The LCD
The readout is the system's voice. Amber phosphor on near-black glass, a slow scanline that sweeps it every 3.2 seconds, and a stepped six-frame flicker whenever the mode changes — like an old display warming up. Results decode into place with ScrambleText; numbers roll like an odometer, each column a little slower than the one to its right, so a change ripples across the figure.
06 · Type
DDS 2's faces, at instrument sizes. Space Mono does triple duty: data, the LCD, and the stencil — the uppercase, +1.3-tracked label that names every group as NN — NAME, where the number is the panel's position on the device. Uppercasing is the label widget's job and nobody else's; a widget that uppercases user content breaks an email address.
--sans: 'Space Grotesk', system-ui, sans-serif;
--mono: 'Space Mono', ui-monospace, monospace;07 · Motion is in the spec
Every control moves like a part. The segmented indicator springs past its target and settles; the toggle knob overshoots; the dial needle swings; a new tray card flies in with a slight spin; a green check pops in rotating. These are tokens, not taste:
| Token | Value | Used by |
|---|---|---|
| press | 90ms | Key sink |
| quick | 180ms | Colour, LED, segment |
| normal | 320ms | Toggles, text style |
| slow | 600ms | File fan, meters |
| spring | cubic-bezier(.34,1.56,.64,1) | Segmented indicator, dialogs |
| spring_strong | cubic-bezier(.34,1.8,.5,1) | Toggle knob, dial needle, LED pop |
| out | cubic-bezier(.2,.8,.2,1) | Odometer, meters |
dds4MotionOf() in Dart and a prefers-reduced-motion block in CSS. Under reduce-motion every duration is zero, every loop stops, the scanline is not painted and scrambled text appears finished. Lots of motion is the character. Optional motion is the law.08 · Forty widgets
This is the part that makes DDS-4 a system rather than a mood. Every widget below exists in dds4.cssand the gallery, and the Flutter package ports dani.tools' shipped widgets under Dds4* names.
| Group | Widgets |
|---|---|
| Surfaces | Unit · RaisedPanel · Well · Label · PanelHeader |
| Keys | Key (sm, icon) · ActionKey · mini key · keyboard hint · tool keypad |
| Indicators | Led (6 states) · LedChase · icon chip · letter tile · badge · chip |
| LCD | Readout · segment bar · caret · ScrambleText · Odometer |
| Selection | Segmented (h / v) · Toggle · ToggleRow · checkbox key · radio key · session tabs |
| Values | Rotary dial · slider · fader · stepper · meter · ring · VU · battery · sparkline |
| Input | Field · mono field · error state · search · copy field |
| Files | Drop well with file fan · tray · tray card · DONE stamp |
| Visualiser | Speaker grille — idle, working, done, error |
| Feedback | LCD toast · LCD tooltip · dialog on scrim |
| Home | Five-line widget chrome — clock, weather, currency, device |
09 · A whole instrument
The parts composed into one screen: dani.tools' image converter. Pick a format and the LCD flickers to the new mode. Press Convert and the system shows busy three ways at once — the ActionKey hums, the LED blinks, the grille waves and the segments fill — and done in green in all three.
10 · What DDS-4 does not have
- · A fifth depth layer, a gradient fill, a glass blur on content
- · A second product hue, or an instrument hue used as decoration
- · Square corners on a key, or a pill on anything but the ActionKey
- · Text links or TextButtons — every action is a key
- · An animation that ignores reduce-motion
- · Colour as the only signal
- · More than one ActionKey on a screen
- · dot.css, dot_flutter or dds3/, edited to suit DDS-4
11 · Adopting it
On the web, three classes:
<link rel="stylesheet" href="shared/design/dds4/dds4.css">
<body class="dds4 dds4-tools dds4-light">In Flutter, a Theme around one screen is the migration path, as in DDS-3:
Theme(
data: dds4Theme(Dds4Palette.dark(Dds4Accent.tools)),
child: const ConverterScreen(),
)Fonts are declared as family names only — no google_fonts, no runtime fetch. The app bundles the six files from shared/design/dds4/fonts/. dani.tools still ships its own copy of the kit; moving it onto the package is a rename, not a redesign.