Taegeuk — dani.korea leaves DDS
dani.korea no longer follows the dani design system. It now specifies 20px cards, pill buttons, soft elevation, full-bleed photography and Manrope — and takes its two accents straight from the 태극기.
57 screens in both dark and light mode, plus the flow that connects them — every screen in the product, drawn. This article is the release note. That is the design.
Every other dani product renders DDS: 4px radii, no pills, no shadows, no circles, mono labels in caps, one accent per product. dani.korea used to be one of them, with a navy accent bolted on. On 2026-08-05 that stopped. What replaced it is called Taegeuk, and it is not a DDS variant — it is a separate base system that happens to live in the same company.
01 · Why dani.ko left DDS
He raised five things and three of them were information architecture rather than styling — sections leading with a summary and a checklist, lists grouped and ranked, the trip as the one container that holds everything. Two were about the surface, and the surface is where DDS binds: “the logo color is too close to black,” and “Korean flag has blue and red — I think we should use those exact colors.”
There was no version of that brief DDS could absorb. Its rules — 4px radii, no pills, no shadows, no circles, no motion, mono labels in caps — are the opposite of what was being asked for. dani.ko now specifies 20px cards, pill buttons and tags, soft elevation, full-bleed photography and Manrope. That is a different system, so it was made one.
What this costs
Written down plainly so nobody is surprised mid-refactor. Leaving a design system is not a theme swap; it is a component rewrite.
| Thing | Consequence |
|---|---|
| DotCard / DotButton / DotChip / DotListItem / DotSectionHeader | Replaced by DaniKo* equivalents. They are used across all 42 screens. |
| Deviations justified by “DDS prohibits pills / motion / circles” | Moot. Those deviations are struck — the prohibitions no longer apply. |
| dot_flutter path dep | Still needed, for dotTypeScaleFor() and the viewport ramp. Not removed, just not used for surface styling. |
| dot-tokens.yaml § brand_colors.ko | Fixed. Flag Blue — #0047A0 light, #3D7BD9 dark — as of 2026-08-05. The row said #000080 until 2026-08-30 and was itself the stale thing. |
dot_flutter and shared/design/dot-tokens.yamlare shared with dani.go and every other dani product. Not one of those files was changed to make this happen. dani.ko owns its own theme and its own widgets and stops consuming DDS's visual layer, which is precisely what keeps dani.go's rendering exactly where it was. A product may leave the system; it may not take the system with it.02 · The accent is the flag
Two accents, taken exactly from the 태극기, each with one job. Blue is structure. Red is either a date or a danger — §03 below is the boundary that keeps those two from blurring.
| Token | Light | Dark | Job |
|---|---|---|---|
| blue | #0047A0 | #0047A0 | Navigation, primary buttons, progress, structure |
| on_blue | #FFFFFF | #FFFFFF | Text on a blue fill — 8.6:1 |
| red | #CD2E3A | #CD2E3A | A date, or a danger — and nothing else |
| on_red | #FFFFFF | #FFFFFF | Text on a red fill — 5.2:1 |
| blue_text | #0047A0 | #3D7BD9 | The one lift — thin blue text on dark |
| black / white | #000000 | #FFFFFF | The trigrams and field of the flag; ink and surface |
Note what the first four rows are saying: light and dark are the same hex. Every filled shape is the exact flag colour in both modes — no tuned pair, no dark-mode lift. The single deviation is blue_text, and it exists because it was measured.
| Pair | Contrast | Verdict |
|---|---|---|
| #FFFFFF on #0047A0 | 8.6:1 | Fills are the exact flag blue in both modes |
| #FFFFFF on #CD2E3A | 5.2:1 | Fills are the exact flag red in both modes |
| #CD2E3A as text on #0D1117 | 3.66:1 | Flag red needs no lift anywhere. Stays exact |
| #0047A0 as text on #0D1117 | 2.41:1 | Unreadable. #3D7BD9 (4.55:1) is used instead |
The lift applies only where blue is thin text or an icon sitting directly on the dark background — the readiness number, section numerals, the active tab label.
#000080 measured 1.31:1 on black, and that liability sat open in TODO.md for two days. #0047A0 is better but still under the 3:1 floor, so it gets the same treatment for the same reason. Do not “fix” blue_text back to #0047A0.03 · Red has two senses and they never share a shape
Red means a date or a danger. Nothing else, ever. That is a much narrower licence than most systems give their warning colour, and it is narrow on purpose: a red that means everything means nothing, and the whole reason this palette works is that the one red thing on a screen is either the thing you must do this week or the thing that can put you in hospital.
- · A date or a time on it — overdue, due-soon, not-yet-filed
- · A drawn subject that can injure a person
- · A blocking conflict, which is time-bound by definition
- · Emphasis
- · Branding
- · Expense
- · A rule you have broken
- · A destructive action that is neither time-bound nor dangerous
The test is only ever can this injure the reader — not “is this expensive” and not “do people get this wrong a lot”. A 7.1% currency conversion on $1,000 is not red. A missed last train is not red; it is a wall. An overstay fine and entry ban is not red — that is your record and your plans, not your person. An earthquake alert is red.
04 · A cool neutral base, not pure black
The old base was pure #000000 / #F7F7F7. Both are replaced, for two reasons that only show up once photography leads: pure black under a full-bleed image produces a hard seam at every image edge, and a cool neutral sits with flag blue where a warm one fights it.
| Token | Light | Dark |
|---|---|---|
| bg | #F7F8FA | #0D1117 |
| surface | #FFFFFF | #161B22 |
| surface_2 | #EEF1F5 | #1D242E |
| border | #DDE2E9 | #28313D |
| text | #0E1420 | #F0F3F7 |
| text_2 | #54606F | #A2AEBD |
| text_3 | #8593A3 | #6E7B8B |
| blue_soft | #E3ECFA | #0E1D38 |
| red_soft | #FDE7E9 | #2E1216 |
blue_soft and red_soft are the container fills behind summary blocks and urgent rows. They are tinted, not tonal-mapped.
Light mode is not an inversion. The two modes carry depth by different means: borders carry it on light, soft elevation carries it on dark. Flipping one to get the other is how an inverted theme ends up with shadows nobody can see.
05 · Manrope and DM Mono
Manrope for everything the user reads. DM Mono for everything the user copies. Chosen 2026-08-05 from four candidates shown side by side; Space Grotesk and Space Mono are retired, and the serif alternative was rejected in the same pass.
| Token | Size | Weight | Family | Usage |
|---|---|---|---|---|
| display | 34 | 700 | Manrope | Screen headline — “Korea, planned before you fly.” |
| display_sm | 27 | 700 | Manrope | Trip name over the hero photo |
| title | 25 | 700 | Manrope | Place names on detail |
| heading | 18 | 700 | Manrope | Card titles |
| body | 14 | 400/600 | Manrope | List rows, task titles |
| body_sm | 12.5 | 400 | Manrope | Secondary lines, descriptions |
| label | 10.5 | 700 | Manrope | Eyebrows and section labels, uppercase, .15em tracking |
| data | 12 | 500 | DM Mono | Every ID, PNR, price, date and confirmation number |
| amount | 34 | 700 | Manrope | The readiness numeral |
The rule that caused the rejection
Display weight is capped at 700. Tracking is −0.3px. Leading is 1.16. The rejected proposal used 900 at −1.6px tracking and 0.94 leading, and Ted's note was “font is too bold they look bunched up”. That is not a taste disagreement — the three values compound, and 0.94 leading on a 900 weight makes descenders touch the next line's caps. Do not raise any of the three.
// display — the cap, and all three numbers together
fontSize: 34, fontWeight: FontWeight.w700,
letterSpacing: -0.3, height: 1.16,Numbers are always DM Mono. Every ID, PNR, price, date and confirmation number. A passport number, a PNR, a confirmation code and a price are things a person reads character by character or copies into another app, so they get tabular figures. This is not up for restyling.
06 · Pills, 20px radii, soft elevation
The 4px spacing grid survives the move — xs 4 · sm 8 · md 12 · lg 16 · xl 20 · xxl 24, with screen padding 20, card padding 17, card gap 9 and section gap 23. Almost nothing else about the geometry does.
- · Cards — 20
- · Tasks and doc rows — 18
- · Buttons — 16
- · Photo tiles — 16–22
- · Hero — 22
- · Tags and chips — full pill
- · Frames — 40
- · Hairline 1px is still the default
- · 1.5px on secondary buttons
- · The only 2px is a red urgent border
- · Elevation light — 0 10px 28px rgba(15,35,70,.10)
- · Elevation dark — 0 14px 36px rgba(0,0,0,.55)
- · Used on the frame and on nothing inside it
07 · Photography leads
The original constraint was that place cards are photo-optional by default, with a category glyph as the normal state, because no API provides photographs of ordinary Korean venues. That is still true of the world. It stopped being true of dani.ko on 2026-08-06, when 1,102 photographs from 한국관광공사 포토코리아 came in hand under 공공누리 제1유형 — commercial use and derivatives permitted, attribution required.
So the rule is both, in that order. Where we hold coverage, a real photograph carries the hero — the brief was “show a lot of images on every page we can”, and this is what makes that literally true. Where we do not, a drawn scene carries it.
One exception, and it is a product boundary rather than a visual one: places the user adds have no guaranteed photograph. Those are a different card, not a broken one.
08 · What survives
Leaving a design system is not permission to relitigate everything. These are product rules rather than style, and none of them changed:
Every user-visible name reads English (한국어) — and copy buttons always copy the Korean.
Non-negotiable, and shipped broken twice already, which is exactly why it stays written down.
A richer surface must not manufacture confidence. A photograph never implies we checked the opening hours.
Including the cross-fade transition that exists to protect it.
An honest gap is not an error. A red unknown reads as “you did something wrong” when the thing missing is ours.
Confidence is still not a palette
verified / stale / unknown / blocking remain icon and word, not hue. Exactly one colour is added back:
| State | Light | Dark |
|---|---|---|
| verified / done | #12855A | #4ADE80 |
| everything else | neutral | neutral |
unknown stays neutral grey with a dashed border. stale carries a date and no colour. blocking uses the flag red, because a blocking conflict is by definition time-bound.
09 · What was not adopted
Taegeuk was one of four proposals and it did not win alone — it absorbed two of the others. Every proposal is kept as reference, with what is worth going back for.
| Proposal | Verdict | Worth revisiting |
|---|---|---|
| A · Postcard | Layout adopted wholesale into D | — |
| B · Compass | Not adopted | Filled tonal containers made status unmissable. Borrow if red-means-deadline proves too subtle |
| C · Passport | Readiness spine adopted into D | The per-shelf colour system, and the passport stamp for completed items |
| A · Ledger | Retired | Its provenance discipline survives in §6.2 |
| B · Passport / C · Atlas | Retired | C’s cluster canvas, if S-31 ever gets revisited |