← Back to Blog
Design··12 min read

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.

Where it lives
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.

SystemUsed byShapeDepthFaces
DDS-1 · dot_flutterdani.go, dani.scoutrectangular, 4pxflatSpace Grotesk / Space Mono
DDS-2 · dot.cssdani.subs, admin-consolerectangular, 4pxflatSpace Grotesk / Space Mono
DDS-3 · dds3/nothing yetrectangular, 4pxflat, no shadowsManrope / DM Mono
DDS-4 · dds4/dani.toolssoft 2–28px + roundphysical, 4 layersSpace 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.

LaneFromWhat that means
FONTSDDS 2Space Grotesk for UI; Space Mono for labels, data and the LCD
SURFACESinstrumentpage → unit → raised / well — four layers and nothing else
COLORper appWarm-grey neutrals and exactly one product accent, plus its edge
INSTRUMENTfixedLCD amber and LED green / amber / red, identical in every product
MOTIONspringyOvershoot 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.

TokenLightDarkRole
page#DEDCD6#111110The desk the device sits on
body#F1F0EB#1C1C1AThe enclosure
key#F7F6F2#262624Key caps and raised panels
key_edge#C9C6BD#0B0B0AThe hard 3px edge under every key
well#E4E2DC#151513Recessed: fields, grille, tray
hairline#D6D3CB#2E2E2BTracks and dividers
text#2A2A28#EDECE6—
text_3#7A776F#8C8980Labels, 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.

Tokentools lighttools 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.

css
.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.

ConstantValueOnly on
lcd.glass / lcd.text#1F1F1B / #F2B33DThe LCD, toasts, tooltips
lcd.ok#7BC34ELCD segments when a job completes
led.ok#5E9E3A / glow #7BC34ELEDs, done bars, the done ActionKey
led.warn#E0A100LEDs
led.error#D23C2CLEDs, error bars, a field's error ring

Three laws stop that from becoming a palette:

✓ Only on instrument parts
  • · LEDs, the LCD, tray bars, the done ActionKey, a field's error ring
  • · Never body text, never a panel fill
✓ Never alone
  • · Every LED state repeats as a label, an icon or an LCD line
  • · The LED is the glance; the label is the meaning
✓ Never per product
  • · Identical in every product and both modes
  • · If an accent collides with led.ok, the label disambiguates
LEDColourLabel
offled_offIDLE
onaccentREADY
busyaccent, blinks 320msWORKING
okled.ok_glowDONE
warnled.warnCHECK
errorled.errorERROR

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.

LayerFillShadow
pagepagenone
unitbody0 30 60 −30 soft drop + 0 2 6 contact; dark adds a hairline border
raisedkey0 3 0 key_edge (the hard edge) + 0 4 10 soft; hover lifts 1px, pressed sinks 3px
wellwellinset 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

TokenpxUsed by
seg2LCD segments, progress bars
thumb6Tray thumbnails, fader cap
card10Tray card, small keys
field12Fields, segmented track, tabs, toast
key14HwKey
panel16Raised panel, well, LCD
unit22The device body, dialogs
pill28ActionKey — 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.

css
--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:

TokenValueUsed by
press90msKey sink
quick180msColour, LED, segment
normal320msToggles, text style
slow600msFile fan, meters
springcubic-bezier(.34,1.56,.64,1)Segmented indicator, dialogs
spring_strongcubic-bezier(.34,1.8,.5,1)Toggle knob, dial needle, LED pop
outcubic-bezier(.2,.8,.2,1)Odometer, meters
The law under the character
Every duration passes through 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.

GroupWidgets
SurfacesUnit · RaisedPanel · Well · Label · PanelHeader
KeysKey (sm, icon) · ActionKey · mini key · keyboard hint · tool keypad
IndicatorsLed (6 states) · LedChase · icon chip · letter tile · badge · chip
LCDReadout · segment bar · caret · ScrambleText · Odometer
SelectionSegmented (h / v) · Toggle · ToggleRow · checkbox key · radio key · session tabs
ValuesRotary dial · slider · fader · stepper · meter · ring · VU · battery · sparkline
InputField · mono field · error state · search · copy field
FilesDrop well with file fan · tray · tray card · DONE stamp
VisualiserSpeaker grille — idle, working, done, error
FeedbackLCD toast · LCD tooltip · dialog on scrim
HomeFive-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

× Not in the system
  • · 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
× Not yours to change
  • · 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:

html
<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:

dart
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.