Owner review 1, lane A — leading markers, heading spacing, type, rail, blocks, strips, English, motion and accessibility (2026-10-11)
Status: Draft · Date: 2026-10-11 · Lane A of the owner's review of the flat v2 component boards: points 1–6, 21 and 22, plus an audit of library §3–§6 for departures from platform guidance and measured peer practice. Inputs: grammar, component library, behaviour addendum, the built boards in docs/public/wireframes/. Brief: platform guidance beats taste, measured peers beat assertions; where sources disagree, say so and recommend. Nothing outside this file was changed.
0. How to read this note
Evidence tags. Doc = a primary document read for this note (URL given). Code = a value read from a published design-system package or source file. Measured = measured by me on App Store frames or raw captures (method below). Derived = computed from those inputs (method stated). Unverified = not found, or only a device can answer.
How the numbers were measured.
- Frames. 431 App Store frames (48 apps, including the English-UI sets of 7 Korean apps), fetched through the iTunes lookup API (
screenshotUrls, original size via the0x0ss.pngsuffix). Most are marketing mock-ups (a phone inside a panel). Two sets are raw captures and therefore exact at 3 px per pt: Apple Developer (1320 × 2868 px = 440 × 956 pt, iOS 26) and Shortcuts (1242 × 2208 px = 414 × 736 pt). The cited frames are listed with their URLs in §12 (app:n= the n-thscreenshotUrlsentry, from 0). - Scale. The screen is found as the widest run of the screen's background colour through the frame centre; pt = px ÷ (screen px ÷ 390), i.e. a 390 pt-wide screen is assumed (the same assumption as lane K). A 393, 402 or 430 pt capture reads 1–10 % low. Three cross-checks against published tokens support the assumption: Toss text 23.5–24.0 (TDS 24), 당근 16.5 (Seed 16), Airbnb 24.1 (24).
- Margins are read from block edges (images, cards, chips, avatars, hairlines: runs of at least 14 pt) as left and right peaks, so a sideways error in the screen bounds cancels. Text first-ink is used only on text-only screens (it overstates by the side bearing, 0.5–1.5 pt). Error: about ±1 pt.
- Vertical gaps are ink to ink on a left column band, same scale.
- String widths are advance sums in Pretendard 1.3.9 (Regular/SemiBold, jsDelivr), no kerning, about ±2 %. Visual angle assumes 1 pt = 0.16 mm.
- English date and time strings are
Intloutput of Node 22.22 (ICU 77.1, CLDR 47).
At a glance
| # | Owner point | Verdict |
|---|---|---|
| 1 | Leading markers | The owner's rule is right and Apple's and Seed's practice agree with it when it covers the whole list (the slot is reserved on every row, so the text edge never moves). Headings never take a slot. Dot 8 (not 6), slot 16, centred on line 1. The hung "today" dot goes. |
| 2 | Space above headings | It is Gestalt proximity: the heading belongs to what is under it (NN/g). Eleven measured headings in six products keep 15–20 below (median 16.8) and 24–55 above (median 33.8), ink to ink. Ours is 36.6 above (fine) and 12.6 below (the outlier): change 8 → 12 below. The peers' section titles are also larger than ours (A9). |
| 3 | Type scale | 15/22 is below every body reference for reading text (KRDS ≥ 16, M3 16/24, Seed 16/22, Apple 17; Toss's primary text is 17); it stays right for single-line UI text. Add a 16/24 reading role, raise tab labels 11 → 12, make wrapping text ≥ 1.5, raise the label cap 1.5 → 2.0. |
| 4 | Side margin | There is no single standard. Platform: 16 (Material, Seed, iOS ≤ 375 pt) and 20 (iOS ≥ 390 pt, measured 20.00 at 414 and 440 pt); Toss 24. Measured third-party apps are bimodal at 16 and 24. Keep 20 at 390–440 pt, 16 below 390, 24 from 600; it does not change between 390 and 430. |
| 5 | Blocks | Left-aligned; zones eyebrow → figure → meta; the affordance is one chevron on the eyebrow row; no buttons inside; one state table for all five blocks. |
| 6 | Strip | Left-aligned equal-pitch columns (today's pitch is 133 / 116: unequal), label above figure (same order as the Block and the spoken order), air instead of hairlines, 2–4 columns, reflow 2-up when a figure does not fit. |
| 21 | English | Our corpus measures EN ≈ 1.33× KO width (median), 1.59× for labels of ≤ 4 syllables. The clutter comes from row L2 (368 px vs 273 px) and long Tag copy, not from dates. Write by role budgets, truncate the flex segment, never the fixed tokens. |
| 22 | Motion and accessibility | Keep the timing set and tokens (they equal M3 and Seed). Change Reduce Motion to replace with a short fade, not remove. Missing: WCAG 2.5.7 dragging alternatives, 1.3.4 orientation, the OS contrast and cross-fade settings, the App Store accessibility-label checklist. |
1. Leading markers: status, unread and live dots (owner point 1)
"When there is a dot in front, we are losing the gap in front. Make sure that we don't lose the front gap, across all components, even if we add the status dot. So everything gets pushed back."
What we have
- Day header (library §3, ruling L2 in §13): a 6 px "today" dot (ball fill, 1 px ink edge) hung in the gutter at x 8–14 so the date stays on the 20 rail.
- Unread marker (§18.2, ruling D1): a 6 px danger dot hung at x 8–14, centred on line 1, with "읽지 않음" prefixed to the spoken label.
- Both put ink inside the 20 pt margin. Two components already follow the owner's rule: the Select-mode checkbox (24 at the rail, content shifted 36, §14) and the LiveScore serving dot (6 + 8 inside the block padding, §14).
Evidence
How references mark "new", "unread" or "live".
| Reference | Marker | Where it sits | Evidence |
|---|---|---|---|
| Gmail (iOS) | none: unread = bold sender and subject; category rows show a trailing count pill ("2 new") | avatar 40 on the left, time and star trailing | Measured, gmail:1 |
| Slack | bold name plus a trailing purple count pill; presence = dot on the avatar's bottom-right corner | trailing, avatar corner | Measured, slack:1 |
| KakaoTalk | trailing time and a red/orange count pill; a count badge beside the tab label | trailing | Measured, kakaotalk:3 |
Toss (TDS Tab) | 6 × 6 #f44336 dot ("redBean") | top 4, right 0 of the tab label | Code, @toss/tds-mobile 2.5.1 (npm) |
Seed (당근) NotificationBadge, List | small = 6 px dot; large = min 18 × 18, label 11/15 bold, ≤ 4 characters; unread rows use List "Highlighted" | top-right of an icon or tab; row emphasis, not a gutter dot | Doc, notification-badge, spec JSON, list |
Material 3 Badge | small 6 dp; large 16 dp (label small 11/16) | top-end corner of the icon | Code, BadgeTokens.kt |
| Apple HIG, tab bars | red oval with a number or "!"; "Reserve badges for critical information" | on the tab | Doc, Tab bars |
| Apple Mail, Messages | blue leading dot for unread | leading edge, text column indented for every row | Observed practice; geometry Unverified (Apple's pages give none; no raw capture exists in the store sets) |
None of the measured Korean apps hangs a dot in the margin. The three working families are a leading dot in a reserved column (Apple), a trailing count (Kakao, Slack, Gmail categories), and an icon-corner dot or row emphasis (Material, Seed, Toss).
Principles the rule rests on.
- Alignment: "Align elements to make them easier to scan, and use indentation to convey hierarchy… People assume that aligned items are related to each other, and conversely, they perceive indented items as subordinate" (HIG Layout). A text edge that jumps between rows of one list reads as hierarchy that does not exist.
- Seed's List guideline says the same as a Don't: "내부 간격을 조정할 때 각 Item의 정렬은 일관되어야 합니다" (keep the alignment of each item consistent), with an image of items aligned differently inside one list (list). Seed also warns against list padding narrower than its 16 global gutter.
- Even Apple's own apps keep ink off the margin: first ink sits at exactly 20.00 pt in 3 of 4 raw Apple Developer frames (1,199–1,431 rows) and in both Shortcuts frames (724–769 rows); inset-grouped cards start 20.0–20.3 pt from each edge (Measured, appledev:0, 1, 3, shortcuts:0, 2).
- Non-text contrast (WCAG 1.4.11, 3:1): the danger dot is 5.91:1 on white; the ball-coloured today dot is 1.28:1, which is why it needs its ink edge (Derived, Understanding 1.4.11).
- Colour is not the only cue: Apple's Differentiate Without Color Alone criteria accept shape and text cues; a dot that is present or absent is a shape cue, and the spoken prefix is the text cue.
- Size: a 6 px dot is 0.96 mm, 8.3 arcmin at 40 cm; reading speed for older adults falls inside a 9–18 arcmin "critical print size" band (systematic review, 12 studies). 8 px is 11.0 arcmin. Peers use 6 only when the dot rides on an icon or tab, where the icon makes it findable; as a row's only cue it should be larger (Derived).
Recommendation: the leading-marker rule
| # | Rule | Value | Why |
|---|---|---|---|
| LM1 | Keep-out | Nothing except full-bleed surfaces (photo, band, hairline, block fill) enters x < rail or x > 390 − rail. No dot, glyph, tag or text in the margin. | The owner's rule; Apple's own apps measure 20.00 exactly. |
| LM2 | The slot | A leading marker takes the first slot of the row, starting on the rail. Slot = marker + gap: dot 8 + 8 = 16 (content edge x 36); checkbox 24 + 12 = 36 (existing); rank column 24 / 32 / 40 + 12 (existing, §20.2). | One ladder for every leading element; the content edge shifts right by the slot. |
| LM3 | Whole list | The slot is reserved on every row of a list that can show the marker, so all text edges match. A list that never shows the marker has no slot (no empty gutter). While Select mode is on, every row's slot is the checkbox slot (36) and the dot is hidden (the state stays in the spoken name). | HIG alignment, Seed List Don't. Shifting only the marked rows would zig-zag the text edge. |
| LM4 | Headings take no slot | Day header, section head and page title text stays on the rail. The today dot is dropped: "오늘" is already the relative-day label (ink, 600). If a mark is ever wanted it follows the date, 8 after, never precedes it. | A heading at x 36 sits off the thumbs below it; the dot adds nothing the label does not say. |
| LM5 | Size and colour | 8 px circle for a row marker (6 px stays for dots that ride an icon or a tab: tab bar, header badge). Unread = danger #B8332E (5.91:1). Do not use brand ball as a dot fill. | Visual angle; peers' 6 px is icon-attached; 3:1 non-text contrast. |
| LM6 | Vertical alignment | Centre on the first line's centre (L1 17/24: 12 from the text-box top; 15/22: 11), top rounded to the 4-grid (8). Not centred on the row. | A 3-line row keeps the dot beside line 1, where the eye starts. |
| LM7 | With avatars, thumbs | Order: [marker 8][8][avatar 40][12][text] (text edge 88, was 72); [marker][8][thumb 56][12][text] for media rows. Presence of a person (online) is a different marker: a 10 px dot on the avatar's bottom-right with a 2 px canvas ring (Slack), not a slot. Counts stay trailing (Tag, §6). | Keeps the avatar on the grid; separates "state of the row" (slot) from "state of the person" (corner). |
| LM8 | Screen reader | The dot is hidden from assistive tech; the row's one accessible name begins "읽지 않음, …" (existing D1). No live-region announcement when the dot clears. Under text scaling the dot and gap stay 8 / 8; the dot follows line 1's centre at the scaled line height. | WAI guidance: state before content; B8.1 one stop per row. |
Cost of the slot. 16 pt of text width on rows that carry a marker (4.6 % of 350). That is why the slot is not added to lists that never show a marker, and why counts remain trailing.
What changes
- Components: Day header (remove the hung dot; spacing in §2); Unread marker (§18.2: 6 → 8, hung → slot); Select mode (§14: record the slot ladder); LiveScore serving dot (§14: use the 8 + 8 slot on both team rows).
- Boards: components (day header and unread specimens), list-a (notifications), patterns (LiveScore, Select mode), additions.
- Gate: a board check "no non-full-bleed element has first ink left of the rail", with the current hung dot as the known-positive canary.
2. Space above headings and the heights of bars (owner point 2)
"And why are we adding the extra padding on top for headers?"
What we have
- Section head (library §3): title 17/24·600, action 13/18·500 + chevron 16; 32 above, 8 below (the edge rule trims the first row's top padding). In ink terms (Derived: a 17/24 Hangul line has its ink 4.6 pt below the box top and 4.7 above its bottom): 36.6 above, 12.6 below.
- Day header: a 32-tall box, 15/22·600 date, 16 above → ink 25.5 above and 9.5 below.
- Header 56 under the status bar; tab row 44 with 24 (title, day header) or 16 (row) below it; Band 12 with 24 above and below.
Why the space above is larger (the plain answer)
A heading belongs to what is under it. If the gap above and below were equal, the heading would float between two groups and the eye could attach it to either. The space above separates it from the previous group; the space below binds it to its own. That is the Gestalt law of proximity, and Nielsen Norman Group states the heading case directly: "the text from the corresponding section is usually placed closer to the heading than the text from the preceding section" and "Using varying amounts of whitespace to either unite or separate elements is key to communicating meaningful groupings" (NN/g, Proximity). HIG says the same about grouping by "negative space" (Layout). KWCAG 5.4.4 lists "line and letter spacing" among the accepted ways to keep neighbouring content distinguishable (KWCAG 2.2, 5.4.4). Equal gaps would not be "cleaner"; they would remove the grouping.
Evidence: how much (Measured, ink to ink, pt at 390)
| Reference | Heading ink height | Gap above | Gap below | Above ÷ below | Frame |
|---|---|---|---|---|---|
| Apple Developer (iOS 26, raw 440 pt) "Recent videos ›" | 17.0 | 24.3 | 17.0 | 1.4 | appledev:0 |
| same, "Revisit WWDC25" | 17.0 | 25.0 | 17.0 | 1.5 | appledev:0 |
| Toss 증권 홈 "내 계좌" (under the tab row) | 16.5 | 30.5 | 17.6 | 1.7 | toss:2 |
| Toss 증권 홈 "내 투자" | 16.9 | 55.1 | 16.5 | 3.3 | toss:2 |
| Airbnb "Recently viewed homes ›" | 17.4 | 31.7 | 15.7 | 2.0 | airbnb:0 |
| Airbnb "Stay in Mexico City ›" | 17.1 | 33.8 | 16.0 | 2.1 | airbnb:0 |
| 당근 알바 "서초동 이웃들이 많이 찾는 알바" | 16.5 | 51.7 (band inside) | 15.1 | 3.4 | daangn:4 |
| 당근 중고차 "지금 인기있는 차" | 18.7 | 54.0 (band inside) | 20.1 | 2.7 | daangn:6 |
| 무신사 "최근 쇼케이스" (under the top tabs) | 17.2 | 27.9 | 16.8 | 1.7 | musinsa:4 |
| 무신사 "최근 매거진" (after a white card) | 17.2 | 40.9 | 16.8 | 2.4 | musinsa:4 |
| NOL "숏폼으로 떠나는 해외여행" (after a carousel) | 17.9 | 54.1 | 17.9 | 3.0 | yanolja:6 |
| Peers, median (range), n = 11 | 17.1 (16.5–18.7) | 33.8 (24.3–55.1) | 16.8 (15.1–20.1) | 2.1 (1.4–3.4) | |
| Lane K, section → section whitespace, 7 screens | — | median 36 (27.7–55.8) | — | — | lane K §3 |
| Ours today | 14.7 | 36.6 | 12.6 | 2.9 | Derived from §3 |
Eleven measured headings in six products (seven of them Korean) keep 15.1–20.1 between the heading's ink and the content it opens (median 16.8, middle half 16.0–17.6) and 24–55 above it (median 33.8). Ours is inside the range above (36.6, a little over the median) and about 4 pt short below (12.6 against 16.8). The Korean flat feeds sit at the long end above (41–55 when a band, a card, tiles or a carousel is above; 28–31 under a tab bar) and keep the same 15–20 below. Lane K's separate set of seven section gaps (27.7–55.8, median 36) and its title → first row figures, 19.1 (카카오톡; 16.3 to its avatar strip) and 27.6 (Toss 혜택), agree. Under a tab bar the first heading sits 27.9 below it (무신사) or 30.5–31.2 (Toss 증권, mine and lane K's); ours is 24 + 4.6 = 28.6, so the 24 stays.
The 당근 seam, twice. Both 당근 frames show the same seam: 19.7–21.5 of air, an 8 pt band (7.8 measured), then 24.2–24.7 to the heading's ink. With 카카오페이 (lane K: 33 / 13.1 / 37) that makes two measured band screens; our Band (12 tall, 24 of air on each side) is between them in thickness.
Heading size. The peers' headings are larger than ours: ink 16.5–18.7 (median 17.1) against our 14.7 (17 px Hangul). For the Hangul-only headings, ink ÷ 0.868 gives about 19–21.5 px. That is a size question, not a spacing one, and it is audit item A9 (§9); the spacing below holds for either size.
Published values.
| Source | Above / below | Evidence |
|---|---|---|
Toss TDS ListHeader (medium) | padding top 24 / bottom 8, gap 4 to a description; (small) 16 / 4 / 0; side 24 (medium) or 20; title t5 17/25.5 bold, right link t6 15 | Code, @toss/tds-mobile 2.5.1 (ListHeader: vertical padding, horizontal padding and size maps) |
Seed ListHeader | paddingY 8 / 8, paddingX 16 (global gutter), gap 10; 14/19 medium, weak colour or 700 solid | Doc, list-header.json (a symmetric box; any asymmetry comes from the layout around it) |
| Material 1 list subheader | a 48 dp tile, Roboto Medium 14 sp, left-aligned with the avatar or icon (symmetric inside the tile; the previous item's own padding makes the sum asymmetric) | Doc, m1 Subheaders |
| Material 3 | no subheader token; list item vertical padding 8 (12 for three lines) | Code, ListItem.kt |
| UIKit grouped and plain tables | iOS 15 added sectionHeaderTopPadding, default "automaticDimension"; developers report 22 pt of extra space above plain headers | Doc, UITableView.sectionHeaderTopPadding; the 22 pt figure is community-reported, not in Apple's text |
| HIG | "the grouped style uses headers, footers, and additional space to separate groups of data" | Doc, Lists and tables |
Bar heights (the owner's second question).
| Bar | Value | Evidence |
|---|---|---|
| iOS navigation bar | 44 pt (64 pt with the 20 pt status bar, iOS 7 era; iOS 26 not documented) | Doc, Apple archive, Transition Guide |
| Material 3 small top app bar | 64 dp | Code, AppBarSmallTokens.kt |
| Seed top navigation | 56 px; title t6 18/24 or t5 16/22, subtitle t2 12/16 | Code, top-navigation.json |
| Ours | 56 under the status bar | between iOS 44 and M3 64, equal to Seed: keep |
Recommendation
| Where | Today | Recommended | Result (ink) |
|---|---|---|---|
| Section head, below | 8 | 12 (the row-padding step) | 12.6 → 16.7 (peers 16.8) |
| Section head, above | 32 | 32 (unchanged) | 36.6 (peers 33.8; 24–55) |
| Day header | 32-tall box, 16 above | a 22 line box (15/22·600) with 24 above, 12 below, no fixed height | 25.5 / 9.5 → 28.5 / 16.5 |
| Tabs → first heading | 24 (title or day header), 16 (row) | unchanged, but the heading's own above-space is the seam (not additive) | 28.6 (peers 27.9–31.2) |
| Header, Band | 56; 12 with 24 / 24 | unchanged | — |
The rule to keep: the ink gap above is about twice the gap below (peers' median ratio 2.1) and the gap below is about 16 (peers' median 16.8). It reads from the peers, not from our ladder.
What changes
- Components: Section head (§3, grammar §3 "title → first row 8" becomes 12, §4 "32 above, 8 below" becomes 32 / 12); Day header (§3 and §13 L2).
- Boards and numbers: every screen with a section head moves its first row down 4 pt per section; day-header lists gain 10 pt per day. Re-measure the 홈, 경기, 기록 and 프로필 screens (grammar §5 totals).
- If the owner adopts D7 (section title 20/28, audit A9): the Section head is 28 tall instead of 24 and each section moves down a further 4 pt; the box spacing (32 above, 12 below) does not change, and the ink gaps become 37.3 and 17.3, still inside the peer ranges.
3. Type scale against accessibility guidance (owner point 3)
"As for the type scale, can you ensure that we are following recommended accessibility guidelines?"
What we have
Grammar §2: display 40/48·700 · hero 24/32·700 · title 20/28·700 · headline 17/24·600 · body 15/22·400/500 · footnote 13/18·500/600 · caption 12/16·500; tab-bar labels and count tags 11/14. Behaviour addendum B6: caps reading 2.0, hero 1.7, numeral 1.5, label 1.5, chrome 1.3; every text-bearing height is a floor.
Evidence
What each source asks for.
| Source | Default / body | Smaller steps | Floor | Line height | Evidence |
|---|---|---|---|---|---|
| Apple HIG (iOS, Large) | Body 17/22 | Callout 16/21, Subhead 15/20, Footnote 13/18, Caption 12/16 | default 17, minimum 11 ("follow the recommended default and minimum text sizes") | Body 1.29 | Doc, Typography (JSON mirror: typography.json) |
| Material 3 | Body large 16/24 | Body medium 14/20, small 12/16; label small 11/16 | Android: "Don't make the body size any smaller than 12 sp" | 1.50 / 1.43 / 1.33 | Code, TypeScaleTokens.kt; Doc, Android accessibility |
| Toss TDS | t5 17/25.5 (primary), t6 15/22.5 | t7 13/19.5, st12 12/18, st13 11/16.5 | 11 | 1.5 up to 18 px, 1.33 at 30 | Code, @toss/tds-mobile 2.5.1 (type map) |
| Seed (당근) | t5 16/22 | t4 14/19, t3 13/18, t2 12/16 | t1 11/15 | 1.33–1.38 | Doc, typography |
| KRDS (Korean government design system, Pretendard GOV) | body medium 17 (default); large 19 | small 15, xsmall 13 | "본문 폰트 크기는 … 최소 16px 이상" | "최소 150% 이상", citing WCAG 1.4.12 | Doc, KRDS typography; the page says Pretendard GOV defaults to 17 px because it looks smaller than other typefaces (paraphrase of the page text) |
| WCAG 2.2 | — | — | none (1.4.4: 200 %; 1.4.10: reflow at 320 px) | 1.4.12: nothing may break at 1.5 / 2 / 0.12 / 0.16 | Doc, 1.4.12 (AA), 1.4.4, 1.4.10 |
| KWCAG 2.2 (KS X OT0003:2022) | — | — | no minimum font size; contrast 4.5:1 (3:1 for ≥ 18 pt or 14 pt bold) | — | Doc, KWCAG 2.2 5.4.3; 6.1.3 controls ≥ 6.0 mm diagonal (≈ 27 dp square; ours 44 passes) |
| Ours | 15/22 | 13/18, 12/16 | 11/14 | 1.47 / 1.38 / 1.33 / 1.27 | Grammar §2 |
Reading of the table. Every body reference puts reading text at 16–17. 15 is the secondary step in Toss (t6), Apple (Subhead) and KRDS (small). So 15/22 is a correct size for a single-line row subtitle and the wrong size for text people have to read (comments, notices, dialogs, descriptions, inputs). KRDS adds that its Pretendard GOV looks smaller than other typefaces, which is why its default is 17.
Pretendard's metrics (Derived from the 1.3.9 font files, units per em 2048):
| Metric | Value | Consequence |
|---|---|---|
| Hangul syllable advance | 1770 = 0.864 em | a 350 pt column holds 23.8 syllables at 17 px, 25.3 at 16 px, 27 at 15 px |
| Hangul ink height | −162…1616 = 0.868 em | 15 px text has 13.0 px of ink; 11 px has 9.5 px |
| x-height / cap height | 0.530 em / 0.707 em | the Latin in English mode is smaller than Hangul at the same size (x-height 7.9 px at 15 px) |
| hhea ascent, descent, gap | 1950, −494, 0 = 1.193 em | line-height: normal is tight for Hangul; set it explicitly |
Line height for Hangul. The white between two Hangul lines is line height − 0.868 em:
| Style | Ratio | White between lines | Style | Ratio | White between lines |
|---|---|---|---|---|---|
| tab 11/14 | 1.27 | 0.40 em | Apple body 17/22 | 1.29 | 0.43 em |
| caption 12/16 | 1.33 | 0.47 em | ours body 15/22 | 1.47 | 0.60 em |
| footnote 13/18 | 1.38 | 0.52 em | Toss t6 15/22.5 | 1.50 | 0.63 em |
| headline 17/24 | 1.41 | 0.54 em | KRDS 17/25.5, M3 16/24 | 1.50 | 0.63 em |
Our small steps leave 0.40–0.52 em, against 0.60–0.63 em in Toss, KRDS and M3. WCAG 1.4.12 does not require an author to set 1.5; it requires that the layout survive it, and KRDS adopts 150 % as the design value. Research on spacing is weak: it found no significant effect of line or letter spacing on Hangul legibility in older readers (below), and only the extremes (0.8 and 1.8) hurt on the web (Rello et al., CHI 2016, abstract-level). So the rule is "≥ 1.5 wherever text can wrap", not a larger scale.
Legibility research for older adults (the audience is 40–60).
| Study | Result |
|---|---|
| Jeong 2016, J. Korea Contents Association: Hangul on smartphones, 24 adults over 65, 11 / 12 / 13 pt | 13 pt was more legible than 11 or 12; larger sizes were preferred in the order 13, 12, 11; letter and line spacing had no significant effect. (The page does not state the device, so the "pt" is the study's own unit.) |
| Systematic review, Frontiers in Psychology 2022, 12 studies | older adults prefer larger sizes but "bigger may not always mean better"; reading speed drops sharply below a critical print size of about 0.15°–0.3° (9–18 arcmin); recommends reporting visual angle. |
| Rello, Pielot, Marcos, CHI 2016 (104 readers, web) | readability and comprehension rose with size; 18 pt and larger recommended; spacing effects weak. |
No study fixes a Hangul minimum. The visual-angle view puts the sizes in context (Derived; glyph height 0.868 em, 1 pt = 0.16 mm):
| Size | 11 | 12 | 13 | 15 | 16 | 17 |
|---|---|---|---|---|---|---|
| arcmin at 35 cm | 15.0 | 16.4 | 17.7 | 20.5 | 21.8 | 23.2 |
| arcmin at 40 cm | 13.1 | 14.3 | 15.5 | 17.9 | 19.1 | 20.3 |
| arcmin at 45 cm | 11.7 | 12.7 | 13.8 | 15.9 | 17.0 | 18.0 |
At 40 cm, 11–13 px sit inside the 9–18 arcmin speed-loss band and 15 px sits at its upper edge; 16–17 px clear it. This is an indicator, not a standard.
Text scaling caps against Toss's published table (Code: the Nf function and the cap array in @toss/tds-mobile; at 200 % the scaled px are min(size × 2, cap)):
| Size (px) | Toss cap | Toss at 200 % | Our class, cap, at 200 % |
|---|---|---|---|
| 11 | 31 | 22 | chrome 1.3 → 14.3 |
| 13 | 34 | 26 | label 1.5 → 19.5 |
| 15 | 37 | 30 | reading 2.0 → 30 |
| 17 | 39 | 34 | reading 2.0 → 34 |
| 20 | 40 | 40 | header title is chrome 1.3 → 26 |
| 24 | 40 | 40 (1.67×) | hero 1.7 → 40.8 |
| 30 | 42 | 42 (1.4×) | numeral 1.5 (24 → 36, 40 → 60) |
Reading, hero and numeral classes match Toss's philosophy (large sizes compress, small sizes reach 2×); Toss's tab component also stops at 140 %. Our label class (13 px) is the outlier: Toss lets 13 px reach 26 px; we stop at 19.5. A Tag carries state ("입금 필요"), so it is content, not chrome; Apple's Larger Text criteria ask content text to reach 200 %.
Colour of text (Derived; Seed's APCA thresholds: readable text Lc ≥ 75 with Lc ≥ 90 recommended, other text Lc ≥ 60 and bold below 16 px, inclusive design):
| Pair | WCAG | APCA Lc | Reading |
|---|---|---|---|
ink #0D100E on white | 19.1:1 | 105.5 | passes everything |
secondary #4F5851 on white | 7.38:1 | 85.6 | passes (Seed's 75; short of 90) |
secondary on gray100 #F2F2F2 | 6.59:1 | 77.9 | passes 75 |
tertiary #626C64 on white | 5.46:1 | 77.3 | passes 75 |
| tertiary on gray100 | 4.87:1 | 69.5 | below 75: keep "≥ 16 px or bold only" |
white α0.8 on block #15181A (eyebrow) | 11.65:1 | 77.5 | passes |
Colour is not the problem; size is.
One web finding. Safari on iOS zooms the page when an input's text is below 16 px: "If the font-size of an <input> is 16px or larger, Safari on iOS will focus into the input normally" and at 15 px or less it zooms (CSS-Tricks). Our Input text is 15/22. On apps/web that is a defect, independent of the accessibility argument.
Recommendation: the scale and its minimums
| Role (size/line · weight) | Ours | Recommended | Change | Why |
|---|---|---|---|---|
| display (numeral) | 40/48·700 | 40/48·700 | none | Seed t13 40/52; numerals only |
| hero | 24/32·700 | 24/32·700 | none | M3 headline small 24/32, Seed t9 24/32 |
| title | 20/28·700 | 20/28·700 | none | Toss t4 20/29 |
| headline (row L1, button lg, section title) | 17/24·600 | 17/24·600 | none | Toss t5, KRDS default 17; a 1–2 line headline tolerates 1.41; the section title is the open question A9 / D7 |
| reading (new role: comment, post body, dialog and notice body, toast, terms, descriptions, input and textarea text) | (15/22·400) | 16/24·400 (500 for emphasis) | new | KRDS "≥ 16", M3 body large 16/24, Seed t5 16, iOS web input zoom; 1.5 ratio; keeps every height on the 4-grid (24) |
| body (single-line UI: row L2, cell label and value, button md / sm, tabs) | 15/22·400/500 | 15/22·400/500 | none | Toss t6, Apple Subhead, KRDS small |
| footnote (Tag, eyebrow, section action, helper) | 13/18·500/600 | 13/18 when one line; 13/20 when it can wrap (helper, caption row, meta) | line height | KRDS xsmall 13/19.5, Toss t7 13/19.5; Jeong 2016: ≥ 13 |
| caption | 12/16·500 | 12/18, only for non-essential text (chart ticks, input counter, timestamps) | line height | 12 is the floor on Android and below Jeong's 13; Hangul ink 10.4 px |
| tab-bar label | 11/14·500/600 | 12/16·500/600 | +1 | M3 navigation bar label is Label Medium 12/16 (NavigationBarTokens); Android floor 12 sp; two-syllable Hangul labels |
| count tag numeral (digits only) | 11/14·700 | 11/16·700 | line height | M3 badge label small 11/16, Seed 11/15 bold; digits, not Hangul |
Minimum size per role. Reading text 16 · primary single-line UI text 15 · informational labels (Tag, eyebrow, action, helper) 13 · non-essential text 12 · tab-bar labels 12 · digits inside badges 11 bold. Nothing in Hangul below 12. Weights: 400 only at 15 and above; 500 or heavier at 13 and below (HIG: prefer Regular, Medium, Semibold or Bold; Seed: bold below 16 px at Lc 60).
Scaling. Add the reading role to B6.2's "reading" class (cap 2.0). Raise the label class cap from 1.5 to 2.0 for Tag, Choice and button labels (Tag keeps minHeight 24 and grows); keep chrome at 1.3, numeral at 1.5, hero at 1.7. Tag at 200 % then reads 26 px beside 30 px reading text.
WCAG and KWCAG mapping after the change. 1.4.4 resize: 200 % holds for every content role; 1.4.10 reflow: the 320 frames in B6; 1.4.12 (web only): heights are floors, check overflow: hidden on nowrap Tags and Choices with a text-spacing override; 1.4.3 contrast: table above; KWCAG 5.4.3 and 6.1.3: pass.
What changes
- Documents: grammar §2 (add the reading role, floors, line heights) and §3; library §1 (type line), §3 (tab bar label 12/16), B6.2 (label cap).
- Components: PostRow and Comment bodies, Dialog and Notice bodies, Toast text, Input and TextArea text, Document sheet paragraphs, Caption row and helper text (13/20), Tab bar label, count tag line height. Heights stay on the grid: Notice and Toast keep 48 (12 + 24 + 12); Input stays 48.
- Boards: B6 frames (re-measure Notice, Toast, Input, Dialog at 100/130/150/200 %); add the 12/16 tab bar.
- Gates:
no-text-below-12,no-400-below-15(ESLint ontextRole), and the label-cap parity test intext-scale.ts.
4. The side margin: is 20 the baseline? (owner point 4)
"We seem to be using 20 as the baseline. Is that the standard baseline? I thought it was like 16 or 24."
What we have
Rail 20 (grammar §1, ruling C10), content 350 at 390 pt, justified as "Apple grouped-list inset 20; Toss 24; Seed 16". Block padding equals the rail; the 4-up roster, 7-day calendar (7 × 50) and 3-up strip are derived from 350.
Evidence
Platform and design-system values.
| Source | Value | Evidence |
|---|---|---|
| UIKit root-view layout margin | 16 on iPhone 8 (375 pt), 20 on iPhone 11 (414) and iPhone 12 (390); 64 in landscape | Doc, Apple forum 697910 (reported layoutMargins.left 16 vs 20; an Apple Frameworks Engineer explains the system minimum margins); monday.com engineering; Use Your Loaf ("16 points for compact width and 20 for regular", iOS 11 era) |
| iOS inset-grouped lists | the table's layoutMargins: "The horizontal insets for UITableView (and UICollectionView lists) using .insetGrouped come from the layoutMargins" | Doc, Apple Frameworks Engineer in the same thread; Apple's property page gives no number (systemMinimumLayoutMargins); HIG Layout gives none either |
| Apple's own apps, raw captures | 20.00 pt at 414 pt (Shortcuts) and at 440 pt (Apple Developer, iOS 26); inset-grouped cards 20.0–20.3 from both edges | Measured: shortcuts:0, 2 (724–769 rows), appledev:0, 1, 3 (1,199–1,449 rows), appledev:4 cards |
| Material 1 | phones 16 dp, tablets 24 dp, baseline 8 dp | Doc, m1 metrics & keylines |
Material 3 ListItem | start and end padding 16 dp (vertical 8; 12 for three lines) | Code, ListItem.kt |
| Seed (당근) | global gutter 16; list padding not narrower than it; web grid margin 12 (< 768 px), 24 (≥ 768) | Doc, spacing, layout, list |
Toss TDS ListHeader, ListRow | horizontal padding 24 (medium, default) or 20 (small) | Code, @toss/tds-mobile 2.5.1 |
| Android design guide | 8 dp grid; "smaller elements … a 4 dp grid" | Doc, grids and units |
Correction to the citation. "Apple grouped-list inset 20" holds from 390 pt up; at 375 pt Apple's own margin is 16. "Toss 24" is the TDS default, with 20 as its documented "small" and 16 for the cards and tiles measured in the app. "Seed 16" is right.
Measured peers (pt at an assumed 390 pt screen, one row per app; "edge" = what was measured; §12 has the frames):
| App | Frame | Margin | Edge |
|---|---|---|---|
| 당근 | daangn:1 | 16.5 | list and photo edges (791 rows); Seed token 16 |
| 배달의민족 | baemin:2 | 15.8 | thumbnails, labels |
| 네이버 | naver:5 | 15.6 / 15.6 | cards, left and right |
| 네이버지도 | navermap:2 | 15.0 | place photos (883 rows) |
| 올리브영 | oliveyoung:7 | 16.6 / 15.2 | cards |
| NOL(야놀자) | yanolja:6 | 17.1 / 17.5 | search box, banner |
| 무신사 | musinsa:1 | 16.8 / 16.4 | cards |
| Spotify | spotify:0 | 15.9 | tiles, headings |
| Slack | slack:1 | 16.7 / 16.0 | list rows, pills |
| Wise | wise:3 | 16.4 | cards |
| TestFlight (Apple) | testflight:2 | 15.2 | list edge (mock-up; device width unknown) |
| 플랩풋볼 | flab:1 | 19.5 / 19.5 | banner, date chips (781 rows) |
| Apple Developer (raw) | appledev:0 | 20.00 | title, cards, headings (1,449 rows), 440 pt |
| Shortcuts (raw) | shortcuts:0 | 20.00 | 414 pt |
| Apple Podcasts, Store, Fitness | applepodcasts:3, applestore:3, applefitness:3 | 19.1–20.7 | tiles, cards (mock-ups) |
| Toss 증권 홈 | toss:2 | 24 (text 23.5–24.0); tiles 20.2 | text, account tiles (TDS 24) |
| Toss 쇼핑 | toss:3 | 15.8 / 15.8 | product cards (445 rows) |
| 카카오뱅크 내 계좌 | kakaobank:2 | 24 (23.6–24.8); hairline 19.5 / 19.9 (lane K) | text, rows |
| 카카오뱅크 홈 | kakaobank:0 | 16.4 / 16.4 | cards (254 rows) |
| KB스타뱅킹 | kbstar:4 | 25.0 / 25.0 | cards (592 / 452 rows) |
| 카카오페이 | kakaopay:2 | 22.4 / 22.9 | cards, button |
| 문토 | munto:4 | 25.7 | posters, chips (719 rows) |
| Airbnb | airbnb:1 | 24.1 / 24.1 | photo cards (975 / 995 rows) |
| Strava | strava:5 | 24.5 / 25.5 | feed card (dark mock-up) |
| Nike Run Club | nrc:1 | 24.9 | stats, text |
Counting each app once (25 apps; Toss and KakaoBank by their page text rail): 16-class (15.0–17.3): 11 apps · 20-class (19.1–20.7): 6 (five are Apple's own apps) · 22–26-class: 8. Median 19.3 and mean 19.6 sit in the gap between two clusters: the distribution is bimodal at 16 and 24, with 20 owned by Apple (Apple's TestFlight reads 15.2 on a mock-up of unknown device width, so it is not a clean exception). Lanes H and K measured the same (Cash App 15.7, Monzo 16.2, 당근페이 15.5, 카카오뱅크 15.9; hairline insets 카카오뱅크 19.5 / 19.9, 네이버 19.2 / 21.6, 테니스프렌즈 19.5 / 20.5).
By product kind. Dense thumbnail feeds, commerce, maps and utilities use 16 (당근, 배민, 네이버, 올리브영, 무신사, Spotify, Slack). Finance and editorial or premium products use 24 (Toss, 카카오뱅크, KB, 카카오페이, Airbnb, Strava, NRC, 문토). The football-match peer 플랩풋볼 uses 20.
Answer and recommendation
20 is not "the standard baseline"; there is none. The standards are 16 (Material, Seed, iOS up to 375 pt) and 20 (iOS from 390 pt, measured exactly in Apple's apps), and Korean products split between 16 and 24. Your recollection of "16 or 24" matches the market. 20 is defensible for a reason that is not the median: it is the one value that lies inside every family's own range (Apple 16–20, Toss 20–24, Seed and Material 16 + 4).
| Option | Content at 390 | For | Against |
|---|---|---|---|
| 16 | 358 | Material, Seed, 11 of 25 measured apps; +8 pt text width (helps English rows and 200 % text) | denser; 4 pt narrower than Apple's own margin on iOS; every board and 350-based number is re-derived |
| 20 | 350 | Apple ≥ 390 pt (measured); Toss "small" and tile edge; within 4 pt of both clusters; no rebuild | not the market mode; 4 pt wider than Material |
| 24 | 342 | Toss text rail, 카카오뱅크, Airbnb; calmest | −8 pt width on already tight rows; re-derivation |
Recommended rule.
- 390–440 pt phones: 20. It does not change at 430 or 440 pt: Apple measured 20.00 at 414 and 440; Material's 16 holds to 599 dp; Toss and Seed are fixed. Only the content width grows (350 → 390 at 430): derive grids from the width (7 equal day cells, 4 roster cells), never from fixed 50 / 100 px.
- Below 390 pt (iPhone SE and mini, Android 360 dp): 16. This is Apple's own step (16 at 375 pt) and Material's value; it protects text width on the narrowest screens. One token,
SEAM.rail = width < 390 ? 16 : 20. - Tablets and web ≥ 600 pt: 24 (Material 1 tablet margin; Seed's 24 from 768 px) with the existing
ContentColumncap. - If the owner would rather follow the Korean-feed mode, 16 is equally defensible and the switch is mechanical; the evidence does not say 20 is wrong, so changing it is a taste call that costs a board rebuild.
What changes
- Documents: grammar §1 (rail rule and the corrected citation), library ruling C10 (
SEAM.railbecomes width-aware), the block row in grammar §4 ("padding 20" follows the rail). - Boards: none at 390; add a 360 frame (rail 16) to the shell specimens.
- Code:
SEAM.rail,ContentColumn(24 from 600), grid components computing from width.
5. One grammar for blocks (owner point 5)
"We need to define the grammar and behaviors across all blocks. What is the hierarchy of information here? Type scale and ratios? Left aligned? What gets on top, middle, and bottom for each block across different states?"
What we have
Five blocks, each specified on its own: the 홈 day block (350 × 144: eyebrow 13/18·600 α0.8 + chevron 16, numeral 40/48·700 ball with unit 15/22·600, meta 15/22·500 α0.85), the ELO block (160: adds an 8 progress bar and a caption), the identity block (avatar 56, name 24/32·700, level Tag, sub 15/22, edit icon), the LiveScore block (team rows, set scores 24/32·700, ball-coloured points, serving dot, host controls below) and the club hero, place variant (logo 56 overlapping the band, name 24/32·700, one MetaSegments line). Library §4, §14, §19.3. They share geometry (r16, pad 20) but no stated order of content, no common affordance rule and no state table.
Evidence
How references compose a "figure" block (Measured on frames; sizes are estimates from ink heights and the usual cap-height ratio, so ≈; where an estimate is not reliable the cell is qualitative).
| Reference | Top (eyebrow row) | Middle (figure) | Bottom (meta, actions) | Affordance | Align | Eyebrow : figure : meta | Frame |
|---|---|---|---|---|---|---|---|
| Toss 증권 홈 · 내 투자 | title ≈ 20 bold | "4,000,000원 ›" ≈ 34 bold | delta ≈ 15 in a semantic colour; then a controls row (13 gray, 32-high toggles) | chevron after the figure; no button inside | left | 20 : 34 : 15 (1 : 1.7 : 0.75) | toss:2 |
| Toss 홈 · 토스뱅크 and 최근 쓴 계좌 | title 17 bold + › at the right; group label 13 gray | rows: amount 17 bold, sub 13 gray | centred link row "내 계좌 · 대출 · 증권 › " | whole card; 32-high 송금 button at each row's end | left | — | toss:0 |
| 카카오뱅크 홈 | — | avatar 40 + amount bold + sub 13 gray | chips row 13 | 이체 pill 36 at each row's end | left | — | kakaobank:0 |
| Apple Health summary card | glyph + label ≈ 15 semibold in the category colour; time ≈ 14 gray + › | figure ≈ 30 bold + unit ≈ 15 gray, same baseline; chart or ring at the right | — | whole card, chevron on the top row | left | 15 : 30 : 15 (1 : 2.0 : 1.0) | applehealth:0 |
| Apple Health activity strip | — | three stats: coloured label above bold numeral + unit | — | 1 px dividers between content-sized columns | left | — | applehealth:0 |
| Apple Fitness | card title (small, gray) + chevron | ring + Move / Exercise / Stand (coloured label over numeral) | bar chart | whole card | left | — | applefitness:0 |
| Cash App · Money | "Cash balance" label + › | "$1,226.00" bold, about 2.4 × the label's ink height | two pill buttons (Add money, Withdraw) | chevron on the label; buttons in the bottom zone | left | figure ≈ 2 × label | cashapp:0 |
| Strava activity card | avatar, name bold, meta gray, "…" at the right | activity title | stat strip (label over larger value), photo, action row | card press; "…" is its own control | left | — | strava:5 |
| Monzo | wordmark left, balance right-aligned | account number | two icon chips | chips | split | — | monzo:0 |
| Wise, Revolut | — | balance centred | — | — | centred | — | lane H |
Lane H counted 12 blocks: left-stacked 8, centred 3 (balance-as-hero wallets), split 1; buttons inside: none in 2, one in 5, two or more in 5 (lane H §3). From the frames above:
- The affordance sits on the top row's trailing end in the pressable cards (Toss, Apple Health, Apple Fitness, Cash App), as a chevron or time + chevron. Buttons, when present, live in the bottom zone or at the end of a row.
- Figure : eyebrow is about 1.7–2.2 where it could be estimated (Toss 1.7, Apple Health 2.0, Cash App about 2); NRC's weekly hero is far higher (about 4). Our brand block's 3.1 (40 / 13) is a hero ratio, acceptable once per hub; the strip uses 1.85.
- The unit is a smaller sibling on the figure's baseline (Apple "steps" ≈ 0.5 × figure; ours "개 일정" 0.375 ×).
- Stale data states its age: Toss 증권 shows "16:50 기준" at the head of 내 계좌 (toss:2): the Korean precedent for our offline state.
- HIG: "place the most important items near the top and leading side" (Layout); and at large text "keep primary elements toward the top of a view even when the font size is very large" (Typography).
Recommendation: the block grammar
Hierarchy of information. Level 1 the figure (the answer); level 2 the eyebrow (what the figure is); level 3 the meta (the qualifier or next step). Reading order is eyebrow → figure → meta: question, answer, so-what. Always left-aligned on one text axis (8 of 12 peers; the centred wallets are the exception that proves the rule).
| # | Rule | Value |
|---|---|---|
| BG1 | Zones, top to bottom | A eyebrow row 13/18·600 α0.8 (18 tall, with the chevron at its trailing end) · 8 · B figure row 40/48·700 with the unit 15/22·600 on the baseline · 8 · C meta 15/22·500 α0.85 (a progress bar or graphic, when any, sits between B and C with 8 either side). Pad 20; min height 144 (ELO 158). |
| BG2 | Ratios | figure : eyebrow 3.1 (once per hub); strips and other figures 1.85 (24 : 13); unit 0.375 × figure. Never two 40 figures on one screen. |
| BG3 | Affordance | The whole block is the press target; the chevron 16 on row A is the one visible cue. No buttons inside a block. Documented exceptions: the identity block's edit icon (its own focus stop), LiveScore's host controls (below the block, not inside it). |
| BG4 | Alignment | One left text axis; the trailing end of row A holds only the chevron (or a time + chevron on a data block). |
| BG5 | Order under text scale | Zones keep their order at every scale; A and B stay at the top; at ≥ 1.5 the unit may wrap under the figure; the block grows (min height) and the chevron stays on row A (HIG: primary elements stay on top). |
| BG6 | States | One table for every block (below). |
States, one rule for all blocks.
| State | Zone A | Zone B | Zone C | Press / a11y |
|---|---|---|---|---|
| Loading | 40 % bar (13/18) | 24 % bar (40/48) | 60 % bar (15/22); gray100 on the block fill, pulse .6 ↔ .9, 5 s cap | aria-busy; not pressable |
| Empty (first run) | stays | a 24/32·700 sentence ("일정 없음", "랭킹 전"), never a "0" | one next step as text ("새 일정을 만들어 보세요") | pressable if the step has a destination |
| Error | stays | "—" at α0.6 | "불러오지 못했어요" 15/22·500, no red | press retries; spoken "다시 시도" |
| Offline | stays | last known figure | meta + "· 16:50 기준" (Toss 증권 precedent); the offline strip is the only alert | press works on cached data |
| Long text | 1 line, tail ellipsis | numeral never truncates | 2 lines, 3 at ≥ 1.5, then ellipsis (places may crop; person names never) | the block opens the full text |
| 200 % | cap 2.0 (reading class) | numeral cap 1.5 (60/72) | wraps | block grows; chevron stays top-right; stack per B6.5 |
Applying it to the five blocks.
| Block | A (eyebrow row) | B (figure) | C (meta) | Affordance | Empty |
|---|---|---|---|---|---|
| 홈 day block | "내일, 9월 19일 (토)" + › | "2" ball + "개 일정" | "첫 시작 09:00 · 올림픽공원" (MetaSegments; the venue crops) | block → 경기 › 내 경기 (ruling H2) | A "오늘", B "일정 없음", C next step |
| ELO block | "ELO · NTRP 3.5–4.0" + › | "1,482"; progress 8 between B and C | "마스터까지 518점" 12/16 caption | block → rating detail | B "랭킹 전", C "경기 N번 더 기록하면 보여요"; progress hidden |
| Identity block | row of avatar 56 + name 24/32·700 (2 lines, never cut) + level Tag sm; edit icon 24 ghost-inverse top-right | — (the name carries the weight) | "강남 테니스 클럽 · 3년 차" 15/22 α0.85 | block → profile; edit is its own stop | name unset: "이름을 입력해 주세요" in B |
| LiveScore | Live row above the block (Live Tag + date and venue, §19.3) | two team rows: names 15/22·600 α0.85 · set scores 24/32·700 tabular in 32 columns · current set and points in ball; serving dot in the 8 + 8 slot on both rows (§1) | host controls below the block (two lg tonal Buttons + undo) | participants: read-only; whole block → 경기 보드 | before the first point: scores "0" at α0.6 |
| Club hero, place variant | logo 56 r12 overlapping the band by 16 + name 24/32·700 (flat, ink on canvas) | — | MetaSegments 15/22 secondary: "서초구 · 멤버 42 · NTRP 3.0–4.0" | none (page identity); actions in the header ⋯ and 관리 | skeleton: logo + two bars |
What changes
- Documents: grammar §4 (one Brand block row becomes BG1–BG6), library §4 Block and §14 LiveScore (state table, serving-dot slot), §19.3 Club hero.
- Boards: add error, offline, long-text and 200 % frames to each of the five blocks; draw the skeleton per zone.
- Code: one
Blockprimitive witheyebrow,figure,metaslots and astateprop; the five blocks become compositions of it.
6. The stat strip: equal columns, alignment, order (owner point 6)
"Strip. We need to divide each section equally with equal spacing and alignment. Left aligned?"
What we have
Library §4 Strip: 80 tall, equal columns (350 ÷ n: 116.7 for three, 87.5 for four), numeral 24/32·700 above a 13/18·500 label, a 1 × 24 #E5E5E5 hairline between columns, content left-aligned, 16 after each hairline, the first column on the rail. The board prints it: hairlines at x 137 and 253, and the figures start at x 20, 153 and 269, so the pitch between text starts is 133 then 116: the columns are equal, the content is not. That is the "equal spacing and alignment" problem the owner saw. Spoken order (B8) is label then value ("총 경기 47"); visual order is numeral then label.
Evidence
| Reference | Order (top → bottom) | Alignment | Columns | Dividers | Evidence |
|---|---|---|---|---|---|
| Nike Run Club, "This Week" stats | numeral (≈ 16–17 bold) above label (≈ 11–12 gray) | left; first column's text at the rail | content-sized, equal gap 33 pt (column starts 27.0, 93.4, 193.1; labels start 27.5, 93.0, 192.7) | none | Measured, nrc:1 |
| Strava, activity card | label above value (label ≈ 15 regular, value ≈ 1.35 × larger) | left; the last column pinned to the right edge | content-sized, equal gaps (space-between) | none | Measured, strava:5 |
| Apple Health, Activity card | coloured label above numeral + unit | left | content-sized; pitch 233 vs 223 px in the crop (about equal) | 1 px hairlines, about 12 pt either side | Measured, applehealth:0 |
| Toss 증권 홈, 원화 / 달러 tiles | label above value (13 over 15 bold) | left inside each tile | two equal tiles, about 170 wide, gap 8, gray fill r16 | none (the fill is the container) | Measured, toss:2 |
| 카카오뱅크 내 계좌, "총 9개 / 3,456,789원" | label above numeral (13 over ≈ 28 bold) | left | single | — | Measured, kakaobank:2 |
| Instagram, TikTok profile counts | numeral above label, centred, equal columns (widely known) | centred | equal | none | Unverified: none of the 10 Instagram storefront frame sets contains a profile frame |
| Ours (board) | numeral above label | left inside equal columns | equal columns, text starts 20 / 153 / 269 (unequal pitch) | 1 × 24 #E5E5E5 (1.26:1) | Board anatomy, flat-v2-components.html |
Five of seven peers put the label first; every measured peer is left-aligned; only profile counts are centred. Dividers appear only in Apple Health, whose content-sized columns have unequal widths and need a delimiter; NRC and Strava use equal air. Our hairline has a contrast of 1.26:1, below the 3:1 that WCAG 1.4.11 asks of a graphical object that carries information, so it carries none; KWCAG 5.4.4 accepts spacing as a way to keep neighbouring content distinct. Our own grammar §4b forbids it: a line is allowed only at a sticky or tab edge, between single-line cells of one group, and as the one band; a strip hairline is none of the three, and the "delete the line and keep the gap" test applies.
Recommendation
| # | Rule | Value |
|---|---|---|
| S1 | Alignment | Left-aligned columns, equal pitch. Column i starts at rail + i × 350 / n: n = 2 → 20, 195; n = 3 → 20, 136.7, 253.3; n = 4 → 20, 107.5, 195, 282.5. Same inner structure in every column. |
| S2 | Separation | Air, no hairline. Equal pitch and a shared left edge group the columns (proximity and common alignment). If a divider is ever needed (content-sized columns, as in Apple Health), draw it 8 left of the column start, never between the rail and the first column. |
| S3 | Order | Label above figure (13/18·500 secondary, 4, figure 24/32·700 ink, optional delta 12/16·600 below). The same order as the Block (eyebrow → figure → meta), the spoken order and the DOM order; five of seven peers. |
| S4 | Sizes | Figure : label 1.85 (peers 1.3–2.1). Height unchanged: 12 + 18 + 4 + 32 + 12 = 78, floor 80 (100 with deltas). |
| S5 | Count | 2–4 columns. Five or more is not a strip: use a Cell group. n = 2: 175 each (the Toss account tiles are the reference). |
| S6 | Long values | A figure never shrinks and never ellipsizes. Fit test: the widest figure at the current scale ≤ pitch − 8. If it fails, reflow to a 2-up grid (rows of two, 12 apart). Examples (Derived, Pretendard Bold): n = 4 (budget 79.5): "1,482" is 64 px at 1.0, 83 px at 1.3 → reflow; n = 3 (budget 108.7): "1,482" is 83 px at 1.3 and 96 at 1.5 → keeps three columns, while "1,482점" is 110 px at 1.3 → reflows. Labels wrap to two lines instead (English "Attendance" is 69 px). |
| S7 | Press | A linked column keeps its own press (.98 / .80) and a whole-column hit area; unlinked columns are plain text (one stop per column, label then value). |
What changes
- Documents: library §4 Strip row (order, pitch, no hairline, fit test), §19.3 "Strip with deltas" (label / figure / delta), grammar §4 Stat strip row.
- Boards: redraw the Strip specimens (3 and 4 columns, 2-up reflow at 1.3×, English labels), remove the hairlines from every page board that uses it (기록, 프로필, club analytics).
- Code:
Strip(wasCellRow): equal pitch container, label-first slots, fit-test reflow.
7. English: expansion, abbreviations, line budgets (owner point 21)
"The english version of the components, like the media row, is quite terrible… So many texts going over a few line, cluttering the view."
What we have
- The patterns board draws English only at 200 % and at 320 wide ("Waitlisted", "Doubles NTRP 3.0–3.5"), and its time reads "9:00–11:00 AM". The code is 24-hour in both languages:
formatTimeRangereturns${s}–${e}of "HH:MM" (ONE-CLOCK, F7, 2026-09-23), and the terminology guide (line 47) rules English as sentence case, "N going", "N KRW" (never won), durations "N min / N hr", relative chips "Nm / Nh / Nd", en-dash ranges "10:00–12:00",enCount/enPlural. The board and the rulings disagree on the clock. - The row text columns are narrow: at rail 20 the media row's text column is 193 px when a "Waitlisted" Tag trails and 242 px when a "6/8" figure trails.
Evidence
How much longer our English really is (Measured on all 3,292 string pairs present in both ko/ and en/ locale files; widths in Pretendard Regular, no kerning).
| KO syllables | Pairs | Median EN ÷ KO width | p75 | p90 | Share > 1.5× | Share > 2× |
|---|---|---|---|---|---|---|
| 1–4 | 1,405 | 1.59 | 1.93 | 2.34 | 57 % | 22 % |
| 5–8 | 790 | 1.31 | 1.60 | 1.86 | 32 % | 5 % |
| 9–14 | 678 | 1.17 | 1.38 | 1.61 | 17 % | 1 % |
| 15–24 | 281 | 1.21 | 1.39 | 1.55 | 15 % | 1 % |
| 25–40 | 82 | 1.25 | 1.39 | 1.51 | 11 % | 0 % |
| all | 3,292 | 1.33 | 1.67 | 2.05 | 37 % | 11 % |
The worst short labels: 문양 → "Generated mark" (4.2×), 정원 → "Max attendees" (3.9×), 혼복 → "Mixed doubles" (3.8×), 업적 → "Achievements" (3.7×), 월 → "Month" (3.3×), 알림 → "Notifications" (3.3×). References for the same effect: Seed's Korean-source table (≤ 10 characters: 150–250 %; 11–20: 130–150 %; 31–50: 90–110 %; ≥ 71: 80 %), which "Save 저장 (2) → Save (4)" illustrates (Seed international design); the IBM table for English sources (up to 10 characters 200–300 %, over 70 characters 130 %) and the note that "each Chinese and Korean character is counted as two English characters in width" (W3C); Android's older checklist, "up to 30 % more" (archived copy). In pixels our short labels exceed the generic 30–50 % rules of thumb.
What the platforms say.
- HIG Lists and tables: "Keep item text succinct so row content is comfortable to read. Short, succinct text can help minimize truncation and wrapping" and "an ellipsis in the middle of text can make an item easier to distinguish because it preserves both the beginning and the end of the content" (Lists and tables). HIG Writing: "write with accessibility and localization in mind" (Writing); Layout lists "text length" among the locale features to preview (Layout).
- Android: the
en-XApseudolocale "expands the original text by adding non-accented text" to expose layout breakage (pseudolocales). - Seed: Badge labels "2~3단어 이내", with a max width that ellipsizes at 10 characters or more (badge); relative time in English "1s, 1min, 1h, 1d, 1w, 1mo, 1y" (and "min" "to make clear it is minutes" against months); en_US dates "Jul 16, 2015" / compact "Jul 16" / weekday "Wed, Jul 16" / "Wed, Jul 16 at 9:41 PM"; counts "1 review", "3 reviews", "30K views"; Korean 만 and 억 never carried into English.
- Apple Style Guide on abbreviations: spell out an unfamiliar abbreviation at first use; avoid Latin abbreviations such as "e.g." (Apple Style Guide).
What CLDR produces (Derived: Intl.DateTimeFormat in Node 22, ICU 77.1 / CLDR 47, 19 Sep 2026, 09:00–11:00):
| Pattern | en-US | en-GB | en-CA | ko-KR |
|---|---|---|---|---|
| weekday, month, day | Sat, Sep 19 | Sat 19 Sept | Sat, Sep 19 | 9월 19일 (토) |
| 09:00–11:00, hour only | 9 – 11 AM (thin spaces round the en dash, U+202F before AM) | 09–11 | 9–11 a.m. | 오전 9시 ~ 11시 |
| 09:00–11:00, minutes | 9:00 – 11:00 AM | 09:00–11:00 | 9:00–11:00 a.m. | 오전 9:00~11:00 |
| 09:30–12:30 | 9:30 AM – 12:30 PM | 09:30–12:30 | 9:30 a.m.–12:30 p.m. | 오전 9:30 ~ 오후 12:30 |
| relative, narrow | 3h ago, 5m ago | 3 hr ago | 3 hrs ago | 3시간 전 |
What Korean apps' English modes do (US-storefront frames): Toss keeps the 24-hour clock ("Last refreshed 16:50", en_toss:2) and uses short verbs and sentence case ("Send", "History", "Get points"; en_toss:0, 5); Naver Maps prints "11.10.(Mon) · PM 7:00 · 20 min" and "6:38 PM - 7:59 PM" and "2 mins ago" (en_navermap:1, 3, 4); KakaoTalk prints "PM 06:00 ~ PM 9:00" and "30 minute(s) ago" (en_kakaotalk:1, 3); Karrot (당근 for the US) follows Seed's spec: "0.2mi · 30 minutes ago", "2min", "3h ago", "1 chat · 3 favorites · 12 views" (en_karrot:1, 4, 6, 7). Korean products are not consistent and often not CLDR ("PM 7:00", "minute(s)"); do not copy them. Seed's own spec is the one to follow.
Where our rows wrap (Derived, Pretendard; Tag 13/600, L1 17/600, L2 15/400; rail 20, thumb 56, gaps 12):
| String | KO | EN today | EN recommended |
|---|---|---|---|
| Row L1, time | 09:00~11:00 101 px | 09:00–11:00 98 px (board draws 9:00–11:00 AM 118) | 09:00–11:00 98 px (12-hour option below) |
| Row L2, venue + format + level | 올림픽공원 테니스장 · 복식 · NTRP 3.0–4.0 273 px | Olympic Park Tennis Courts · Doubles · NTRP 3.0–4.0 368 px | Olympic Park · Doubles · 3.0–4.0 228 px (−38 %) |
| Row L2 with a Tag trailing | 2 segments 163 px | — | Olympic Park · Doubles 160 px |
| Text column, figure trailing | 242 px | 242 px | 242 px |
| Text column, "Waitlisted" trailing | 232 px ("예비") | 193 px | 193 px |
| Lines for L2 in that column | 2 | 3 | 1 (figure) or 1 (Tag, two segments) |
| Club L2 | 서초구 · 멤버 42 · NTRP 3.0–4.0 214 px | Seocho-gu · 42 members · NTRP 3.0–4.0 286 px | Seocho · 42 members · 3.0–4.0 221 px |
| Person L2 | 총무 · NTRP 3.5 95 px | Treasurer · NTRP 3.5 129 px | unchanged (fits) |
| Hero fact, date and time | 9월 19일 (토) 09:00–11:00 189 px | — | Sat, Sep 19 · 09:00–11:00 195 px (parity) |
| Hero fact, venue | 올림픽공원 테니스장 · 하드 183 px | Olympic Park Tennis Courts · Hard 265 px (fits 322 in one line) | unchanged |
| Day header | 9월 18일 (금) 83 px | Fri, Sep 18 72 px | unchanged |
In these cells "·" stands for the MetaBreak (a 1 px bar with 8 px of air each side, 17 px per break); the boards draw the bar, never the dot (library C7). Text columns: 390 − 2 × 20 − 56 − 12 − trailing element − 12.
The clutter is row L2 (three segments with a long English venue and the "NTRP" prefix) and long Tag copy; the clock, the day header, the hero facts and person rows are not an English problem. Tag copy over the 80 px budget (13/600): "Recruitment closed" 117, "Awaiting confirmation" 133, "Refund pending" 95, "Partial payment" 94, "Awaiting reply" 87.
Recommendation
| # | Rule | Value |
|---|---|---|
| E1 | Expansion budget | Write English to ≤ 2.0 × the Korean width for labels of up to 4 syllables, ≤ 1.6 × for 5–8, ≤ 1.4 × for longer (the corpus p75). Design the box for 2.3 × at the shortest labels (p90). |
| E2 | Role limits (English) | Tag ≤ 12 characters, 1–2 words (≤ 80 px at 13/600), never truncated: the copy is shortened instead (Seed: 2–3 words, ellipsis at 10). Tab: one word, ≤ 8 (HIG: "single words whenever possible"). Choice ≤ 14. Button ≤ 2 words. Header title ≤ 22 (one line at 20/28). Cell label ≤ 26. |
| E3 | Dates | CLDR en-US / Seed: Sat, Sep 19 (weekday + date), day header Fri, Sep 18 + Today / Tomorrow, no year within the current year, Sep 19, 2027 otherwise; "at" only inside sentences (Sat, Sep 19 at 9:00 AM). Month abbreviation "Sep" (en-US; en-GB "Sept"). |
| E4 | Clock | Keep ONE-CLOCK (24-hour) for English: 09:00–11:00 costs the same width as Korean (98 vs 101 px), Toss's English keeps it, and the terminology guide rules it. The owner's 9–11 AM is the CLDR en-US form: 65 px on the hour, 85 px for 6–9:30 PM, 123 px for 9:30 AM–12 PM, i.e. shorter only on the hour. If the owner rules 12-hour for English, use CLDR formatRange (en dash, narrow no-break space before AM/PM) and keep a single formatter path. |
| E5 | Counts and numbers | Numerals always; plural by Intl.PluralRules (enPlural): "1 court", "4 courts"; rows show 6/8, spoken "6 of 8 going"; compact numbers only from 10,000 (12.3K), never 만 or 억 in English. Money: the terminology guide's "N KRW" stays for prose; in tabular trailing figures allow ₩30,000 (52 px vs 75 px at 15/700) and CLDR agrees; owner and terminology ruling needed. |
| E6 | Relative time | Nmin, Nh, Nd, Nw, Nmo, Ny (Seed): the guide's Nm is ambiguous between minutes and months. Amend line 47. |
| E7 | Abbreviations | Allowed: AM / PM, Mon–Sun, Jan–Dec (CLDR), the relative units above, "vs". Not allowed: Latin abbreviations (e.g., i.e.), "approx.", "No." (use "#N", ruled). Unfamiliar terms are spelled out where they first appear on a screen (NTRP stays: it is a player term). |
| E8 | One line or wrap | One line, at 100 % text: row L1, row L2, day header, Cell label, Tag, tab, button. Wrap, never truncate: hero facts, notice and dialog body, helper text, Cell values, names. In a row, the flex segment truncates (the venue or club name: tail ellipsis; a long single name may use a middle ellipsis) while the fixed tokens (format, level, count) never do. |
| E9 | Row L2 budget | At most 2 segments when a Tag trails, 3 when a figure trails. In English rows drop generic words from venue names ("Olympic Park Tennis Courts" → "Olympic Park" through a shortName rule), drop the "-gu" suffix ("Seocho"), and omit the "NTRP" prefix in rows (keep it in detail and hero). |
| E10 | Tag copy to shorten | "Recruitment closed" → Closed (42 px), "Awaiting confirmation" → To confirm (65), "Refund pending" → Refund due (69), "Partial payment" → Part paid (54), "Waitlist open" → Waitlist (45), "Awaiting reply" → Pending (52). "Waitlisted" (61), "Payment due" (79), "Closing soon" (78) already fit. Each needs the terminology guide's sign-off. |
| E11 | Gates | (a) a pseudolocale test that expands every Tag, Tab and Button string to its role budget and measures it in Pretendard; (b) character limits per role in check:i18n-house-style; (c) board rows drawn in English at 100 % with a DOM check: L1 one line, L2 one line on figure rows and at most two lines on Tag rows. |
What changes
- Copy and rules: terminology-guide line 47 (relative chips
Nmin; the money form in tables; the 24-hour clock stays), new E2 budgets, Tag copy list (E10),shortNamefor venues. - Boards: draw the session, club and person rows and the hero facts in English at 100 % beside Korean; keep the 200 % and 320 frames.
- Code: a venue
shortNamehelper, the E2 limits in the i18n lint, the pseudolocale width test.
8. Motion and accessibility audit (owner point 22)
"As for motion and accessibility, make sure you reference best practices to learn from them."
What we have
Behaviour addendum B7 (durations 80 / 100 / 150 / 200 / 250 / 300 ms, effects on timing, easings 0.2,0,0,1 · 0,0,0,1 · 0.3,0,1,1, press springs, a Reduce Motion table, toasts 4 / 6 / 8 s, no layout animation in virtualized lists, loops capped at 5 s) and B8 (one focus stop per row with a composed name from the same model, one h1, announcements per platform, focus return, 44 targets with hit slop, gesture alternatives, language, web keyboard patterns).
References read for this audit: HIG Motion (updated 2025-09-09) and HIG Accessibility (2025-06-09); Apple's App Store Connect evaluation criteria for Reduced Motion, Sufficient Contrast, Differentiate Without Color Alone and Larger Text; Material 3 motion in code (MotionTokens, MotionScheme, Standard and Expressive spring tokens) and the MDC Motion guide; Seed motion, feedback scale, inclusive design; WCAG 2.2 (what is new); Android accessibility; React Native AccessibilityInfo.
Keep (the spec already agrees with the references)
| # | Item | Our value | Reference |
|---|---|---|---|
| K1 | Duration set | 100 / 150 / 200 / 250 / 300 ms (+ 80 press-in) | Identical to M3 short2 … medium2 (100 … 300) and Seed d2 … d6 (Seed also calls ≤ 200 ms "micro" motion and > 200 ms "macro"). Nothing of ours exceeds 300 except platform pushes and loops. |
| K2 | Easing | standard (0.2,0,0,1), enter (0,0,0,1), exit (0.3,0,1,1) | = M3 EasingStandard, StandardDecelerate, StandardAccelerate (MotionTokens.kt); Seed uses (0.35,0,0.35,1) / (0,0,0.15,1) / (0.35,0,1,1). |
| K3 | Effects on timing, no overshoot | opacity and colour on tweens | M3's effects springs have damping ratio 1.0 (Standard and Expressive alike): no overshoot on colour or opacity. |
| K4 | Press: colour first, scale optional | dim always, settle dropped under Reduce Motion | Seed: colour is the base feedback and scale does not happen when the OS reduce-motion setting is on (translated from the Korean page); HIG Motion: "Make motion optional". |
| K5 | Toast time and dismissal | 4 / 6 / 8 s, OS timeout honoured, × and tap | Seed snackbar 4 s default and longer for long text (snackbar); M3 snackbar 4–10 s (MDC Web README); HIG Accessibility "Minimize use of time-boxed interface elements" and "Offer alternatives to gestures". |
| K6 | Targets | 44 with hit slop | HIG 44 × 44 default; WCAG 2.5.8 24 × 24 (AA); Seed 44 ideal, 24 minimum; KWCAG 6.1.3 ≈ 27 dp. |
| K7 | Focus ring | 2 px ink, offset 2 | Meets the geometry (area ≥ a 2 px perimeter) and the 3:1 change of WCAG 2.4.13, which is Level AAA in the final WCAG 2.2 (Understanding 2.4.13). |
| K8 | Loops | shimmer and live dot stop after 5 s | WCAG 2.2.2 (A): auto-starting movement over 5 s needs a way to stop; Apple's Reduced Motion criteria ask to consider stopping "auto-advancing carousel screens or any other ongoing motion". |
| K9 | One stop per row, composed names, Label in Name | B8.1–B8.2 | Android: merge a group into its parent and keep descriptions short (principles); WCAG 2.5.3. |
| K10 | Colour never alone | Tag text, glyph fill vs outline, shape cues | Apple's Differentiate Without Color Alone criteria (shape or text beside colour); passes for Tags, tabs, selected rows (check mark + 600 weight). |
Change
| # | Item | Today | Reference and finding | Fix | Pri |
|---|---|---|---|---|---|
| C1 | Reduce Motion removes; Apple says replace | B7.7: translate, scale and layout rows vanish, and opacity cross-fades become instant | HIG Accessibility: "Replacing transitions in x-, y-, and z-axes with fades". Apple's criteria: "Removing animations entirely can have a negative effect on usability and understandability"; suggested replacements: "a dissolve, highlight fade, or color shift". | Under Reduce Motion keep the 100–150 ms cross-fade for sheet, dialog, menu, toast, FAB label and header title; remove only translate, scale and layout. Read prefersCrossFadeTransitions (iOS) to choose fade over cut. | P1 |
| C2 | Press springs are the expressive class | pressIn {20, 400, 0.7}: ζ 0.60, 9.5 % overshoot, 247 ms; pressOut {15, 300, 0.7}: ζ 0.52, 14.8 %, 377 ms | Compose's MotionScheme KDoc: standard = "basic motion scheme for utilitarian UI elements and recurring interactions" (spatial ζ 0.9); expressive = "for prominent UI elements and hero interactions" (fast spatial ζ 0.6). A press is a recurring interaction; HIG: avoid extra motion on frequent interactions and don't make people wait. | pressIn {30, 400, 0.7} and pressOut {26, 300, 0.7}: ζ 0.90, overshoot 0.1 %, settle 196 / 226 ms (integrated like the addendum's table). | P3 |
| C3 | Press scale as a ratio | row .98, button .97, icon .92, pill .96, fab .96, block .98 | Seed defines shrink as a distance (2 px, basis = max(height, width ÷ 4, 24)) so every element feels the same. Ours: a 44 icon shrinks 3.5 px, a 350 button 10.5 px wide, a block 7.0 × 2.9; Seed would give 2.0, 8.0 and 4.9 × 2.0. | Optional: compute the scale from the element's size (one helper in pressable-visual.ts). Not needed for correctness. | P3 |
| C4 | Header actions 44 | two 24 glyphs in 44 targets, gap 8 | Android: "Ensure all touch targets are at least 48 dp, even if this extends past the UI element visual"; our primary audience is on Android. | 48 targets, no gap: 2 × 48 = 96 = 44 + 8 + 44, the same footprint. | P2 |
| C5 | Orientation | orientation: portrait (iPhone and phone Android) | WCAG 1.3.4 Orientation (AA): content "does not restrict its view and operation to a single display orientation" unless essential; users with a device mounted on a wheelchair arm cannot rotate it (Understanding 1.3.4). On Android 16 the lock is ignored from 600 dp anyway (B9.8). | Decide: support landscape on reflow-safe screens (the 320 / 200 % frames already prove reflow), or record that portrait is not essential and accept the departure. Owner call. | P2 |
| C6 | Label cap 1.5 | Tags and Choices stop at 1.5 × | See §3: Toss lets 13 px reach 26; Apple's Larger Text criteria want content text at 200 %. | Raise to 2.0. | P2 |
Missing
| # | Gap | Why it matters (reference) | What to add | Pri |
|---|---|---|---|---|
| M1 | Dragging alternatives | WCAG 2.5.7 Dragging Movements (AA, new in 2.2): "All functionality that uses a dragging movement for operation can be achieved by a single pointer without dragging". B8.7 gives screen-reader alternatives (adjustable actions, move actions, stepping); a sighted person with a tremor or a head pointer gets none (Understanding 2.5.7). | TimeBandSlider, RangeBandSlider, Slider: tap the track to set the nearest thumb, plus the existing 44 Stepper for ±. Photo tile reorder: "앞으로 / 뒤로 이동" in the tile's action sheet. Chart scrub: tap a point to set the readout. SwipeAction and toast swipe already have buttons. | P1 |
| M2 | App Store accessibility labels (iOS build) | Apple's evaluation criteria define what a claim means: Larger Text ≥ 200 % for content; Sufficient Contrast tested with Bold Text, Increase Contrast and Reduce Transparency on; Reduced Motion stops ongoing motion; Differentiate Without Color Alone tested in grayscale; VoiceOver and Voice Control for the common tasks. | An "iOS build gate" checklist: five common tasks (find and RSVP, pay the fee, confirm dues, advance a wizard step, enter a score) run with each setting; a grayscale screenshot pass; record which labels to declare. | P1 (at the first iOS build) |
| M3 | OS settings we do not read | RN exposes prefersCrossFadeTransitions (iOS), isDarkerSystemColorsEnabled (iOS Increase Contrast), isHighTextContrastEnabled (Android), isReduceTransparencyEnabled, isGrayscaleEnabled, isBoldTextEnabled; we read Reduce Motion and plan Bold Text only. | Increase Contrast / high text contrast: promote tertiary and the 1.26:1 hairlines to the secondary and edge tiers (#4F5851, #D4D4D4). Reduce Transparency: nothing to change (scrims only; the α0.8 / 0.85 block text is already 11.7:1). | P2 |
| M4 | WCAG 2.2 criteria not in B8 | 3.3.7 Redundant Entry (A), 3.3.8 Accessible Authentication (AA), 3.2.6 Consistent Help (A) (new in 2.2). | Wizards: never ask the same value twice (prefill, copy from the last session; check 이름 and 지역 across onboarding and club creation). Email sign-in: allow paste and password managers (textContentType, autoComplete), no puzzle. Help entry in the same place on every screen. | P2 |
| M5 | Text spacing on the web build | WCAG 1.4.12 (AA): no loss when a user sets line height 1.5, paragraph 2, letter 0.12, word 0.16 × the font size. minHeight floors help; overflow: hidden on nowrap Tags and Choices would clip. | A one-time check on apps/web with a text-spacing override on the board and three real screens; add it to the web gate. | P3 |
Priorities. P1 = before the first public build (C1, M1, M2). P2 = in the same wave as the type changes (C4, C5, C6, M3, M4). P3 = polish (C2, C3, M5).
What changes
- Addendum B7.7 (Reduce Motion table: opacity rows keep a short fade), B7.2 (press spring values), B8.7 (visible alternatives to drags), new B8.11 (OS accessibility settings), B9.8 / B8 (orientation ruling), library §2 (press, hit slop 48 on Android).
- Boards: the motion board's Reduce-motion column, a "drag alternatives" specimen per slider, the iOS gate checklist in the contract.
9. Audit of the sections the owner did not comment on (library §3–§6)
Every value or behaviour that departs from current platform guidance or measured peer practice, found while checking §3 chrome, §4 surfaces, §5 rows and §6 labels against the evidence in this note. Items already settled above are cross-referenced, not repeated. P1 before the first public build · P2 with the type and spacing wave · P3 polish.
| # | Section · item | Issue | Evidence | Fix | Pri |
|---|---|---|---|---|---|
| A1 | §3 Header, action targets | 44 targets on a platform whose guide says 48 | Android: "at least 48 dp, even if this extends past the UI element visual" | 48 targets, gap 0 (same 96 footprint); see §8 C4 | P2 |
| A2 | §3 Header, height and title | none: 56 equals Seed's top navigation (56), sits between iOS (44) and M3 (64) | top-navigation.json, AppBarSmallTokens (64), Apple archive (44) | keep | — |
| A3 | §3 Tab row (scope tabs) | row 44 and a 2 pt underline are the thinnest of the references; inactive 500 vs active 600 is a small difference | M3 primary tab: container 48, active indicator 3 (PrimaryNavigationTabTokens); Toss Tab selected 700 vs 600 and grey800 vs grey600 (Code) | row 48, underline 3, active 700 · inactive 500 (ink vs secondary already 19:1 vs 7.4:1) | P3 |
| A4 | §3 Tab bar, labels | 11/14 Hangul labels | M3 navigation bar label = Label Medium 12/16 (NavigationBarTokens); Android floor 12 sp; §3 above | 12/16 (fits: 6 + 24 + 2 + 16 = 48 in 56) | P2 |
| A5 | §3 Tab bar, badge dot | a 7 px dot (off the 4-grid and off every peer) | M3 small badge 6 dp, Seed small 6 px, Toss redBean 6 × 6 (Code) | 6 px dot with the 1.5 canvas ring (icon-attached dots stay 6; row markers are 8, §1) | P3 |
| A6 | §3 FAB | extended pill 48 tall, collapsing to a 48 circle | M3 extended FAB container 56 dp, icon 24 (ExtendedFabPrimaryTokens); it is the screen's one coloured action | 56 tall, 20 pad, label 15/22·600; collapsed 56 circle; list tail padding clears the 56 plus its 16 offset | P2 |
| A7 | §3 CTA band, helper line | the fee and cancellation terms (money-affecting copy) are 13/18 secondary | reading-class text should be ≥ 15–16 (KRDS ≥ 16; Seed t5 16; §3); Jeong 2016 puts 13 at the lower bound for older readers | helper line 15/22 secondary, ≤ 2 lines; band 118 → 148 (one line) / 170 (two); wrap, never ellipsize | P2 |
| A8 | §3 Section head, action | "전체 보기 ›" is 13/18·500 secondary: the navigation affordance is the smallest and lightest text on the row | Toss ListHeader right link t6 15 for medium and large headers (Code); Apple Developer "Recent videos ›" sits at heading size; Shortcuts "See All" 17 | 15/22·500 secondary with the 16 chevron; keep the 44 target | P2 |
| A9 | §3 Section head, title size | the title is the row-title style (17/24·600), so the hierarchy rests on space alone; the peers' section titles measure larger (ink median 17.1 against our 14.7, about 19–21 px Hangul); KRDS puts a 17 heading in its h4/h5 tier ("similar in size to body text") and asks for 1.25–1.5 × the body, and the new 16 reading role would leave 1.06 | §2 table; KRDS typography (mobile heading sizes 28 / 24 / 22 / 19 / 17 / 15; h2 = 24–22, h3 = 22–19, h4 = 19–17, h5 = 17–15; "ideal heading to body difference 1.25–1.5×"; use with care where body and heading sizes coincide). Toss's published ListHeader title is also 17/25.5 bold, and its 홈 cards use 17 bold titles while 증권 홈 reads ≈ 20 bold (§5 table), so this is a judgment, not a violation | the existing title 20/28·700 (20 = 1.25 × 16; ink 17.4 against the peer median 17.1); Section head 24 → 28; box spacing unchanged (ink 37.3 above, 17.3 below); decision D7 | P2 |
| A10 | §3 Day header, today dot | hung in the margin; ball on white is 1.28:1 | §1 | remove (LM4) | P1 |
| A11 | §3 Band | none: 12 tall #F2F2F2 sits inside the measured bands (7–16; Toss 16, Seed 8, M3 heavy 8) | lane K §3 | keep | — |
| A12 | §4 Block, text on ink | none: eyebrow α0.8 is 11.65:1, meta α0.85 is 13.0:1, ball numeral 13.95:1 | §3 contrast table | keep; add the state table (§5) | — |
| A13 | §4 Strip | numeral above label, unequal text pitch, hairline outside the §4b line law | §6 | S1–S7 | P2 |
| A14 | §4 Notice, Dialog and Toast body | body 15/22 | reading class (§3) | 16/24; heights stay 48 | P2 |
| A15 | §4 Chart, labels | axis ticks 12/16·500 | §3 (12 is the floor; wrapping rule 1.5) | 12/18, non-essential only; the values are in the readout and the spoken summary | P3 |
| A16 | §4 Thumb, court tile | the hard / clay / grass tile says the surface by colour alone | Apple's Differentiate Without Color Alone criteria | acceptable while the venue name and the surface word sit in the row or the hero; if a row ever shows the tile without the name, add the word | P3 |
| A17 | §5 MediaRow, L2 in English | three segments overflow the text column | §7 (368 px vs 193–242 px) | E8, E9 | P2 |
| A18 | §5 Roster cell, names | person names at 13/18 are the smallest names in the app, in a 4-up cell about 87 px wide | Jeong 2016 (13 is the best of 11–13 for 65+); KRDS body ≥ 16, small 15; "names never cut" makes two lines the norm | 15/22·600, up to 2 lines; cell pitch 100 → 104 (one line) / 126 (two lines); the level Tag stays | P2 |
| A19 | §5 Cell, value and label | none: 48 high, label secondary 7.38:1, value ink; between Apple 44 and M3 56 | ListTokens (56 one-line) | keep | — |
| A20 | §5 Row hairlines (cell tier) | #E5E5E5 is 1.26:1 | decorative: WCAG 1.4.11 applies to information-bearing graphics; KWCAG 5.4.4 accepts lines or spacing | keep; Increase Contrast promotes it to #D4D4D4 (§8 M3) | — |
| A21 | §6 Tag, English copy | state labels up to 133 px against an 80 px budget | §7; Seed Badge 2–3 words | E2, E10 | P2 |
| A22 | §6 Count tag | 11/14 numeral in a 20 tall pill | M3 badge label small 11/16, Seed 11/15 bold: size is peer-standard (digits, not Hangul); only the line height is short | 11/16·700 | P3 |
| A23 | §6 Level tag | none: ball with an ink-edged 1 px #D4D4D4 ring on white | ball on white is 1.28:1, the ring supplies the 3:1 | keep | — |
| A24 | §6 Live tag | none: white on #B8332E is 5.91:1, pulse capped at 5 s | WCAG 2.2.2; Apple Reduced Motion criteria | keep; under Reduce Motion show the static dot (§8 C1 keeps fades) | — |
| A25 | §7 Switch (outside the requested scope) | 48 × 28 is smaller than every reference | M3 track 52 × 32, thumb 24 (SwitchTokens); Toss 50 × 30 (Code); iOS UISwitch is commonly reported as 51 × 31 (Apple forum); RN reads UISwitch.intrinsicContentSize at run time, and the size is reported to differ on iOS 26 | 52 × 32 track, thumb 24 (28 pressed), still inside a 48 hit area | P2 |
10. Decisions this note needs from the owner
| # | Decision | Recommendation | Cost of the other way |
|---|---|---|---|
| D1 | Rail: 20, 16 or 24 at 390 pt (§4) | 20 (16 below 390, 24 from 600) | 16: +8 pt text width, rebuild of 350-based boards; 24: −8 pt width, rebuild |
| D2 | English clock: keep the 24-hour ONE-CLOCK, or 12-hour "9–11 AM" for English (§7 E4) | keep 24-hour (same width as Korean; Toss English; terminology ruling) | 12-hour: a second formatter path; shorter on the hour only (65 vs 98 px), longer at :30 |
| D3 | English money in tables: "₩30,000" or the ruled "30,000 KRW"; relative chips Nmin (§7 E5, E6) | ₩ in tabular trailing figures, Nmin; amend terminology-guide line 47 | "30,000 KRW" costs 23 px per figure; Nm stays ambiguous (minute or month) |
| D4 | Shorter English Tag copy (§7 E10) | adopt the six shortened labels | long labels keep wrapping rows or force ellipses that B6.6 forbids |
| D5 | Orientation: support landscape on reflow-safe screens or accept the portrait lock (§8 C5) | decide with the first iOS build; the 200 % and 320 frames show the layouts reflow | a recorded WCAG 1.3.4 departure |
| D6 | Optional polish: press springs ζ 0.9 and distance-based press scale (§8 C2, C3) | adopt the springs, skip the distance helper unless a QA finding asks | none functional |
| D7 | Section title size: keep 17/24·600 or use the 20/28·700 title role (§2, audit A9) | 20/28·700 (reuses an existing role; 1.25 × the new 16 reading size, as KRDS asks) | 17: the title stays the size of a row title and 1.06 × the reading size; the head stays 24 |
11. Sources
Apple. HIG Typography, Layout, Lists and tables, Tab bars, Motion, Accessibility, Writing (read through the JSON mirror at developer.apple.com/tutorials/data/design/human-interface-guidelines/<page>.json); UIKit systemMinimumLayoutMargins, UIView.layoutMargins, sectionHeaderTopPadding; Apple Developer Forums 697910 and 675534; Transition Guide (archive); App Store Connect evaluation criteria: Larger Text, Reduced Motion, Sufficient Contrast, Differentiate Without Color Alone; Apple Style Guide, abbreviations. Third-party explanations of the UIKit margins: Use Your Loaf, monday.com engineering.
Google and Material. Material 1 metrics and keylines, Material 1 subheaders; Compose Material 3 source (androidx-main): TypeScaleTokens, ListItem, ListTokens, NavigationBarTokens, AppBarSmallTokens, ExtendedFabPrimaryTokens, SwitchTokens, BadgeTokens, PrimaryNavigationTabTokens, MotionTokens, MotionScheme; MDC Motion; Android accessibility design, grids and units, accessibility principles, pseudolocales, localization checklist (archive); React Native AccessibilityInfo.
Design systems. Toss TDS: docs and the published package @toss/tds-mobile 2.5.1 (type map, ListHeader, Button, Tab, Switch, font-scale caps read from dist/esm/index.js). Seed (당근): typography, spacing, layout, motion, feedback scale, inclusive design, international design, components list, badge, notification-badge, snackbar, specs list-header, notification-badge, top-navigation. KRDS typography. KWCAG 2.2 (KS X OT0003:2022).
WCAG 2.2. What is new; Understanding 1.3.4, 1.4.4, 1.4.10, 1.4.11, 1.4.12, 2.4.13, 2.5.7.
Research and articles. Jeong 2016, Hangul on smartphones for adults over 65; systematic review of font size for older adults, Frontiers in Psychology 2022; Rello, Pielot, Marcos, CHI 2016; NN/g, Proximity; W3C, text size in translation; CSS-Tricks, iOS form zoom.
Data and fonts. iTunes lookup API; Pretendard 1.3.9 (jsDelivr); Unicode CLDR 47 through Node 22 Intl.
In this repository. Lane H, lane K, grammar, library, addendum, terminology guide; boards flat-v2-components.html (Strip, section specimens) and flat-v2-patterns.html (English at 200 %); packages/app/src/presentation/utils/date-format.ts (formatTimeRange); the ko/ and en/ locale files under packages/app/src/config/i18n/.
12. Frames measured
en_* frames are the US-storefront listing's English-UI frames of the same app. Each image is the original-size file (0x0ss.png); app:n is the n-th screenshotUrls entry, counting from 0.
13. Method and limits
Screen and scale. Every frame is converted to RGB; the screen background colour is the modal colour in the central 40 % of rows 28–95 % of the height; for each row whose centre pixel has that colour the run is expanded left and right (tolerance 3); the screen bounds are the median ends of the runs that are at least 97 % of the longest. pt = px ÷ (screen px ÷ 390). The two raw captures use 3 px per pt directly. The assumption of a 390 pt screen is validated against three published tokens (Toss 24, Seed 16, Airbnb 24); for the Apple mock-ups (device width 402 or 440 pt possible) values read up to 3 % low at 402 and 13 % low at 440, which cannot move 19.1–20.7 out of the 20 class.
Margins. For each row of the content area, the first and last run of at least 14 pt of non-background pixels gives a left and a right edge; the histogram peaks (merged ±1 px) are the margins. Left and right peaks of the same height confirm the screen bounds (a sideways error cancels). Text-only screens use the first ink and overstate by 0.5–1.5 pt.
Vertical gaps. Ink runs in a column band to the right of the rail (threshold 8 for light fills, 14–28 for text, 6 to catch a grey band or a white card on a grey canvas); gaps are run-to-run, so they are ink to ink, and a container's visible edge counts as the end of the previous group. Heading ink heights (16.5–18.7) correspond to about 19–22 px Hangul headings (ink ÷ 0.868 to ÷ 0.80, depending on which syllables the string contains). The 무신사 frame is named 430x932, so its screen may be a 430 pt design and its pt values up to 10 % low; the ratio above ÷ below does not depend on the scale.
Strings. Advance widths of Pretendard 1.3.9 (Regular, Medium, SemiBold, Bold) from the hmtx table, no kerning, no shaping; missing glyphs count 1 em. Row text columns: 390 − 2 × 20 − 56 − 12 − trailing − 12. The ko and en locale modules were bundled with esbuild and flattened; 3,295 Korean string leaves, 3,292 pairs present in both.
Not verified, and why. iOS Mail and Messages dot geometry (no raw capture; Apple's pages give none); Instagram and TikTok profile-count strips (none of 10 Instagram storefront sets has a profile frame); the iOS 26 UISwitch size and tab-bar height (no primary figure); Hangul-specific line-height research beyond the null result of Jeong 2016; the Korean mobile-app accessibility guideline KS X 3253 (text not found); Material 3's margin per window size class (the m3.material.io pages are script-rendered); VoiceOver and TalkBack pronunciations (need devices, already open in the addendum). Frames are marketing images, so spacing seen there is what the marketing team chose to show; the three raw Apple captures and the published tokens anchor the numbers.
Reproducing. The measurement scripts live in the session scratch directory (not committed): a downloader for the lookup API, the centre-run screen detector, the block-edge histogram, the vertical-run tool, the string-width and wrap simulator, and an esbuild bundle of the locale modules. Each step above is a few lines of NumPy, Pillow and fontTools.
14. The ten changes, in one list
- Leading markers: the owner's rule stands as a whole-list slot (dot 8 + 8, content edge 36, first-line aligned); headings never take a slot; delete the hung today dot; unread dot 6 → 8.
- Heading spacing: keep 32 above a section head, change 8 → 12 below; day header becomes 24 above / 12 below on a 22 line (11 measured headings in 6 products: 16.8 below, 33.8 above, ink to ink). The peers' section titles are also larger than ours: audit A9, decision D7 (title 20/28·700).
- Reading text 16/24: add the role for comments, dialogs, notices, toasts, descriptions and input text (KRDS ≥ 16, M3 16/24, Seed 16, iOS web input zoom); keep 15/22 for single-line UI text.
- Minimums: nothing in Hangul below 12; tab-bar labels 11 → 12/16; wrapping text at least 1.5 (13/20, 12/18); 500 or heavier at 13 and below; raise the label scaling cap 1.5 → 2.0.
- Rail: 20 stays at 390–440 pt (Apple measured 20.00 at 414 and 440), 16 below 390, 24 from 600; the citation is corrected (20 is Apple's value from 390 pt; the market is bimodal at 16 and 24).
- Blocks: one grammar, zones eyebrow → figure → meta, left-aligned, whole block pressable with one chevron on the eyebrow row, no buttons inside, one state table (loading, empty, error, offline, long, 200 %).
- Strip: equal pitch (20 / 136.7 / 253.3), label above figure, no hairlines, 2–4 columns, 2-up reflow by a fit test.
- English: budget by role (Tag ≤ 12 characters; row L2 ≤ 2 segments beside a Tag, 3 beside a figure), truncate the flex segment only,
shortNamefor venues, relative chipsNmin, keep the 24-hour clock unless the owner rules otherwise (E4). - Motion: keep the duration and easing tokens (they equal M3 and Seed); Reduce Motion replaces with a 100–150 ms fade instead of cutting (HIG, Apple criteria); press springs to ζ 0.9 (optional).
- Accessibility gaps to close: WCAG 2.5.7 drag alternatives (P1), the iOS accessibility-label checklist (P1 at the first iOS build), 1.3.4 orientation decision, OS contrast and cross-fade settings, 3.3.7 and 3.3.8; plus the audit items: 48 targets, FAB 56, Switch 52 × 32, CTA helper 15/22, section action 15, roster names 15/22.