← Cockpit Wireframes Pages by level Navigation map
Library — chrome, surfaces, rows, labels▾
Shipped app (old grammar)Components · 공유 키트상세 페이지 — 플랫 언어Session Card · 밴드 없는 카드
홈 · Home
Home · 홈
시작 · Entry
Onboarding · 시작
ArchiveStrip Map · 세션 스트립 전체 시나리오Headers · 헤더 아이콘Card Continuity · 카드 = 상세의 예고편Session Hero · 더 조용한 공유 카드 (제안)Session Share Card · 히어로 = 공유 카드Session List Frame · 날짜 섹션 · 패널 필터 · 2+2 카드Compaction — 일정 · 코트 · 스코어카드Compaction — 클럽 상세 · 홈Compaction — 기록 · 프로필Live anchor — 진행 중 밴드Density · 카드 밀도토스트 — 리디자인 제안Club List · 클럽Clubs · 클럽클럽 상세 — 여섯 탭 플랫운영 섹션 — 플랫 목표위저드 내비게이션 — 결정 보드드래프트 복원 — 무음 복원클럽 조 편성 — 모델 결정 보드게스트 → 회원 — 심사 흐름Page polish — titled postsWeb app — desktop layoutProduct questions — openWeb app — desktop adaptation, round 2Visual tone — pure white, flat rows, one Tag, a colour budgetLibrary — chrome, surfaces, rows, labelsLibrary — controls, overlays, states, formsLibrary — page patterns, scaling, motionLibrary — additions from the pagesEmpty-once — 프로필 빈 섹션Notification groups — 할 일 밴드 그룹화Profile Cockpit · 프로필 압축Session Detail — Redesign · 일정Venue Detail — Redesign · 테니스장Profile Detail — Redesign · 프로필Tournament Detail — Redesign · 대항전Applicant Detail — Redesign · 가입 심사Canvas Migration — 회비 · 멤버 · 규칙Record Detail — Redesign · 기록Venue Finder · 코트 찾기Venue Filters · 코트 필터 재구성Notifications · 알림Panel Placement · 하단 고정
Archived — superseded by Library — chrome and navigation
Interactive kit · grounded in code

Library — chrome, surfaces, rows, labels

Flat v2 · Phase B · proposal

Component library: chrome · surfaces · rows · labels

Owner, 2026-10-09: “design all of the components first, including all their interactive behaviours, so that we have shared components used throughout the app.” Every component of library sections 3 to 6 is drawn below as one specimen block: its name and role; the anatomy at 1:1 with vertical and horizontal dimension rulers; the states row with the section 2 values applied; the variants; the behaviour note; and what it replaces. Scheme S2 Ball & Ink. The controls, overlays, states and forms are the sibling board /wireframes/flat-v2-controls; the grammar and the six screens are /wireframes/flat-v2.

How to read a block
  • Anatomy is on a 390 stage; the magenta lines are the rail (x 20, 370) and the edges the labels above name; the brackets on the right are vertical pt values, the ones under the stage horizontal.
  • States and variants sit on a mat 16 wider than the stage: 350 for a component that lives rail to rail, 390 where it is full-bleed (header, tabs, tab bar, CTA band, band). The caption prints the applied values.
  • A pressed state is drawn at its real scale and opacity; the dashed magenta outline is where the element rests. Hover carries a pointer; focus the 2px ink ring at offset 2.
  • Loading is drawn at both ends of the .6 ↔ .9 pulse.
  • Not drawn lists the states a component does not have, and why.
Open for the owner (library section 12) drawn here
  • brand-weak #EFF9B7 for 모집 중 — in the Tag matrix and on the 클럽 찾기 row
  • the figure + level-tag cluster in rank and result rows — PersonRow, ResultRow
  • the FAB collapse-on-scroll — the FAB sheet (states, transition)
  • whether 클럽's row logo keeps the identity hues at 56 r12 — Thumb, MediaRow
Resolved 2026-10-09 (owner: go with best practices and your recommendation) — library section 13
  • L1 Header — tab roots 20/28·700 at the rail; pushed screens the back glyph at the rail + the title 17/24·600 at x 56; a detail page fades the hero title into the header (150 ms)
  • L2 Day header — the today dot hangs in the gutter (x 8 to 14), 오늘 in ink 600
  • L3 Block — the standard ink focus ring, offset 2, outside; press .98 / .90
  • L4 Notice, offline strip — min 48
  • L5 Dark weak Tag tints — accepted, gated at S1
Press physics (section 2) — scale / opacity, applied in every pressed cell
buttonprimary / tonal / danger Button, the CTA button.97 / .92pillChoice, small pills.96 / .85rowMediaRow, PersonRow, Cell, Strip column, Roster cell, Avatar row (v2: .98 / .80, was .98 / .70).98 / .80iconheader actions, tab-bar items, IconButton.92 / .78fabthe FAB.96 / .90blockthe Block (the code card variant).98 / .90dimtab labels, the section action, the Notice action: the dim without a settle1 / .85
spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in / 150 ms out · hover (web) .85 · focus 2px ink ring, offset 2 · disabled .5 · reduced motion drops the settle, keeps the dim · hit slop (44 − visual) / 2
Colour read by these components — light (S2) and dark (section 1)
canvas
#FFFFFF
ink
#0D100E
secondary
#4F5851
gray100
#F2F2F2
cell hairline
#E5E5E5
edge hairline
#D4D4D4
ball
#D6F04A
block
#15181A
brand-weak
#EFF9B7
success
#00796A
success text
#014B40
success weak
#CBEBE5
warning
#F5B301
danger
#B8332E
info
#4B0082
info weak
#E8DCFB
canvas
#0F1211
surface
#1A201B
block
#232824
ink
#DFE4DC
secondary
#A9B3AB
band
#000000
success
#75D1C6
warning
#E2B13C
danger
#FA9B94
AChromeheader · tabs · tab bar · FAB · CTA band · day header · section head · band
HeaderThe screen's top bar: a title and at most two action icons; no line at rest.
56 · tab roots: title 20/28·700 at the rail · pushed screens: back = 24 arrow in a 44 target at the rail, title 17/24·600 left-aligned at x 56, one line, ellipsis · detail pages with a hero: no title at rest, the hero title fades into the header (150 ms) once it scrolls under it · ≤ 2 action icons 24 in 44 targets, gap 8, right · no divider at rest · edge hairline when scrolled · optional subtitle 13/18 secondary below a root title (height 64)
Anatomy · tab root
20346370
경기
204484410
56 header
title 20/28·700 on the rail
24 glyph in a 44 target · gap 8
no divider at rest
Press
action icon: scale 0.92 · opacity 0.78 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Edge hairline
the header draws #D4D4D4 when content passes under it: useScrollEdge(offsetY > 8), 8px hysteresis, 100 ms fade
Tab root
title 20/28·700 at the rail, no back arrow
Pushed screen
the back arrow (24 in a 44 target) sits on the rail; the title is 17/24·600, left-aligned at x 56 (the target ends at 54, +2), one line, ellipsis
Detail page
with a hero: no title at rest; the hero title fades into the header (150 ms) once it scrolls under it, together with the edge hairline · hysteresis via useCollapsingTopBar (enter at T, exit at T − 24)
Text
title truncates with an ellipsis at 1 line
Hit slop
(44 − 24) / 2 = 10 · the glyph sits in a 44 target, 10 bleeds into the margin
A11y
each icon is a button with a required aria-label (IconButton); the back button is first in the tab order
Reduced motion
drop the settle, keep the dim
Replaces
AppHeader (centered 17 title + permanent hairline) · MainTabShell title block (24/32)
Anatomy · pushed
2056370
알림
2024122420
56 header
back 24 on the rail, 44 target
title 17/24·600 at x 56, 1 line
no divider at rest
States
경기
rest bell
경기
pressed bell · scale 0.92 · opacity 0.78
경기
hover bell · web: opacity .85 + pointer
경기
focus bell · web: 2px ink ring, offset 2
경기
disabled bell · opacity .5 · inert · aria-disabled
Not drawn: loading, selected, empty — the header never skeletons (the title comes from the route) and holds no selection.
Variants
알림
pushed screen back glyph at the rail, title 17/24·600 left-aligned at x 56
9월 19일 (토)
토요일 오전 복식
detail page · at rest (no title) the hero title is on the page, not in the header (back; bookmark, more)
토요일 오전 복식
9월 19일 (토) 09:00–11:00
올림픽공원 테니스장
detail page · scrolled the hero title faded into the header + the edge hairline #D4D4D4 · 150 ms fade, hysteresis via useCollapsingTopBar
서초 올림픽공원 테니스 클럽 정기 모임 안내
pushed · long title one line, ellipsis before the action
강남 라켓클럽멤버 42명
tab root · subtitle 64 13/18 secondary under the title
경기
09:00–11:00
올림픽공원 테니스장복식NTRP 3.0–4.0
tab root · scrolled edge hairline #D4D4D4, content passes under
서초 올림픽공원 테니스 클럽 정기 모임 안내
tab root · long title one line, ellipsis
프로필
tab root · one action the rest of the row stays empty
Tabs (scope)Switches the scope of one list; content-width labels from the rail, the active one underlined.
row 44 · labels 15/22, active 600 ink + 2pt underline the label width, inactive 500 secondary · left-aligned from the rail, gap 24 · scrolls horizontally past 4 · 24 below to a title or day header, 16 to a row
Anatomy
20
예정
매치 찾기
테니스장
44 tabs row
label 15/22 · 600 ink / 500 secondary
2pt underline = label · gap 24
24 below a title (16 to a row)
Press
label: dim 0.85, no settle · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Underline
slides to the new label with the navSlide spring on selection
Scroll
horizontally scrollable past 4 labels; the edge hairline sits under the row only while content passes under it
A11y
role="tablist"; each label role="tab" with aria-selected; arrow keys move between tabs on web
Hit slop
none: the visual is already 44 ≥ 44 · the label box is the full row height; the gap 24 is shared
Reduced motion
drop the settle, keep the dim
Replaces
PillNav (both variants on list surfaces) · SegmentedTabs
States
예정
매치 찾기
테니스장
rest 매치 찾기 (selected = 600 + underline)
예정
매치 찾기
테니스장
pressed 매치 찾기 (selected = 600 + underline) · opacity 0.85, no settle
예정
매치 찾기
테니스장
hover 매치 찾기 (selected = 600 + underline) · web: opacity .85 + pointer
예정
매치 찾기
테니스장
focus 매치 찾기 (selected = 600 + underline) · web: 2px ink ring, offset 2
예정
매치 찾기
테니스장
disabled a label at opacity .5 · inert
예정
매치 찾기
테니스장
transition underline slides 예정 → 매치 찾기 (navSlide); the ghost is the origin
Not drawn: loading, empty — the labels are static copy.
Variants
우리 클럽
친구
지역
전국
전체
5 labels 기록: 전체 / 우리 클럽 / 친구 / 지역 / 전국
예정
매치 찾기
테니스장
지난 경기
취소됨
5 labels, scrolls the 5th passes the edge; the row scrolls horizontally
예정
매치 찾기
테니스장
09:00–11:00
올림픽공원 테니스장복식NTRP 3.0–4.0
scrolled edge hairline under the row, content passes under it
Tab barThe five top-level destinations; neutral, never brand-coloured.
56 + home inset 34 · 5 items · icon 24 stroke 2, label 11/14 · active = filled glyph + ink + 600, inactive = outline + secondary + 500 · no top line at rest, edge hairline while content lies below · badge = 7px danger dot (ring 1.5 canvas) or a 16-tall count tag
Anatomy
홈
클럽
경기
기록
프로필
7878787878
56 tab bar
5 × 78 · icon 24 · label 11/14
active filled ink 600 · idle 500
no line at rest
34 home inset
Press
the item: scale 0.92 · opacity 0.78 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Edge hairline
the tab bar draws #D4D4D4 when content passes under it: useScrollEdge(offsetY > 8), 8px hysteresis, 100 ms fade
Badge
a 7px danger dot with a 1.5 canvas ring, or a count tag 16 tall (12/16·700, 99+ cap); neither moves the glyph
A11y
each item aria-selected; the badge reads in the label ("경기, 읽지 않음 3")
Reduced motion
drop the settle, keep the dim
Tint
ink, never brand: the only colour on the bar is the danger dot / count tag
Replaces
the inline tab bar in (tabs)/_layout.tsx (brand tint, permanent border)
States
홈
클럽
경기
기록
프로필
rest no line: nothing runs under it
09:00–11:00
올림픽공원 테니스장복식NTRP 3.0–4.0
홈
클럽
경기
기록
프로필
content below edge hairline #D4D4D4 on the top pixel row
홈
클럽
경기
기록
프로필
pressed 기록 item · scale 0.92 · opacity 0.78
홈
클럽
경기
기록
프로필
hover 기록 item · web: opacity .85 + pointer
홈
클럽
경기
기록
프로필
focus 기록 item · web: 2px ink ring, offset 2
홈
클럽
경기
기록
프로필
selected 기록 active: filled glyph + ink + 600; 경기 falls back to outline + secondary + 500
홈
클럽
경기
기록
프로필
dot badge 클럽 · 7px danger dot, 1.5 canvas ring
홈
클럽
3
경기
99+
기록
프로필
count badge 경기 · 3, and 99+ on 기록 · count tag 16 tall
Not drawn: disabled, loading, empty — the five destinations are always present.
FABThe one coloured action of a list screen: an extended pill that collapses to a circle on scroll.
extended pill 48 · pad 0 20 · icon 20 + 8 + label 15/22·600 · ball fill, ink · 20 from the right rail, 16 above the tab bar · collapses to a 48 circle after 120px of downward scroll (150 ms) · one per screen
Anatomy
270370
매치
홈
클럽
경기
기록
프로필
100 · pad 0 2020
48 FAB · 100 × 48, r24
pad 0 20 · icon 20 + 8 + label
the one coloured action
16 above the tab bar
56 tab bar + 34 inset
Press
scale 0.96 · opacity 0.90 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2 (the ring is ink on a ball fill)
Disabled
opacity .5, not-allowed, inert · aria-disabled
Scroll
extended at rest; after 120px of downward scroll it collapses to a 48 circle (icon only) in 150 ms; expands on upward scroll or at the top; hides behind the keyboard
A11y
role button; the collapsed circle keeps the label as aria-label
Hit slop
none: the visual is already 48 ≥ 44
Reduced motion
drop the settle, keep the dim
Replaces
none (new) · the variant="fab" press physics already exists
States
매치
rest
매치
pressed scale 0.96 · opacity 0.90
매치
hover web: opacity .85 + pointer
매치
focus web: 2px ink ring, offset 2
매치
disabled opacity .5 · inert · aria-disabled
collapsed 48 circle, icon 24 only (scrolled past 120px)
Not drawn: loading, selected, empty — a FAB either exists or the screen has none (클럽 has none).
Variants
일정
홈 · 일정 the 홈 list's single coloured action
매치
경기 · 매치 the 경기 list's single coloured action
scroll transition extended → 48 circle, 150 ms; the dashed pill is where it was
CTA bandThe bottom action of a detail page or wizard: one primary on the rail.
button 52 inside 16 top/bottom + 20 sides + home inset 34 = 118 · one primary · a tonal secondary appears only in wizards and confirms (pair: tonal flex 1 + primary flex 2, gap 12) · edge hairline while content lies below
Anatomy
20370
참석하기
20350 · the button on the rail20
16 padding
52 button
350 wide on the rail
r12 · label 17/24·600 ink on ball
16 padding
34 home inset
hairline only if content under
Press
the button: scale 0.97 · opacity 0.92 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web: opacity .85 + pointer
Focus
web: 2px ink ring, offset 2 (a ball button takes the ink ring)
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
an ink spinner replaces the label at full opacity, the width held; aria-busy
Edge hairline
the band draws #D4D4D4 when content passes under it: useScrollEdge(offsetY > 8), 8px hysteresis, 100 ms fade
Pair
tonal gray100 flex 1 + primary flex 2, gap 12; tonal only in wizards and confirms; danger primary for a destructive confirm
Button
the Button itself is specified on /wireframes/flat-v2-controls; the band is its container
Replaces
BottomCtaBand (16 sides, permanent hairline)
States
참석하기
rest end of the page: no hairline
09:00–11:00
올림픽공원 테니스장복식NTRP 3.0–4.0
참석하기
content below edge hairline #D4D4D4 on the top pixel row
참석하기
pressed button · scale 0.97 · opacity 0.92
참석하기
hover button · web: opacity .85
참석하기
focus button · 2px ink ring, offset 2
참석하기
disabled button · opacity .5 · inert
loading ink spinner replaces the label, width held
Not drawn: selected, empty — a band always carries its action.
Variants
이전
다음
pair tonal flex 1 + primary flex 2, gap 12 (wizards, confirms)
09:00–11:00
올림픽공원 테니스장복식NTRP 3.0–4.0
취소
확정
pair, content below the pair with the edge hairline
Day headerOpens each day of a session list: the date and how far away it is.
32 · date 15/22·600 ink left, relative day 13/18·500 secondary right · 16 above (12 row padding + 16 + 4 = 32 seam to the text) · today dot = 6px ball with a 1px ink edge hung in the gutter at x 8 to 14, the date stays on the rail, 오늘 in ink 600
Anatomy
814370
9월 18일 (금)오늘
2020
16 above · seam 12 + 16 + 4
32 day header
dot 6 at x 8 to 14 · date on the rail
Interaction
none: a day header is a label, not a control (no press, hover or focus)
Today
the 6px ball dot with a 1px ink edge marks today only. It hangs in the gutter (x 8 to 14) so the date stays on the rail (hanging punctuation: every day header starts at x 20); "오늘" is 13/18·600 ink, not 500 secondary
A11y
role heading (level by the screen); the dot is decorative (aria-hidden)
Seam
16 above the header, 24 below a tab row; the next row starts at its own 12 padding
Replaces
SessionDayHeader
Variants
9월 19일 (토)내일
a later day date + relative day; the magenta line is the rail
9월 18일 (금)오늘
today the dot hung in the gutter (x 8 to 14), the date on the rail, 오늘 in ink 600
9월 26일 (토)다음 주
a week out the relative day is a footnote, never a Tag
Rejected: an inline dot before the date — it shifts today's date 14 off the rail.
Not drawn: states: none interactive; loading — a day header is derived from loaded rows.
Section headTitles a section and, optionally, links to the full list.
24 · title 17/24·600 · action 13/18·500 secondary + chevron 16 right · 32 above, 8 below (the edge rule trims the first row's top padding) · action target 44
Anatomy
20370
내일 일정
전체 보기
2020
32 above (content to title)
24 section head
8 below
Press
action: dim 0.85, no settle · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Hit slop
(44 − 18) / 2 = 13 · the action text is 18 tall; its target is 44
A11y
the title is a heading; the action is a link/button named "전체 보기"
Seam
the 32 above is the section seam (ink to ink); no line, no band
Replaces
SectionHeader (navTitle 17/22·700 + divider)
States
내일 일정
전체 보기
rest the action
내일 일정
전체 보기
pressed the action · opacity 0.85, no settle
내일 일정
전체 보기
hover the action · web: opacity .85 + pointer
내일 일정
전체 보기
focus the action · web: 2px ink ring, offset 2
Not drawn: disabled, loading, selected, empty — the head follows its section; an empty section renders no head.
Variants
이번 주
no action title only
내일 일정
전체 보기
action 전체 보기 ›
친구 24명
전체
short action 전체 ›
BandThe one zone change on a screen, where air is not enough.
12 tall, full-bleed #F2F2F2, 24 above and below · at most one per screen · darker than the canvas in dark mode (#000000)
Anatomy
24 above
12 band · full-bleed #F2F2F2
24 below
Interaction
none: a band is a surface, not a control
Use
a change of kind (the facts zone to the 댓글 zone); never between ordinary sections (air + title) and never as a card
Dark
goes darker than the canvas: #000000 under #0F1211
A11y
decorative (aria-hidden)
Replaces
Divider seam="section" (retired) · tinted bands
Variants
light #F2F2F2 on #FFFFFF
dark #000000 on #0F1211
Not drawn: states, loading, selected, empty — a band has none.
BSurfacesblock · strip · hero · chart · thumb · notice · avatar · avatar row
BlockThe one ink surface of a hub: a number the screen is about, pressable as a whole.
350 × 144 (home, identity) / 160 (ELO) · r16 · pad 20 · ink #15181A · eyebrow 13/18·600 white α.8 + chevron 16 α.8 at the row end · numeral 40/48·700 ball + unit 15/22·600 white on the baseline · meta 15/22·500 white α.85 · ELO adds progress 8 (ball on white α.2) + caption · identity: avatar 56, name 24/32·700 white, level tag, sub 15/22 α.85, edit icon 24 ghost-inverse top-right
Anatomy · home
2040370
내일, 9월 19일 (토)
2개 일정
첫 시작 09:00올림픽공원
20 pad310 inner20 pad
20 padding
18 eyebrow 13/18·600 α.8
8
48 numeral 40/48·700
unit 15/22·600, same baseline
8
22 meta 15/22·500 α.85
20 padding
Press
the whole block: scale 0.98 · opacity 0.90 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: the standard 2px ink ring, offset 2, outside the block — one focus recipe app-wide; ink on white is 19:1, so the change of contrast holds (WCAG 2.4.13)
Loading
a gray100 block 350 × 144/160, r16, opacity .6 ↔ .9 · aria-busy
Empty
eyebrow + "일정 없음" 24/32·700 white in the numeral row; the meta line is dropped
Content
no button inside; one block per hub; the numeral is the only ball on the screen besides the FAB
A11y
role button; the label reads the eyebrow, the numeral and the unit as one sentence
Reduced motion
drop the settle, keep the dim
Replaces
HeroCard · SummaryCard · StatGrid hero · profile-hero-card
States · home
내일, 9월 19일 (토)
2개 일정
첫 시작 09:00올림픽공원
rest
내일, 9월 19일 (토)
2개 일정
첫 시작 09:00올림픽공원
pressed scale 0.98 · opacity 0.90
내일, 9월 19일 (토)
2개 일정
첫 시작 09:00올림픽공원
hover web: opacity .85 + pointer
내일, 9월 19일 (토)
2개 일정
첫 시작 09:00올림픽공원
focus web: 2px ink ring, offset 2, outside the block
loading · .6 skeleton block, gray100 at .6 = #F7F7F7
loading · .9 the same block at .9 = #F3F3F3
내일, 9월 19일 (토)
일정 없음
empty eyebrow + "일정 없음" 24/32·700
Not drawn: disabled, selected — a block is a read-out; an unavailable one is not rendered.
Anatomy · ELO / identity
2040370
ELONTRP 3.5–4.0
1,482
마스터까지 518점
20 pad310 inner20 pad
20 padding
18 eyebrow ELO | NTRP
8
48 numeral 40/48·700
8 · progress 8 · 8
22 caption 15/22·500 α.85
20 padding
2040108370
김
김민준3.5
강남 테니스 클럽3년 차
56 avatar1220 pad
20 padding
104 inner, row starts 24 down
avatar 56 + 12 + text column
name row 32 · 4 · sub 22
edit 24 ghost at (20, 20)
20 padding
Variants
ELO · loading skeleton block 350 × 160
identity · loading skeleton block 350 × 144
ELONTRP 3.5–4.0
1,482
마스터까지 518점
ELO · pressed block · scale 0.98 · opacity 0.90
김
김민준3.5
강남 테니스 클럽3년 차
identity · pressed block · scale 0.98 · opacity 0.90
StripA row of equal figures under a block; the CellRow of the old kit, renamed.
80 · equal columns · numeral 24/32·700 ink · label 13/18·500 secondary · 1 × 24 #E5E5E5 between columns · pad 12 inside (above and below the 56 text block) · 3 or 4 columns
Anatomy
20137253370
47총 경기
68%승률
92%출석률
116.7 column116.7116.7
12 above
56 text block: 32 + 6 + 18
numeral : label = 1.85
12 below
Press
a linked column: scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Layout
equal columns on the rail (350 ÷ n); content left-aligned, 16 after each hairline; the first column sits on the rail (the group-spread law governs equal-column strips)
Loading
per column: a 24/32 numeral bar + a 13/18 label bar, gray100
A11y
a linked column is a button named "total 경기 47"; an unlinked one is plain text
Reduced motion
drop the settle, keep the dim
Replaces
StatStrip · CellRow · ActionIconRow (icons 24 + 12/16 labels)
States
47총 경기
68%승률
92%출석률
rest 승률 column (linked)
47총 경기
68%승률
92%출석률
pressed 승률 column (linked) · scale 0.98 · opacity 0.80
47총 경기
68%승률
92%출석률
hover 승률 column (linked) · web: opacity .85 + pointer
47총 경기
68%승률
92%출석률
focus 승률 column (linked) · web: 2px ink ring, offset 2
loading · .6 skeleton columns, gray100 at .6 = #F7F7F7
loading · .9 the top of the pulse: .9 = #F3F3F3
Not drawn: disabled, selected, empty — a strip with no data is not rendered (empty-once).
Variants
47총 경기
68%승률
92%출석률
3 columns 116.7 each
47총 경기
32승
15패
68%승률
4 columns 87.5 each; labels stay 1 line
일정
멤버
저장
공유
action icons (derived) the ActionIconRow face: the numeral is a 24 glyph, label 13/18
Hero (detail)The title block of a detail page: what, where, when, how much.
top 24 · eyebrow 13/18·500 secondary (a 20 row) · 4 · title 24/32·700 · 16 · three facts 17/24·500 with a 20 glyph (calendar, map-pin, card) + 8, gap 8 · 32 below
Anatomy
2048
강남 라켓클럽
올림픽공원 테니스장
9월 19일 (토) 09:00–11:00
올림픽공원 테니스장하드
무료
208
24 hero top
20 eyebrow row (13/18·500)
4
32 title 24/32·700
16
24 fact · glyph 20 + 8
8
24 fact
8
24 fact
32 below
Interaction
none as a whole; the eyebrow (the club) is the one link, pressed .85 like any text action
Glyphs
the only glyphs on the screen: calendar, map-pin, card at 20, stroke 2, in the label colour
Loading
a 13/18 line (30%), a 24/32 line (70%) and three 17/24 lines in gray100, pulse .6 ↔ .9
Text
the title is one line with an ellipsis; facts never wrap
A11y
the title is the page heading; each fact reads as "날짜, 9월 19일 (토) 09:00–11:00"
Replaces
TypeHero · SessionFactsSection lead · VenueHero (retire)
States · variants
loading · .6 skeleton at the same 160
loading · .9 the top of the pulse: .9 = #F3F3F3
강남 라켓클럽
올림픽공원 야외 테니스장 4번 코트 정기 모임 특별전
9월 19일 (토) 09:00–11:00
올림픽공원 테니스장하드
무료
long title one line, ellipsis
Not drawn: pressed, hover, focus, disabled, selected, empty — the hero is a read-out.
ChartOne line over time; scrub it to read a value.
160 · inset 16 · one ink line 2, no fill · scrub: 1px #D4D4D4 guide + 8px ink marker + the readout as an ink count tag above · empty = 48 glyph + 15/22 secondary, centred
Anatomy
36354
16318 plot16
16 inset
128 plot
one ink line, stroke 2 · no fill
16 inset
Scrub
pan along the plot: a 1px #D4D4D4 guide and an 8px ink marker follow the finger, the readout count tag (ink, 12/16·700) rides 10 above the marker; lifting the finger clears it
Reduced motion
no marker animation: it jumps to the nearest point
Loading
a gray100 160 plot, pulse .6 ↔ .9 · aria-busy
Empty
a 48 glyph (secondary) + one 15/22 secondary line, centred in the 160
A11y
role img with a summary label ("ELO 1,388 → 1,482, 12 경기"); the scrub is an extra, never the only access to the values
Color
one series, one ink line: no fill, no legend
Replaces
SparkLine (120 × 48)
States
rest the line only
1,462
scrubbing guide + 8px marker + readout tag
아직 기록이 없어요
empty 48 glyph + 15/22 secondary
loading skeleton 350 × 160, pulse .6 ↔ .9
Not drawn: pressed, hover, focus, disabled, selected — the chart is a read-out; the scrub is a pan, not a press.
Thumb / map tileThe 56 picture on a row's left: a photo, else a map, else a court tile; a club shows its logo.
56 × 56 r8 · venue photo → static map → court tile cascade · map and tile carry a 1px inset #E5E5E5 · club logo 56 r12 · loading = a skeleton square
Anatomy
20
서
56165616561656
56 × 56
photo · map · court tile r8
club logo r12 + monogram
Cascade
the venue photo when it exists, else the static map tile, else the flat court tile (hard #089AC3 · clay #8D3D00 · grass #83971F); a club shows its logo
Edge
map and court tiles carry a 1px inset #E5E5E5 so a pale map never melts into the canvas
Loading
a gray100 56 square (r8; r12 for a logo), pulse .6 ↔ .9
Interaction
none of its own: the row presses (.98/.80)
A11y
decorative (aria-hidden) beside the row text; a logo-only context keeps the club name as the label
Replaces
VenueTile (aspect-bound r12) · ClubAvatar
Variants
photo 56 r8, no inset edge
map 56 r8, 1px inset #E5E5E5
court tile · hard 56 r8, 1px inset #E5E5E5
court tile · clay
court tile · grass
서
club logo r12 the club's hue + monogram
loading skeleton square 56
Not drawn: pressed, hover, focus, disabled, selected, empty — a thumb is inert; a row without a picture shows the court tile.
NoticeAn inline message with a tone: what changed, what to do.
inline · r12 · pad 12 16 · min height 48 · weak tone fill (success #CBEBE5, warning #F5B301, danger #B8332E with white, info #E8DCFB, neutral gray100) · icon 20 + 12 · title 15/22·600 + body 15/22·400 · optional text action 15/22·600 right · optional close 24 in 44 · never a surface of its own on a hub
Anatomy
20370
일정이 변경됐어요시작 시간이 10:00으로 바뀌었어요.
16201216
12 padding
22 title 15/22·600
22 body 15/22·400
12 padding
Press
the text action: dim 0.85, no settle · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Dismiss
the close (24 glyph in a 44 target) animates the height to 0 in 150 ms; reduced motion removes it at once
Tones
success, warning, danger, info, neutral — the fills and texts are the Tag weak table; danger carries white text
Placement
inline in the flow, 20 sides; never a card, never a surface of its own on a hub; the offline strip is the neutral Notice pinned under the header
A11y
role status (alert for danger); the close is a button with aria-label "닫기"
Height
68 with a body; a single line, and the offline strip, is min 48 — resolved 2026-10-09 (library section 13, L4): pad 12 around one 22 line gives 46, off the 4pt grid; 48 equals the Cell
Replaces
Banner · Callout · Well · NetworkBanner (all $primarySubtle)
States
일정이 변경됐어요시작 시간이 10:00으로 바뀌었어요.
rest info · title + body
일정이 변경됐어요시작 시간이 10:00으로 바뀌었어요.
확인
pressed the action · opacity 0.85, no settle
일정이 변경됐어요시작 시간이 10:00으로 바뀌었어요.
확인
hover the action · web: opacity .85 + pointer
일정이 변경됐어요시작 시간이 10:00으로 바뀌었어요.
확인
focus the action · web: 2px ink ring, offset 2
Not drawn: disabled, loading, selected, empty — a notice is shown or absent.
Variants
입금을 확인했어요회비가 완납 처리됐어요.
success #CBEBE5 · text #014B40
마감이 가까워요내일 18:00에 모집이 끝나요.
warning #F5B301 · ink
저장하지 못했어요네트워크 연결을 확인해 주세요.
danger #B8332E · white
일정이 변경됐어요시작 시간이 10:00으로 바뀌었어요.
info #E8DCFB · text #4B0082
초대 코드를 복사했어요클럽에 새 멤버를 초대해 보세요.
neutral gray100 · ink
참석 확인이 필요해요
확인
with action a text action 15/22·600 on the right
일정이 변경됐어요시작 시간이 10:00으로 바뀌었어요.
with close 24 glyph in a 44 target
오프라인 상태예요
다시 시도
single line · offline strip min 48 (resolved)
결제에 실패했어요
재시도
action + close single line, danger
AvatarA person: initials on gray100, or their photo.
circle · 40 (rows, default) / 48 (roster) / 56 (identity) · initials 15/22·600 (40), 17/24·600 (48), 20/28·700 (56) ink on gray100 · photo when present · club logo r12 (see Thumb)
Anatomy
20
김
이
박
40485640 photo48 photo56
40 · 48 · 56 circles
bottoms aligned for the drawing
Scale
40 in rows (default) · 48 in the roster · 56 on the identity block; 32 only inside Tag-sized contexts (DM thread avatars)
Initials
the first character of the name, 15/22·600 (40) · 17/24·600 (48) · 20/28·700 (56), ink on gray100 (never a colour hash)
Photo
cover-fit in the circle; falls back to initials while loading or on error
Interaction
none of its own; the row or roster cell presses
A11y
decorative beside the name; alone it carries the person's name as its label
Replaces
AvatarBubble (default 32, mint) · ClubLogoBubble
Variants
김
40 · initials 15/22·600
김
48 · initials 17/24·600
김
56 · initials 20/28·700
40 · photo cover-fit
48 · photo cover-fit
56 · photo cover-fit
Not drawn: pressed, hover, focus, disabled, loading, selected, empty — inert; a missing photo is initials.
Avatar rowWho is in it: a row of faces and how many more.
40 × n, gap 8, from the rail · '+N' as a 40 gray100 circle 13/18·600 · 350 holds 7 circles (with an overflow the 7th is the +N)
Anatomy
20
최
+19
40840840+N
40 avatars · gap 8
+N: 40 gray100 circle
Press
the whole row: scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Overflow
six faces + "+19" fill 7 slots of the 350 rail; with no overflow the row simply ends
Empty
renders nothing (empty-once): no zero-state row
A11y
one button for the row ("친구 24명 보기"); the circles are decorative
Replaces
AvatarStack (overlap −6)
States
최
+19
rest the row
최
+19
pressed the row · scale 0.98 · opacity 0.80
최
+19
hover the row · web: opacity .85 + pointer
최
+19
focus the row · web: 2px ink ring, offset 2
loading 6 skeleton circles, pulse .6 ↔ .9
Not drawn: disabled, selected, empty — see Behaviour (an empty row is not rendered).
Variants
3 people no overflow
최
+19
6 + overflow +19
CRowsMediaRow · PersonRow · Cell · Roster cell · ResultRow
MediaRowA session or a club in a list: a 56 picture, two lines, one trailing figure.
80 · pad 12 · thumb 56 r8 + 12 · L1 17/24·600 ink (time, tabular) · L2 15/22·400 secondary (venue, MetaSegments) · trailing: count 15/22·600 ink or one Tag, centred on L1 · no divider
Anatomy
207688370
09:00–11:00
4/8
올림픽공원 테니스장복식NTRP 3.0–4.0
205612282 text20
12 padding
56 thumb · text block 50
L1 24 (starts 4 in) · 4 · L2 22
trailing centred on L1
12 padding
Press
scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Selected
a trailing check 16 in ink (no fill); L1 is already 600 at rest, so the weight does not move · aria-selected
Loading
a 56 square + a 17/24 line at 60% + a 15/22 line at 40%, gray100, pulse .6 ↔ .9 · aria-busy
Trailing
exactly one slot, centred on L1: a count (4/8) or one Tag or the selected check; LIVE is a Tag
Pitch
80 each, no divider and no fill between rows: the 12 padding is the rhythm
A11y
role button (list item); the label reads L1, L2 and the trailing together
Replaces
SessionCardV2 · SessionCompactRow · ClubCard · DirectoryVenueCard on list surfaces
States
09:00–11:00
4/8
올림픽공원 테니스장복식NTRP 3.0–4.0
rest trailing count
20:00–22:00
마감 임박
반포 한강 테니스장혼복NTRP 3.5–4.5
rest · Tag trailing one Tag (마감 임박)
09:00–11:00
4/8
올림픽공원 테니스장복식NTRP 3.0–4.0
pressed scale 0.98 · opacity 0.80
09:00–11:00
4/8
올림픽공원 테니스장복식NTRP 3.0–4.0
hover web: opacity .85 + pointer
09:00–11:00
4/8
올림픽공원 테니스장복식NTRP 3.0–4.0
focus web: 2px ink ring, offset 2
09:00–11:00
4/8
올림픽공원 테니스장복식NTRP 3.0–4.0
disabled opacity .5 · inert · aria-disabled
09:00–11:00
올림픽공원 테니스장복식NTRP 3.0–4.0
selected trailing check 16, no fill · aria-selected
loading · .6 56 square + 60% / 40% lines
loading · .9 the top of the pulse: .9 = #F3F3F3
Not drawn: empty — a list with no rows shows EmptyState (states board), never an empty row.
Variants
09:00–11:00
4/8
올림픽공원 테니스장복식NTRP 3.0–4.0
court tile · count the 경기 / 홈 session row
20:00–22:00
마감 임박
반포 한강 테니스장혼복NTRP 3.5–4.5
map tile · Tag warning Tag trailing
09:00–11:00
LIVE
올림픽공원 테니스장복식NTRP 3.0–4.0
photo · LIVE the Live tag trailing
서
서초 테니스 모임
3
서초구멤버 42NTRP 3.0–4.0
club logo r12 클럽 row: unread count tag
서
서초 테니스 모임
모집 중
서초구멤버 42NTRP 3.0–4.0
club · 클럽 찾기 모집 중 brand-weak Tag
09:00–11:00
4/8
올림픽공원 테니스장복식NTRP 3.0–4.0
20:00–22:00
마감 임박
반포 한강 테니스장혼복NTRP 3.5–4.5
서
서초 테니스 모임
3
서초구멤버 42NTRP 3.0–4.0
pitch 80 each · no divider, no fill
PersonRowA person in a list: members, dues, rankings, results.
68 · avatar 40 + 12 · L1 15/22·600 · L2 13/18·500 secondary · trailing: amount / score 15/22·700 tabular, an ELO figure + level tag cluster, or one Tag · rank variant: 13/18·600 secondary rank in a 24 column before the avatar
Anatomy
206072370
박
박지훈
1,5473.5
52경기
204012298 text20
12 padding
44 text block (22 + 4 + 18)
avatar 40, centred; trailing on L1
12 padding
Press
scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Selected
a trailing check 16 (no fill) in place of the trailing slot · aria-selected
Loading
a 40 circle + a 15/22 line at 50% + a 13/18 line at 30%, gray100, pulse .6 ↔ .9
Trailing
one slot: an amount or score (15/22·700), a figure + level tag cluster (counted as one), or one Tag
Rank
the rank sits in a 24 column before the avatar (13/18·600 secondary); the avatar moves to x = 44
A11y
role button; the label reads the name, the line 2 and the trailing together
Replaces
PersonRow · MemberRow · DuesRow · LeaderboardRow · RankRow
States
박
박지훈
1,5473.5
52경기
rest · cluster ELO 15/22·700 + level tag
이
이서연
30,000원
9월 회비
rest · amount 15/22·700 tabular
박
박지훈
1,5473.5
52경기
pressed scale 0.98 · opacity 0.80
박
박지훈
1,5473.5
52경기
hover web: opacity .85 + pointer
박
박지훈
1,5473.5
52경기
focus web: 2px ink ring, offset 2
박
박지훈
1,5473.5
52경기
disabled opacity .5 · inert · aria-disabled
박
박지훈
52경기
selected trailing check 16, no fill
loading · .6 40 circle + two lines
loading · .9 the top of the pulse: .9 = #F3F3F3
Not drawn: empty — a list with no people shows EmptyState (states board).
Variants
김
김민준
호스트
호스트
Tag trailing the host role, one Tag
1
이서연
1,6234.0
47경기
2
박
박지훈
1,5473.5
52경기
3
최
최유나
1,4983.0
38경기
rank variant rank in a 24 column · avatar at x = 44 · 68 each
rank · skeleton rank + circle + two lines
정우진
1,5473.5
29경기
photo avatar 40 photo
CellA label and its value: facts, settings, pickers.
48 · label 15/22·400 secondary left · value 15/22·500 ink right · variants: nav (chevron 16), toggle (Switch), value + chevron, destructive (label danger), input (value editable) · cell hairline #E5E5E5 rail to rail between cells of one group, none after the last
Anatomy
20370
종목복식
대상NTRP 3.0–4.0
20hairline 1px #E5E5E5, x 20 → 37020
48 cell
label 15/22·400 secondary, left
value 15/22·500 ink, right
48 cell · last of the group
no hairline after it
Press
nav / value cells: scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Toggle
the cell does not press: the Switch is the target and its movement is the feedback (150 ms); role switch + aria-checked
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Selected
a trailing check 16 + a 600 ink label (no fill) · aria-selected
Loading
a label line at 40% + a value line at 20%, gray100, pulse .6 ↔ .9
Input
the value is editable in place; the caret ends it; focus = a 2px ink ring
Hairline
between cells of one group only; a group of one has none
Replaces
FactRow · ListRow (34 files) · SettingsRow · AdminNavRow · PickerRow
States
대상NTRP 3.0–4.0
rest plain: label + value
알림 설정
pressed nav cell · scale 0.98 · opacity 0.80
알림 설정
hover nav cell · web: opacity .85 + pointer
알림 설정
focus nav cell · web: 2px ink ring, offset 2
알림 설정
disabled nav cell · opacity .5 · inert · aria-disabled
복식
selected trailing check 16 + 600 ink label
loading · .6 label 40% + value 20%
loading · .9 the top of the pulse: .9 = #F3F3F3
Not drawn: empty — a group with no cells is not rendered.
Variants
종목복식
plain label + value
알림 설정
nav label + chevron 16
지역
서울 송파구
value + chevron opens a picker sheet
푸시 알림
toggle · off Switch 48 × 28, #D4D4D4 track
푸시 알림
toggle · on ink track, 24 white thumb
클럽 탈퇴
destructive label in danger #B8332E
닉네임
민준
input value editable, caret at the end
종목복식
대상NTRP 3.0–4.0
호스트김민준
참가비무료
group cell hairlines between, none after the last
Roster cellOne participant in the 4-up roster of a session; an empty slot is a dashed circle.
100 pitch, 4-up · avatar 48 · name 13/18·500 (a 20 row) · level tag 24 · empty slot = a 48 dashed #D4D4D4 circle · row gap 16
Anatomy
20120220320
김민준
3.5
이서연
4.0
박
박지훈
3.5
48524852485248
100 roster cell
avatar 48 · 4 · name (row 20) · 4
level tag 24 · pitch 100
Press
the cell: scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Interaction
a filled cell opens the person's profile; an empty slot is inert (the host's invite sits in the section head)
Names
person names are never cut: the name row is 20 with overflow visible; the pitch of 100 leaves room for four Korean characters
Grid
4-up on x = 0 / 100 / 200 / 300 (the first column on the rail); rows 16 apart
A11y
role button, label "김민준, NTRP 3.5"; the empty slot is aria-hidden
Replaces
ParticipationSection rows
States
김민준
3.5
이서연
4.0
박
박지훈
3.5
rest 이서연
김민준
3.5
이서연
4.0
박
박지훈
3.5
pressed 이서연 · scale 0.98 · opacity 0.80
김민준
3.5
이서연
4.0
박
박지훈
3.5
hover 이서연 · web: opacity .85 + pointer
김민준
3.5
이서연
4.0
박
박지훈
3.5
focus 이서연 · web: 2px ink ring, offset 2
empty slots 48 dashed #D4D4D4 circles, inert
Not drawn: disabled, loading, selected — the roster follows the session's own state.
Variants
김민준
3.5
이서연
4.0
박
박지훈
3.5
최유나
3.0
정
정우진
3.5
한
한소희
4.0
two rows 6 filled + 2 empty, row gap 16
ResultRowA played match: opponents, date, the score and who won.
PersonRow with the opponents in L1, the date in L2, the score 17/24·700 tabular + a 승/패 Tag (success weak / neutral weak) as the one trailing cluster · 68
Anatomy
20370
박
박지훈, 최유나
6–4 6–3승
9월 14일 (일)
204012298 text · cluster at the right20
12 padding
44 text block · opponents, date
avatar 40, centred · score + 승/패 Tag
12 padding
Press
scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Cluster
the score and the Tag count as one trailing slot (the open question of section 12: the figure + level-tag cluster in rank/result rows)
Tags
승 = success weak (#CBEBE5 / #014B40); 패 = neutral weak (gray100 / #4F5851); never a solid red for a loss
Loading
the PersonRow skeleton
A11y
role button; the label reads "박지훈, 최유나, 9월 14일, 6–4 6–3, 승"
Replaces
completed-row · match rows
States
박
박지훈, 최유나
6–4 6–3승
9월 14일 (일)
rest · win success weak 승
이
이서연, 정우진
3–6 4–6패
9월 7일 (일)
rest · loss neutral weak 패
박
박지훈, 최유나
6–4 6–3승
9월 14일 (일)
pressed scale 0.98 · opacity 0.80
박
박지훈, 최유나
6–4 6–3승
9월 14일 (일)
hover web: opacity .85 + pointer
박
박지훈, 최유나
6–4 6–3승
9월 14일 (일)
focus web: 2px ink ring, offset 2
loading the PersonRow skeleton
Not drawn: disabled, selected, empty — results are history; an empty list is the states board's EmptyState.
DLabelsTag · Level tag · Count tag · Live tag
TagA status word on an object: passive, one or two words.
sm 24 r6 13/18·600 pad 0 8 (default) · md 28 r8 15/22·600 pad 0 10 (hero and identity only) · passive (no press, no aria-pressed) · ≤ 1 per row, ≤ 2 per object · label 1–2 words
Anatomy
2028
완납
88
24 sm · r6 · pad 0 8
2030
완납
1010
28 md · r8 · pad 0 10
Interaction
none: a Tag is passive (no press, no hover, no focus, no aria-pressed); a pressable status is a Choice (controls board)
Disabled
a Tag inherits its row: the host row at .5 dims its Tag with it
Budget
≤ 1 per row, ≤ 2 per object; label 1–2 words; colour is never the only carrier (the word is always there)
Sizes
sm in rows and cards; md only in the hero and the identity block
A11y
plain text in the row's label; no role of its own
Replaces
Badge (73 files) · hand-drawn pills (9) · ContextChip · MarkerPill · ReliabilityPill · TierRangeBadge in rows
Variants
tone · fill | textsm weaksm solidmd weakmd solid
neutral #F2F2F2 | #4F5851 · solid #4F5851 | white
role, pending, metadata
호스트
호스트
호스트
호스트
brand #EFF9B7 | ink · solid #D6F04A | ink
invitation (brand-weak; the ball fill is its solid, derived)
모집 중
모집 중
모집 중
모집 중
success #CBEBE5 | #014B40 · solid #00796A | white
paid, confirmed, win
완납
완납
완납
완납
warning #F5B301 | ink
deadline, unpaid (one fill, both tiers)
마감 임박
마감 임박
마감 임박
마감 임박
danger #B8332E | white
cancelled, overdue (one fill, both tiers)
취소됨
취소됨
취소됨
취소됨
info #E8DCFB | #4B0082 · solid #4B0082 | white
waitlist, notice
예비
예비
예비
예비
Not drawn: pressed, hover, focus, selected, loading, empty — passive; the row owns them.
Level tagThe NTRP / tier numeral as a ball.
24 circle · ball fill · 13/18·700 ink numeral · 1px #D4D4D4 edge on white
Anatomy
2044
3.03.54.04.55.0
24824
24 circle · ball · 13/18·700
Interaction
none: passive like a Tag; the tier explainer opens from the row or the profile, not from the ball
Numeral
NTRP (3.0 – 5.0) or the tier numeral; "3.5" fits the circle at 13/18·700 with 2px to spare
Edge
the 1px #D4D4D4 edge only on white; on the ink block the ball reads without it (dark: white α.14)
A11y
plain text, read as "NTRP 3.5" in the row's label
Replaces
TierRangeBadge · EloTierBadge chrome
Variants
3.03.54.04.55.0
on white 1px #D4D4D4 edge
3.03.54.04.55.0
on the ink block no edge: the ball reads on #15181A
1,5473.5
in a row cluster figure 15/22·700 + level tag, one trailing slot
3.03.54.04.55.0
dark edge white α.14
Count tagHow many unread or how many: a number, capped at 99+.
20 tall, min 20, r full, 12/16·700 · danger solid (unread) or neutral · 99+ cap · 16 tall on the tab bar
Anatomy
20
11299+
2026.433.6
20 tall · min 20 · pad 0 6
Interaction
none: passive; the row or the tab it sits on presses
Cap
values above 99 print "99+"; a single digit stays a circle (min-width = height)
Tones
danger solid = unread / needs action · neutral gray100 = a plain count · ink = the chart readout
A11y
read in the host's label ("알림 3개 읽지 않음"); never alone
Replaces
CornerCountBadge
Variants
11299+
danger · unread #B8332E, white 12/16·700
11299+
neutral gray100, secondary
11299+
ink · readout the chart scrub readout
11299+
tab-bar · 16 tall 1.5 canvas ring, pad 0 4
Not drawn: pressed, hover, focus, disabled, loading, selected, empty — passive; absent when zero.
Live tagA match or session is being played now.
a danger solid Tag with a 6px white pulse dot (1600 ms) before 'LIVE' · reduced motion: a static dot
Anatomy
2028
LIVE
868
24 Tag sm · danger solid
Pulse
the white dot carries a ring that grows 1 → 2.4× while fading .45 → 0, looping every 1600 ms (UI thread)
Reduced motion
the ring goes, the dot stays: a static white dot
Interaction
none: a Tag; on a row it is the one trailing element
A11y
read as "진행 중"; the pulse is decorative
Replaces
SessionLiveBand on rows · PulseDot
Variants
LIVE
pulse · 0% ring at the dot, α.45
LIVE
pulse · 35% ring 1.7×, α.22
LIVE
pulse · 70% → 100% ring 2.4×, α0 (invisible)
LIVE
reduced motion static dot, no ring
Not drawn: pressed, hover, focus, disabled, loading, selected, empty — passive.
EDark modethe same markup in the dark values of section 1: canvas #0F1211, surface #1A201B, block #232824, ink #DFE4DC, secondary #A9B3AB
경기
Header ink #DFE4DC on #0F1211 · edge white α.14
홈
클럽
경기
3
기록
프로필
Tab bar ink active, secondary idle · dot + count badge · edge hairline
내일, 9월 19일 (토)
2개 일정
첫 시작 09:00올림픽공원
Block #232824, ball numeral, white α inks unchanged
09:00–11:00
4/8
올림픽공원 테니스장복식NTRP 3.0–4.0
20:00–22:00
마감 임박
반포 한강 테니스장혼복NTRP 3.5–4.5
MediaRow rest · count; the court tile keeps its colours
종목복식
지역
서울 송파구
복식
클럽 탈퇴
Cell group cell hairline white α.09, destructive #FA9B94
호스트모집 중완납마감 임박취소됨예비
호스트모집 중완납마감 임박취소됨예비
3.53LIVE
Tag row weak, then solid
Accepted, gated at S1 (library section 13, L5): the dark weak Tag fills are the tone at 16% over the canvas (brand = the ball, success #75D1C6, info #C9B2F2); warning and danger solids use their section 1 colours (#E2B13C, #FA9B94) with ink text; the neutral weak fill is the surface #1A201B and the solid is the secondary #A9B3AB. Section 1 gives the text colours only, so these tints and the dark info text #C9B2F2 are derived values: the owner accepted them on 2026-10-09 and they ship only behind the S1 dark contrast gate.
FVerificationevery component root on this board measured in a real browser (Chromium, window 1800): offsetHeight against the library table
componentmarkspec heightmeasuredinstancespasslibrary row
Headerheader565615pass§3 Header
Header · subtitleheader-sub64641pass§3 Header (height 64)
Tabs (scope)tabs444410pass§3 Tabs
Tab bartabbar565611pass§3 Tab bar
Tab bar + home insettabbar-wrap909011pass§3 Tab bar (56 + 34)
FABfab484810pass§3 FAB
CTA bandcta-band11811810pass§3 CTA band (16 + 52 + 16 + 34)
CTA buttoncta-btn525212pass§3 CTA band (button 52)
Day headerday32324pass§3 Day header
Section headsection24248pass§3 Section head
Bandband12123pass§3 Band
Block · homeblock-home1441449pass§4 Block
Block · ELOblock-elo1601603pass§4 Block
Block · identityblock-id1441443pass§4 Block
Stripstat808010pass§4 Strip
Hero (detail)hero1601604pass§4 Hero (24 above and 32 below are gaps)
Chartchart1601605pass§4 Chart
Thumb / map tilethumb565634pass§4 Thumb
Notice · title + bodynotice-2686811pass§4 Notice (12 + 22 + 22 + 12)
Notice · single linenotice-148483pass§4 Notice (min 48, §13 L4)
Avatar 40avatar-40404068pass§4 Avatar
Avatar 48avatar-48484836pass§4 Avatar
Avatar 56avatar-5656566pass§4 Avatar
Avatar rowavatar-row40408pass§4 Avatar row
MediaRowrow-media808025pass§5 MediaRow
PersonRow / ResultRowrow-person686823pass§5 PersonRow (§15: pitch 68)
Cellcell484825pass§5 Cell
Roster cellroster-cell10010032pass§5 Roster cell
Tag smtag242444pass§6 Tag
Tag mdtag-md282813pass§6 Tag
Level taglevel242455pass§6 Level tag
Count tagcount202016pass§6 Count tag
Count tag · tab barcount-1616166pass§3 Tab bar (count tag 16 tall)
Switch (in Cell)switch28282pass§7 Switch
Block progressprogress882pass§4 Block (progress 8)
Roster 4-up rowroster1001008pass§5 Roster cell (pitch 100)
Hero fact rowhero-fact24246pass§4 Hero (three facts 17/24)
Tabs underlineunderline2211pass§3 Tabs (2pt underline)
Strip hairlinehair242422pass§4 Strip (1 × 24)
Cell hairlinehair-cell117pass§5 Cell (1px #E5E5E5)
Edge hairlinehair-edge119pass§3 (1px #D4D4D4)
Tab-bar dot badgedot772pass§3 Tab bar (7px dot)
Today dottoday-dot662pass§3 Day header (6px dot)
Home insetinset343411pass§3 Tab bar / CTA band (34)
Action glyphicon-24242496pass§1 icons 24 (actions)
Hero / inline glyphicon-2020206pass§1 icons 20 (hero facts)
Method offsetHeight of every element carrying a component mark (data-g), read by a scratch CDP/DOM script in headless Chromium at window 1800: 718 component roots across the anatomy stages, the states, the variants and the dark strip, 0 off the library height. offsetHeight ignores transforms, so a pressed (scaled) cell is held to the same layout height as its rest cell.
Widths Block (home, ELO, identity), Strip, Chart and Notice measure 350 wide: 0 misses. The FAB measures 100 wide with a 2-character label (min-width 100).
Grid Of the 46 distinct component marks, 38 have a height that is a multiple of 4; the 8 that are not are the hairlines (1), the tab underline (2), the two dots (6 and 7) and the three heights that carry the 34 iOS home inset (34, and the 90 and 118 it makes up). Off-grid marks: hair-edge, underline, tabbar-wrap, inset, dot, cta-band, today-dot, hair-cell.
Rulers 79 vertical ruler notes checked in the same run for text beyond the ruler width, text taller than its bracket and overlaps between neighbouring notes: 0 issues.
Header (L1) Left edge and type read from the rendered titles: the 4 pushed-screen titles sit at x 56 in 17/24·600 (1 of them the long title, ellipsized without moving the 44 targets); the 11 tab-root titles sit at x 20 in 20/28·700; the back glyph's left edge is x 20 in all 5 headers with a back arrow. 20 readings, 0 off.
Day header (L2) The 2 today dots measure x 8 to 14 (6 wide) with the date at x 20, and the dot and the date centre on the same line (18 / 18); 오늘 renders 600 in ink (rgb(13, 16, 14)). 4 day headers on the board, 2 with the dot.
Block (L3, press) All 15 drawn focus rings are 2px ink at offset 2 (0 ball rings, 0 inside the block); every pressed Block (3) renders scale(0.98) at opacity 0.9.
Notice (L4) The 3 single-line Notices (the offline strip included) measure 48.
Where the drawing departs from, or has to interpret, the library text
Header with back and a title
Resolved 2026-10-09 (library section 13, L1). Section 3 gave the back arrow "at the rail" and the title "at the rail"; both cannot sit there. Ruled: a tab root keeps the title 20/28·700 at the rail; a pushed screen draws the back glyph at the rail and the title 17/24·600 left-aligned at x 56 (one line, ellipsis); a detail page with a hero has no title at rest and the hero title fades into the header (150 ms, hysteresis via useCollapsingTopBar) together with the edge hairline.
Day header today dot
Resolved (L2). Hung in the gutter (6px at x 8 to 14), the date stays on the rail, 오늘 in ink 600. The inline dot the spec first described is rejected: it shifts today's date 14 off the rail.
MediaRow selected
Section 5 says "selected = trailing check 16 + L1 600", but L1 is already 17/24·600 at rest; drawn as the check alone, the weight unchanged.
Block press opacity
Resolved (library section 4, section 13): the Block presses .98 / .90, the code card variant in packages/ui/src/pressable-visual.ts; no new physics entry (it was .98 / .92 in the first draft).
Block focus ring
Resolved (L3): the standard 2px ink ring, offset 2, outside the block. One focus recipe app-wide; ink on white is 19:1. The ball ring drawn inside the ink (offset −6) is retired.
Notice single line
Resolved (L4): min 48 for a Notice and the offline strip. Pad 12 around one 22 line gives 46, off the 4pt grid; 48 equals the Cell.
Strip action-icon face
Section 4 says the Strip replaces ActionIconRow but gives no geometry for it; drawn as the stat strip with a 24 glyph in the numeral position (derived).
Dark Tag tints
Accepted, gated at S1 (L5). Section 1 gives the dark text colours only; the weak fills (tone at 16% over the canvas) and the info text #C9B2F2 are derived, and ship only behind the S1 dark contrast gate.
Brand Tag solid
Section 6 lists the brand tone as weak only; the solid column draws the ball fill with ink text (the primary), derived.
Strip "pad 12 inside"
Read as the 12 above and below the 56 text block (80 = 12 + 56 + 12), the same air a row has; the columns are equal (350 ÷ n) and left-aligned, 16 after each hairline.
A11y and behaviour lines
Section 2 gives the semantics (aria-selected / checked / pressed, hit slop, durations); the per-component role names and spoken labels in the Behaviour notes are drafted here, not quoted from the spec.
Stage width
The states and variants are 350 wide (rail to rail) or 390 where the component is full-bleed; the anatomy is always on the 390 stage so the rail (20) reads.
Spec (grounding, anatomy)

Proposal (2026-10-09), Phase B of the flat grammar v2 migration: docs/design/flat-v2-component-library-2026-10-09.md sections 3 to 6, drawn component by component. Each block: the name and role, the anatomy at 1:1 with vertical and horizontal dimension rulers, the states row (rest, pressed with the dim and scale of section 2 applied, hover, focus, disabled, loading, selected, empty where each exists), the variants, the behaviour note printed from section 2 and the replaces line; then a dark-mode strip and the DOM verification of every component height against the library table. Chrome: Header, Tabs, Tab bar, FAB, CTA band, Day header, Section head, Band. Surfaces: Block, Strip, Hero, Chart, Thumb, Notice, Avatar, Avatar row. Rows: MediaRow, PersonRow, Cell, Roster cell, ResultRow. Labels: Tag, Level tag, Count tag, Live tag. Built from local primitives only (Fq*): no WFPhone, WFCard, WFChip or WFBadge. The controls, overlays, states and forms are the sibling board flat-v2-controls.

route · packages/ui/src/pressable.tsx · packages/ui/src/pressable-visual.ts · packages/ui/src/motion.ts · packages/ui/src/app-header.tsx · packages/ui/src/pill-nav.tsx · packages/ui/src/bottom-cta-band.tsx · packages/ui/src/section-header.tsx · packages/ui/src/list-row.tsx · packages/ui/src/person-row.tsx · packages/ui/src/fact-row.tsx · packages/ui/src/badge.tsx · packages/ui/src/avatar-bubble.tsx · packages/ui/src/stat-strip.tsx · packages/ui/src/cell-row.tsx · packages/ui/src/banner.tsx · packages/ui/src/empty-state.tsx · packages/app/src/presentation/components/sessions/venue-tile.tsx · apps/mobile/app/(tabs)/_layout.tsx grounded in 18 source file(s)
packages/ui/src/pressable.tsxpackages/ui/src/pressable-visual.tspackages/ui/src/motion.tspackages/ui/src/header.tsxpackages/ui/src/pill-nav.tsxpackages/ui/src/bottom-cta-band.tsxpackages/ui/src/section-header.tsxpackages/ui/src/person-row.tsxpackages/app/src/presentation/components/person-row.tsxpackages/ui/src/fact-row.tsxpackages/ui/src/badge.tsxpackages/ui/src/avatar.tsxpackages/ui/src/stat-strip.tsxpackages/ui/src/cell-row.tsxpackages/ui/src/banner.tsxpackages/ui/src/empty-state.tsxpackages/app/src/presentation/components/sessions/venue-tile.tsxapps/mobile/app/(tabs)/_layout.tsx