DDS 2 — one accent per product
The rule that every dani product shares one red accent is gone. Each product now carries its own brand colour as its UI accent. Two smaller corrections come with it: the neutrals we published were never the neutrals we shipped, and the logo lockup finally has written ratios.
Every token, component and specimen in one place: 20 accent pairs across four platform mirrors, 13 components with usage rules, 21 foundation specimens and four product UI kits. This article is the release note. That is the reference.
DDS 1.1.0 was emphatic on this point. Product colours existed for app icons and marketing assets; in-app accent was Dot Red, the same in every product, and §09 listed the six places it was allowed to appear. That rule is now retired.
01 · One accent per product
A product's brand colour is now its UI accent. Nothing else changes: the neutral base, the type scale, the 4px grid, the radii, the no-shadow rule and every component are identical across all twenty products. The accent is the only variable.
Each accent ships as a pair — one tuned for light surfaces, one for dark — following the pattern Dot Red already used. Never cross-use them.
| Product | Name | Light | Dark |
|---|---|---|---|
| dani.subs | Alert Red | #E80000 | #FF2D2D |
| dani.news | Scarlet | #D11500 | #FF4518 |
| dani.penny | Copper | #C4470A | #FF6B1A |
| dani.park | Signal Orange | #B26500 | #FF8A00 |
| dani.tos | Amber | #8F5C00 | #FFB300 |
| dani.money | Gold | #8C7000 | #FFD400 |
| dani.snap | Flash Yellow | #5C7A00 | #E8FF1A |
| dani.go | Alert Red | #E80000 | #FF2D2D |
| dani.plant | Green | #0A8F3D | #3AFF7D |
| dani.budget | Mint | #008F5E | #00FFB0 |
| dani.clean | Teal | #00806F | #00E0C4 |
| dani.track | Sky | #0070B2 | #00B8FF |
| dani.web | Blue | #0047D6 | #3D7AFF |
| dani.bot | Violet | #4B1FBF | #8F5CFF |
| dani.photo | Purple | #7A14B5 | #C14DFF |
| dani.design | Magenta | #B51495 | #FF4DD4 |
| dani.pet | Pink | #C4144F | #FF4D8F |
| dani.zone | Graphite | #333333 | #666666 |
| dani.korea | Flag Blue | #0047A0 | #3D7BD9 |
| dani.do | — not assigned | #000080 | #4D7CFF |
dani.subs and dani.go share Alert Red; dani.ko and dani.do currently share navy. See “Still open”.
All twenty pairs as CSS, YAML, Dart and Swift: products.css · accent specimen
The mark carries the accent. One geometry, twenty hues.
What this costs
Under 1.1.0 a screenshot from any dani product was recognisably dani, because the red was the through-line. That is gone. What remains shared is the monochrome base, the Grotesk / Mono split, the rectangles and the hairlines — which is most of the system, but it is a quieter kind of family resemblance. This was the trade, and it was made deliberately.
- · Primary button fill
- · Key numeric values
- · Active tab / selected state
- · Alert badges and status pills
- · Section bullets and tag dots
- · Focus rings
- · Body text
- · Borders or dividers
- · Page or card backgrounds
- · Disabled or muted states
- · Standard icons
- · Section headers
§09 is unchanged. Only which colour fills those slots has changed.
In code
The Flutter package reads a per-product lookup instead of two constants. On the web, scope a product with a data attribute and --accent follows.
[data-product="plant"] { --accent: #0A8F3D; --on-accent: #FFFFFF; }
[data-theme="dark"] [data-product="plant"] { --accent: #3AFF7D; }// lib/src/skin/skins/dds_accents.dart
const DdsAccents.of('plant'); // → (light: 0xFF0A8F3D, dark: 0xFF3AFF7D)02 · The published neutrals never shipped
§02 of the 1.1.0 spec gave nine tokens per mode and said to memorise them and never deviate. Those are not the values in dds_skin.dart, which is what every product actually renders. Eleven of them differ.
| Token | Published | Shipped |
|---|---|---|
| light bg | #F7F7F7 | #F9FAFB |
| light surface_2 | #F0F0F0 | #F3F4F6 |
| light border | #E0E0E0 | #E5E7EB |
| light text | #111111 | #111827 |
| light text_2 | #666666 | #6B7280 |
| light text_3 | #AAAAAA | #9CA3AF |
| dark bg | #000000 | #111827 |
| dark surface | #0B0B0B | #1F2937 |
| dark surface_2 | #141414 | #374151 |
| dark border | #1F1F1F | #374151 |
| dark text | #FFFFFF | #F9FAFB |
The shipped ramp is Tailwind's slate. The published one was never in the code.
Both ramps side by side: code vs. published spec
This is a documentation fix, not a design change — nothing on screen moves. But it means the most-quoted sentence in the original post was describing an intention rather than a behaviour, which is worth saying plainly.
03 · The lockup has ratios
The mark had artwork and two implementations but no written specification, so consumers re-derived its proportions and drifted. Every ratio below is normalised to the tile edge S, taken from dani_logo_dds_v3.svg — the 560×160 master.
| Property | Value | Note |
|---|---|---|
| tile edge | S | the unit everything else derives from |
| tile shape | superellipse | |x/r|^5 + |y/r|^5 = 1 |
| tile r | 0.42 · S | ≈ 0.20 effective circular roundness |
| glyph size | 0.658 · S | Space Grotesk 700, live text |
| dot diameter | 0.125 · S | r 7.5 at S 120 |
| gap, tile → wordmark | 0.25 · S | x 170 against a tile ending at 140 |
| wordmark size | 0.867 · S | 104 at S 120 |
| wordmark weight | 700 | not 600 |
| wordmark tracking | −0.029 em | −3px at 104px |
| clear space | 0.25 · S | all four sides — same as the gap |
| Flutter baseline nudge | +0.075 · wordmark | Flutter only; CSS/SVG is 0 |
Derive everything from S. Never hardcode pixel values at one size.
The last row is the only number not in the artwork, and it is a platform correction rather than a proportion. Flutter puts the alphabetic baseline 0.771 of the font size below the line box at height: 1.0; CSS and SVG half-leading put it at 0.846. Centre a wordmark against the tile in a Flutter Row and it hangs about 8% of a tile edge too high, so Flutter consumers translate it back down. Use a Transform.translate, never padding — padding adds to the laid-out height and a surrounding FittedBox scales the whole lockup down to pay for it.
Construction diagram and the twenty marks: lockup · app icons
Tile 56, gap 14, wordmark 48.5 — all derived from S = 56.
The tile is the system's second curve
§05 says DDS is rectangular, with three radii and one stated exception: the small dot indicator. There is a second, and it is not on the radius scale at all. The tile is a superellipse — |x/r|^5 + |y/r|^5 = 1 at r 0.42 of its edge — rather than a rounded rectangle. A circular arc of the same apparent roundness pinches visibly at the sizes the mark actually ships at, 34px in a list row and 24px in a nav bar; continuous curvature does not. Pre-DDS-2 artwork carries rx 26 (0.217 of the edge) and needs regenerating. The rule now reads: two curves, the tile and the dot, both in the mark.
04 · What did not change
Space Grotesk for UI and Space Mono for data. The 4px grid, with 18px card padding, 14px card gap and 24px section gap. The three radii. Hairline 1px and strong 2px borders, and no drop shadows anywhere. 100ms and 200ms, ease-out, reduced motion disabled rather than shortened. The 4.5:1 contrast floor, the 44pt hit area, the 2px focus ring at 2px offset. §09's two lists. All of it stands.
05 · Migrating
06 · Still open
Four things this release does not settle, listed so nobody has to rediscover them.
It ships, and it was absent from the 1.1.0 colour table. It currently borrows dani.ko's navy, which means two unrelated products share a hue.
subs and go both use Alert Red; ko and do both use navy. If the accent is what distinguishes a product, four products are not distinguished.
on_accent is #FFFFFF for all twenty pairs, but Flash Yellow #E8FF1A and Gold #FFD400 need dark text, not white. Those two pairs need an on_accent of their own.
This post's predecessor cites shared/design/dot-tokens.yaml; dds_skin.dart cites shared/design/dds-tokens.yaml. Resolved for the logo block on 2026-08-30 — shared/design/dot-tokens.yaml and dot.css now carry §03 — but two files still claim the same name.
§03 specifies the tile as a superellipse at r 0.42 S. Every shipping consumer — this site's own AppIcon, dani.ko's icon builder, both Flutter widgets — still draws the pre-DDS-2 rounded rect at 0.217. The tile shape and every generated icon in every product have to move in one pass or the marks disagree.