@godxjp/ui 24.1.0 → 25.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/components/data-display/badge.js +3 -0
  2. package/dist/components/data-entry/select.d.ts +26 -0
  3. package/dist/components/data-entry/select.js +16 -1
  4. package/dist/components/general/logo.d.ts +16 -0
  5. package/dist/components/general/logo.js +10 -2
  6. package/dist/components/layout/auth-identity.d.ts +1 -1
  7. package/dist/components/layout/auth-identity.js +5 -2
  8. package/dist/components/navigation/tabs.js +24 -6
  9. package/dist/contracts/measurement.json +1 -1
  10. package/dist/email/tokens.generated.d.ts +1 -1
  11. package/dist/email/tokens.generated.js +1 -1
  12. package/dist/props/components/layout.prop.d.ts +32 -1
  13. package/dist/props/registry.d.ts +4 -0
  14. package/dist/props/registry.js +5 -0
  15. package/dist/props/vocabulary/interaction.prop.d.ts +10 -1
  16. package/dist/styles/control.css +5 -1
  17. package/dist/styles/data-display-layout.css +16 -5
  18. package/dist/styles/data-entry-layout.css +5 -0
  19. package/dist/styles/layout.css +5 -0
  20. package/dist/styles/logo-layout.css +30 -3
  21. package/dist/styles/motion.css +4 -0
  22. package/dist/styles/shell-layout.css +11 -4
  23. package/dist/styles/text-layout.css +5 -0
  24. package/dist/tokens/components/logo.css +8 -0
  25. package/dist/tokens/components/shell.css +5 -3
  26. package/dist/tokens/derived.css +8 -8
  27. package/dist/tokens/foundation.css +2 -2
  28. package/docs/DESIGN-AUTHORITY.md +36 -22
  29. package/docs/FRAME-A11Y-CI.md +165 -24
  30. package/docs/data-display/popover.tsx +3 -1
  31. package/docs/data-entry/date-picker.tsx +3 -1
  32. package/docs/data-entry/select-matrix.tsx +3 -1
  33. package/docs/data-entry/select.tsx +4 -1
  34. package/docs/feedback/dialog.tsx +4 -1
  35. package/docs/feedback/sheet.tsx +2 -1
  36. package/docs/foundation/colors.tsx +4 -1
  37. package/docs/general/logo.tsx +63 -0
  38. package/docs/general/typography.tsx +31 -0
  39. package/docs/layout/auth-identity.tsx +34 -0
  40. package/docs/navigation/dropdown-menu.tsx +8 -1
  41. package/docs/showcase/acme-website.tsx +3 -1
  42. package/docs/showcase/case4-login.tsx +2 -2
  43. package/docs/showcase/case5-shift-calendar.tsx +5 -3
  44. package/docs/showcase/case6-agency-handy.tsx +13 -9
  45. package/docs/showcase/futurelastic-web.tsx +11 -3
  46. package/docs/showcase/table-bulk-actions.tsx +13 -2
  47. package/docs/showcase/table-density.tsx +3 -1
  48. package/docs/showcase/table-expandable-rows.tsx +25 -15
  49. package/docs/showcase/table-grouped-subtotals.tsx +5 -1
  50. package/package.json +2 -2
@@ -2,15 +2,15 @@
2
2
 
3
3
  :root {
4
4
 
5
- --primary-hover: 206 100% 29.4%;
5
+ --primary-hover: 268.7 100% 41.6%;
6
6
 
7
- --primary-active: 208 100% 22%;
7
+ --primary-active: 268.7 100% 34.5%;
8
8
 
9
- --primary-border: 198 68% 66%;
9
+ --primary-border: 268.7 68% 66%;
10
10
  --destructive-hover: 359 50.6% 53.1%;
11
11
  --destructive-active: 355 70.6% 33.3%;
12
12
 
13
- --control-outline: 192.1 100% 44.7%;
13
+ --control-outline: 268.7 100% 44.7%;
14
14
  --control-outline-alpha: 0.11;
15
15
 
16
16
  --control-outline-error: 4.3 87.6% 34.7%;
@@ -22,15 +22,15 @@
22
22
  .dark,
23
23
  :root[data-theme="dark"] {
24
24
 
25
- --primary-hover: 202.2 74.6% 64.5%;
25
+ --primary-hover: 268.7 100% 92.7%;
26
26
 
27
- --primary-active: 200.2 81.3% 74.9%;
27
+ --primary-active: 268.7 100% 81.1%;
28
28
 
29
- --primary-border: 204.6 46.7% 23.5%;
29
+ --primary-border: 268.7 46.7% 23.5%;
30
30
  --destructive-hover: 358.8 42.3% 53.1%;
31
31
  --destructive-active: 357.3 50.6% 34.1%;
32
32
 
33
- --control-outline: 204.6 99% 61.8%;
33
+ --control-outline: 268.7 99% 61.8%;
34
34
  --control-outline-alpha: 0.29;
35
35
 
36
36
  --control-outline-error: 351.5 98.3% 53.5%;
@@ -26,13 +26,13 @@
26
26
  --input: 30 7% 53%;
27
27
 
28
28
  --success: 146 40% 58%;
29
- --success-foreground: 60 33% 99%;
29
+ --success-foreground: 48 8% 13%;
30
30
  --warning: 44 100% 49%;
31
31
  --warning-foreground: 48 8% 13%;
32
32
  --info: 221 40% 50%;
33
33
  --info-foreground: 60 33% 99%;
34
34
  --attention: 25 99% 46%;
35
- --attention-foreground: 60 33% 99%;
35
+ --attention-foreground: 48 8% 13%;
36
36
 
37
37
  --brand: 268.7 100% 50%;
38
38
  --brand-foreground: 60 33% 99%;
@@ -18,8 +18,8 @@ It changes no code by itself. It is the tie-breaker a reviewer points at.
18
18
  | Behaviour primitives | **Radix** | already the implementation — 193 references |
19
19
  | Component composition shape | **shadcn** | already the structural convention — 23 references |
20
20
  | Component taxonomy / grouping | **Ant Design** groups | already the catalog shape: `data-entry`, `data-display`, `layout`, `feedback`, `navigation`, `general` — a naming precedent, nothing is installed |
21
- | Colour foundation | **SmartHR** | already the palette source — `--primary` = SmartHR MAIN `#0071bd`, `--foreground` = TEXT_BLACK, `--border` = BORDER |
22
- | **Derived colour — the interaction states hanging off each seed** | **Measured contrast (WCAG 2.2 / JIS X 8341-3)** | Authored in `src/tokens/derived.css`; no algorithm derives them. Four contrast suites read that file and hold every value to a threshold — see below |
21
+ | Colour foundation | **SmartHR**, brand roles aside | the NEUTRAL spine is SmartHR's — `--foreground` = TEXT_BLACK, `--border` = BORDER. `--primary` was SmartHR MAIN `#0071bd` until identity v2.3 took the brand roles back: it is GoDX violet `#7A00FF` today |
22
+ | **Derived colour — the interaction states hanging off each seed** | **Measured contrast (WCAG 2.2 / JIS X 8341-3)** | Authored in `src/tokens/derived.css`; no algorithm derives them. Four contrast suites read that file and hold every value to a threshold, and a fifth holds it to the seed's HUE (gh#648) — see below |
23
23
  | **Japanese UI convention — density, JP typography, form patterns** | **SmartHR** | **NEW — this decision.** Extends SmartHR from "where the colours came from" to the authority for how a JP business screen behaves |
24
24
  | **Japanese accessibility / public-sector convention** | **デジタル庁 Design System** (Digital Agency) | **NEW — this decision.** The reference when a JP customer asks which standard a screen meets (JIS X 8341-3) |
25
25
  | **Spacing, density, type scale, information architecture** | **IBM Carbon** | **NEW — this decision** |
@@ -385,17 +385,25 @@ the property actually being sold.
385
385
  suite — that is CI's job on the PR), and if a threshold breaks the value is
386
386
  wrong. Do not relax the threshold.
387
387
 
388
- **The four overrides that existed even while a generator did** — the clearest evidence the
389
- algorithm was never the real authority. Stepping an interactive fill towards the label sitting on
390
- it lands under 4.5:1, so those four states take the same ramp at the same step size in the
391
- opposite direction:
392
-
393
- | token | conventional step | shipped |
394
- | ------------------------ | ----------------- | ----------------- |
395
- | light `--primary-hover` | #208bc9 · 3.69:1 | #005596 · 7.53:1 |
396
- | light `--primary-active` | #005596 · 7.53:1 | #003c70 · 10.97:1 |
397
- | dark `--primary-hover` | #61b6e8 · 7.92:1 | #61b6e8 · 7.92:1 |
398
- | dark `--primary-active` | #2f76a6 · 3.60:1 | #8bd0f3 · 10.50:1 |
388
+ **The primary interaction ramp, and the one override left in it.** The light states are TAKEN from
389
+ the identity v2.3 kit rather than computed — the kit publishes its own violet ramp — and each is
390
+ snapped to the seed's hue (1.008:1 / 1.010:1 from the kit value, i.e. the same colour). Dark takes
391
+ the kit's `action.primary.hover` and then reflects the pressed step, because the dark seed sits at
392
+ 86.9% L and the conventional step runs out of LIGHTNESS rather than out of contrast:
393
+
394
+ | token | conventional step | shipped |
395
+ | ------------------------ | ------------------------ | ---------------------------------- |
396
+ | light `--primary-hover` | kit #6400D4 | #6500d4 · 8.23:1 |
397
+ | light `--primary-active` | kit #5200B0 | #5400b0 · 10.31:1 |
398
+ | dark `--primary-hover` | kit #E8DAFF | #ecdaff · 13.51:1 |
399
+ | dark `--primary-active` | #fbf7ff · 1.056:1 from white | #cd9fff · 8.41:1 (sign flipped) |
400
+
401
+ **The ratio is blind to hue, and that cost a release (gh#648).** Identity v2.3 moved the seed 63
402
+ degrees and this tier stayed on the blue it was derived from: 24.0.0 and 24.1.0 shipped a primary
403
+ button that was violet at rest and navy on hover, with all four contrast suites green throughout,
404
+ because #005596 clears 7.53:1 on the primary label whatever the seed underneath it is. A threshold
405
+ is still the right authority — it just answers one axis of two.
406
+ `src/tokens/__tests__/derived-hue-lock.test.ts` answers the other, and asserts nothing else.
399
407
 
400
408
  **What was deliberately never derived, and why — each a measurement, not a preference:**
401
409
 
@@ -482,11 +490,16 @@ the **field** indicator on every control — one hairline (1px) in the focus hue
482
490
  `--control-outline` halo — rather than the heavy 3px outline form. Measured, in Chromium, on
483
491
  `ql.test` after the transition settles:
484
492
 
485
- | control | switch off | switch on |
486
- | -------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------- |
487
- | Input / Select trigger | border `1px rgb(144,135,127)`, resting shadow intact | border `1px rgb(0,113,189)` + `rgba(0,182,228,0.11) 0 0 0 2px` |
488
- | Button (primary) | outline `0px`, resting shadow intact | `outline: 1px solid rgb(0,113,189)` @ `0px` + same halo |
489
- | Sidebar nav row / list row | outline `0px` | `outline: 1px solid rgb(0,113,189)` @ **`-1px`** (inset into the row) |
493
+ | control | switch off | switch on |
494
+ | -------------------------- | ---------------------------------------------------- | -------------------------------------------------------------- |
495
+ | Input / Select trigger | border `1px rgb(144,135,127)`, resting shadow intact | border `1px` of `--ring` + `--control-outline` halo `0 0 0 2px` |
496
+ | Button (primary) | outline `0px`, resting shadow intact | `outline: 1px solid` `--ring` @ `0px` + same halo |
497
+ | Sidebar nav row / list row | outline `0px` | `outline: 1px solid` `--ring` @ **`-1px`** (inset into the row) |
498
+
499
+ The GEOMETRY above is the Chromium measurement; the COLOUR is named by token rather than pinned as
500
+ a hex, because it follows the seed and the hexes this table used to carry were the pre-v2.3 blue —
501
+ the same staleness gh#648 found one tier down. Today `--ring` resolves to rgb(122,0,255) light /
502
+ rgb(220,188,255) dark and the halo to `rgba(109,0,228,0.11)` / `rgba(153,61,254,0.29)`.
490
503
 
491
504
  The field pair is the recoloured boundary plus the halo, exactly as the convention specifies for
492
505
  this seed. The nav row insets its mark into its own shape rather than wrapping an already-shaded surface,
@@ -497,10 +510,11 @@ after: the field form only recolours a border and an `outline` is painted outsid
497
510
  Input, Save and Delete all stay at 32.00px.
498
511
 
499
512
  **Two criteria, and only one is met by thickness alone.** SC 1.4.11 (AA, non-text contrast) is about
500
- COLOUR — the 1px mark measures 5.05:1 light and 7.07:1 dark on every surface a control sits on, so
501
- the light weight costs nothing there. `--primary-border` (#6dc0e3) measures **2.00:1 light /
502
- 1.66:1 dark** and could not have satisfied it at any thickness, which is why the on-state takes the
503
- focus hue instead — the single place it departs from the outline form. SC 2.4.13 Focus Appearance
513
+ COLOUR — the 1px mark measures at worst 5.30:1 light and 6.87:1 dark across every surface a control
514
+ sits on (an accent panel in both themes), so the light weight costs nothing there. `--primary-border`
515
+ (#a66de3) reaches 3.49:1 on the page and **2.92:1 light / 1.03:1 dark on that same accent panel**, so
516
+ it still could not satisfy the criterion everywhere at any thickness, which is why the on-state takes
517
+ the focus hue instead — the single place it departs from the outline form. SC 2.4.13 Focus Appearance
504
518
  (AAA) additionally wants a 2px perimeter; the on-state does not target it, and
505
519
  `--focus-outline-weight: var(--stroke-lg)` restores the 3px weight if a customer needs the area
506
520
  clause.
@@ -31,6 +31,7 @@ pnpm check:frame-axe -- /isolate/layout-topbar # one route, while fixing
31
31
  | tags | `wcag2a` · `wcag2aa` · `wcag21aa` · `wcag22aa` — **the consumer's set, verbatim** |
32
32
  | routes | every `/isolate/<id>` in `window.__STORY_MANIFEST__` + every `/showcase/<id>` |
33
33
  | viewports | 1440×900 · 375×667 · **320×568** |
34
+ | states | as rendered, **plus** one overlay OPEN on the routes that declare `data-axe-open` |
34
35
 
35
36
  320 is not decoration: it is WCAG 2.2 SC 1.4.10's reflow width, it is the width the consumer's
36
37
  nightly runs, and it is the width gh#639 failed at while 390 passed.
@@ -39,23 +40,160 @@ Showcases are included on purpose. They are the only frames here shaped like a r
39
40
  page, a landmark tree, a focus order — which is precisely the class the component frames cannot
40
41
  reach and the consumer has been carrying alone.
41
42
 
42
- ## What it found on its first run
43
+ ## The overlay scope — opening the thing before measuring it
43
44
 
44
- 54 rows, **159 violation nodes**, on code that passed every other gate in this repository:
45
+ A menu, dialog, listbox or popover that is closed at rest **paints nothing**. Every axe rule whose
46
+ condition only exists while one is open was therefore outside this gate's field of view, not passing
47
+ it. The gate shipped in 24.1.0 without this half; it is back (gh#643 item 4), rebuilt from the
48
+ version #492 deleted.
45
49
 
46
- | nodes | rows | rule | what it means |
47
- | ----: | ---: | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
48
- | 62 | 17 | `color-contrast` | text under 4.5:1 (SC 1.4.3) |
49
- | 28 | 16 | `target-size` | under 24×24 with no spacing exception (SC 2.5.8) — on `Carousel`, `Attachments`, `FilterBar` and `Toolbar`, at **every** width |
50
- | 21 | 6 | `aria-prohibited-attr` | an `aria-*` on a role that does not allow it |
51
- | 18 | 6 | `button-name` | **a button a screen reader announces as nothing** |
52
- | 18 | 6 | `aria-valid-attr-value` | an `aria-*` pointing at an id that is not there |
53
- | 12 | 3 | `aria-conditional-attr` | an `aria-*` that is invalid in the state it is in |
50
+ A demo opts in **declaratively**, from the demo itself:
54
51
 
55
- 48 of those nodes are on `/isolate/**` — single components, this package's own output — and 111 on
56
- `/showcase/**`. None of the six rules exists in `scripts/visual-audit.mjs`. The `target-size` rows
57
- are the sharpest: our own `target-size-min` rule was running on those same frames and reporting them
58
- clean, because it measures a painted box.
52
+ ```tsx
53
+ // the trigger, when it forwards DOM props
54
+ <DialogTrigger asChild>
55
+ <Button data-axe-open size="sm">仕訳新規作成</Button>
56
+ </DialogTrigger>
57
+
58
+ // the region that owns it, when the component renders its own trigger
59
+ <CardContent data-axe-open>
60
+ <FormField id="status" label="状態"><Select … /></FormField>
61
+ </CardContent>
62
+
63
+ // right-click, for the gesture nothing else reaches
64
+ <Button data-axe-open="contextmenu" variant="outline">…</Button>
65
+ ```
66
+
67
+ What the gate then does, per route/viewport, **after** the default-state scan (opening an overlay is
68
+ destructive to the state that scan measures):
69
+
70
+ 1. Take the **first** `[data-axe-open]` on the page. No declaration ⇒ the route skips this entirely
71
+ and costs nothing.
72
+ 2. Press **Escape** up to three times while any overlay is mounted. Several demos deliberately
73
+ render one open at rest (`popover`'s anchored panel, `dropdown-menu`'s `defaultOpen` card), and a
74
+ modal one makes the rest of the page unreachable. Dismissing first also makes the measurement a
75
+ real **closed → open** transition rather than whatever the demo happened to leave mounted.
76
+ 3. Resolve the click target: the declaring element itself when it matches
77
+ `button, [role=button], [role=combobox], [role=menuitem], a[href], summary`, otherwise the first
78
+ such control inside it. (`Select` and `DatePicker` own their trigger DOM and forward no `data-*`
79
+ to it — hence the declare-on-the-region form. `DatePicker`'s combobox `<input>` is what opens the
80
+ calendar, and it is what gets pressed.)
81
+ 4. Click (or right-click), then wait until **one more** overlay is mounted
82
+ (`[data-radix-popper-content-wrapper], [role=dialog], [role=alertdialog], [role=menu],
83
+ [role=listbox]`) — never a bare timer.
84
+ 5. Scan again, and key the rows `@<viewport>+open`:
85
+
86
+ ```
87
+ /isolate/data-entry-select-matrix @1440+open aria-required-children
88
+ ```
89
+
90
+ **A declaration whose overlay never opens is a gate FAILURE**, recorded as `overlay-did-not-open`
91
+ and red on the spot. A broken declaration otherwise reads exactly like a clean frame, which is the
92
+ one thing this scope exists to prevent.
93
+
94
+ **Routes declaring an open step today — 7 declared, 7 open:** `data-entry-select`,
95
+ `data-entry-select-matrix`, `data-entry-date-picker`, `data-display-popover`,
96
+ `navigation-dropdown-menu`, `feedback-dialog`, `feedback-sheet`. (The old `navigation-context-menu`
97
+ frame is gone — `ContextMenu` was folded into `DropdownMenu trigger={["contextMenu"]}` in v23 — so
98
+ the right-click gesture is declared on that card of `navigation-dropdown-menu` instead.)
99
+
100
+ ### What it found, and the proof it has teeth
101
+
102
+ **`aria-required-children`, CRITICAL, on `/isolate/data-entry-select-matrix` at all three
103
+ viewports.** `SelectSeparator` rendered react-aria's `<Separator>`, i.e. `role="separator"`, as a
104
+ direct child of the viewport's `role="listbox"`. ARIA 1.2 lets a `listbox` own `option` and `group`
105
+ and nothing else. Fixed in `src/components/data-entry/select.tsx` — the divider is decoration over a
106
+ structure the two `SelectGroup`s already announce, so it now renders `aria-hidden` with no role.
107
+ `menu` **does** own `separator`, so `DropdownMenuSeparator` is correct as it stands. Regression test:
108
+ `src/components/data-entry/__tests__/select-groups.test.tsx`.
109
+
110
+ The control run is the part worth keeping. On the **same defective build**:
111
+
112
+ | declarations | result |
113
+ | ------------ | --------------------------------------------------------------------------- |
114
+ | removed | ✓ green, `0 route(s) opened a declared overlay` |
115
+ | present | ✗ red, 3 × `aria-required-children` on `@1440+open / @375+open / @320+open` |
116
+
117
+ A `data-axe-open` on a `Button` that opens nothing turns the gate red with three
118
+ `overlay-did-not-open` rows, one per viewport.
119
+
120
+ **The original 2026-09 proof no longer reproduces, and the reason matters more than the proof.** That
121
+ commit disabled `src/components/general/inert-background.ts` and watched `aria-hidden-focus` go red
122
+ on `select`, `dropdown-menu` and `context-menu`. Disabling it today changes nothing measurable:
123
+ these overlays are **react-aria-components**, not Radix: react-aria inerts the background itself —
124
+ `#root` plus three focus sentinels carry `inert` the moment a listbox opens, with no `aria-hidden`
125
+ anywhere on them. Stripping every `inert` attribute immediately before the scan still leaves the
126
+ page clean and `aria-hidden-focus` in axe's `passes` bucket, because that rule needs an
127
+ `aria-hidden` ancestor to fire at all. No `src/` component imports `useInertHiddenBackground` any
128
+ more; the only importer is the Radix parity fixture
129
+ `src/components/data-entry/__tests__/radix-select.fixture.tsx`. So `inert-background.ts` is dead
130
+ for `Select` and `DropdownMenu`, and the overlay scope's value is no longer the rule it was built
131
+ for.
132
+
133
+ Be careful with the stronger version of that claim, because the first draft of this paragraph made
134
+ it and it is false: `data-aria-hidden` is **not** absent from the built preview. The `aria-hidden`
135
+ npm package ships inside the `command` chunk (`cmdk`'s dependency tree), so the marker exists in
136
+ the bundle even though no first-party overlay sets it. What is measured above is the narrow claim —
137
+ `Select` and `DropdownMenu` background-inert via `inert`, not `aria-hidden` — not a repo-wide
138
+ absence. It is still the rule class: `aria-required-children` is the same
139
+ shape, and nothing else here could see it.
140
+
141
+ ### What is still outside this scope, stated rather than hidden
142
+
143
+ - Only the **first** declaration per route is exercised. A route with several distinct overlays is
144
+ measured on one of them.
145
+ - Only **one step deep**: submenus, an overlay opened from inside another, and the state after a
146
+ selection are not reached.
147
+ - **Keyboard opening is not exercised** — the gate clicks. A trigger that opens on click but not on
148
+ `Enter`/`Space` passes.
149
+ - The overlay's **focus trap and focus order** are not asserted; axe cannot see a JS focus trap.
150
+
151
+ ## What it found on its first run — and what came of it
152
+
153
+ 54 rows, **159 violation nodes**, on code that passed every other gate in this repository. Fixing
154
+ them took it to **12 rows / 19 nodes**, and then to **0**:
155
+
156
+ | rule | first run | now | what it was |
157
+ | ----------------------- | --------: | --: | ---------------------------------------------------------------------------------------------------------------------------------------- |
158
+ | `color-contrast` | 62 | 0 | text under 4.5:1 (SC 1.4.3) |
159
+ | `target-size` | 28 | 0 | under 24×24 (SC 2.5.8) — `Carousel` dots, `SearchInput`'s clear button, `Attachments`' file input |
160
+ | `aria-prohibited-attr` | 21 | 0 | `Badge`'s `aria-label` on a `generic` div — the name was dropped by every screen reader |
161
+ | `button-name` | 18 | 0 | **`Select`'s trigger is `role="combobox"`, which cannot be named by its contents** — every unlabelled Select shipped a nameless combobox |
162
+ | `aria-valid-attr-value` | 18 | 0 | `aria-controls` pointing at ids that do not exist |
163
+ | `aria-conditional-attr` | 12 | 0 | `aria-expanded` on a `<tr>`, which only a `treegrid` row may carry |
164
+
165
+ Four of those were LIBRARY defects, not demo slips — `Badge`, `Select`, `Carousel`, `SearchInput` —
166
+ and the fifth produced a new vocabulary member: `tone="inherit"` on `Text` / `Heading` / `Title` /
167
+ `Activity` / `Separator`, because every other tone is an absolute token and there was no way to put
168
+ text on a coloured surface without fighting it.
169
+
170
+ The `target-size` rows are the sharpest point about two rulers. Our own `target-size-min` was
171
+ running on those same frames and reporting them clean, because it measures a painted box. And the
172
+ fix axe wants is not the fix the standard wants: a centred `::after` genuinely makes the target
173
+ 24×24 — `docs/MEASUREMENT-CONTRACT.md` records that under `expanders` — but axe reads
174
+ `getBoundingClientRect()`, which cannot see a pseudo-element. Both fixes were tried; the boxes had
175
+ to grow.
176
+
177
+ ## The last twelve rows, and what each one turned out to be
178
+
179
+ The table above is what the sweep found; this is what the remaining 12 rows / 19 nodes turned out
180
+ to be once each was measured rather than reasoned about. The rows are kept, with the verdict this
181
+ file used to carry quoted back, because four of the five were wrong: two had no diagnosis at all
182
+ and two had one the measurement contradicted — and in both of those the guess pointed AWAY from
183
+ this package.
184
+
185
+ | rows | what | the measurement, and where it was fixed |
186
+ | ---: | ------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
187
+ | 3 | `foundation-colors` · `--success-foreground` on `--success`, `--attention-foreground` on `--attention` | Called "a PALETTE decision, an owner-level call". It was not the palette: the FILL was fine and the INK was wrong. Two of the light theme's eight fills carried a near-white ink their own lightness cannot support (2.19:1 / 3.32:1) while the sibling `--warning` had always used the near-black one on the equally pale 山吹, and the DARK theme already flipped both. `src/tokens/foundation.css`: ink only — success 2.19 → **7.08**, attention 3.32 → **4.68**. Guarded by `__tests__/status-fill-contrast.test.ts`. |
188
+ | 3 | `case6-agency-handy` · a SELECTED `ToggleGroupItem` paints `--foreground` over `--primary` | "Cause not yet found." Found, with `CSS.getMatchedStylesForNode`: the demo wrote `text-[var(--font-size-xs)]`, and Tailwind v4 reads a bare `var()` in `text-*` as a COLOUR — it compiled to `color: var(--font-size-xs)` in the UTILITIES layer, an invalid colour that clobbered the component's own `color: hsl(var(--primary-foreground))` in `components` and left the trigger inheriting the page ink. The component rule was right all along. Demo fix (`text-[length:var(…)]`, 10 sites in `docs/`), plus the count pill: the hand-rolled `Text` at `opacity-70` measured 2.15:1 and `ToggleGroupItem`'s own `count` prop inverts to 6.32:1. |
189
+ | 3 | `table-view-tabs` · a `Tab`'s `aria-controls` points at a `tabpanel` that is never rendered | A LIBRARY defect, and the composition is published: `Tabs` / `TabsList` / `TabsTrigger` are three separate exports, nothing in their types asks for a fourth, and the attribute could not be overridden (`withDomProps` puts RAC's props after the caller's). `TabsContent` now registers its value and a trigger claims a panel only when one is declared. No new public prop. `__tests__/tabs-panel-reference-643.test.tsx`. |
190
+ | 2 | `futurelastic-web` @320/@375 · white ghost buttons read as 1.01:1 | "Probably a false positive." It was not. The tenant's `.fl-navbar-inner` was a fixed-height flex row with no wrap, so at 320 its content ran to **x=569.6 inside a 320px nav** — the links painted outside the navbar's own box, over the page behind it. A WCAG 2.2 SC 1.4.10 reflow failure reported by the contrast rule, because that is what an overflowing element looks like from the outside. Demo CSS + `Flex wrap`. |
191
+ | 1 | `table-bulk-actions` @375 · `target-size` | "Not yet diagnosed." The left cluster carried `min-w-0 flex-1` — "squeeze me to nothing" — over children that refuse to shrink, so its box shrank to 113.6px while its content ran to x=462 and painted 一括承認 (264.6→360.2) under 解除 (272.3→341.0), leaving 19.2×30.2 unobscured against SC 2.5.8's 24×24. Demo: `wrap`, and no `min-w-0`. |
192
+
193
+ Two of the five were library defects and three were genuine demo slips — a different split from the
194
+ first sweep, where four of five were the library's. Which is the point of writing the number down
195
+ next to each one: "it is the demo" and "it is the palette" are both cheap to say and neither
196
+ survived a measurement here.
59
197
 
60
198
  ## The baseline is a debt ledger, not an allowlist
61
199
 
@@ -76,12 +214,10 @@ The gate fails on a key that is **not** in the baseline, and on a baselined key
76
214
 
77
215
  ## What this gate does NOT do — stated, not hidden
78
216
 
79
- The gate deleted in #492 did three things this one does not, and each is a real gap:
217
+ The gate deleted in #492 did three things this one does not. One is now back; two are still gaps:
80
218
 
81
- 1. **No overlay scope.** It scans the frame as rendered. A menu, dialog, listbox or popover that is
82
- closed at rest is never measured, so `aria-hidden-focus` and friends stay outside its field of
83
- view. The old gate opened one overlay per frame from a `data-axe-open` attribute; that attribute
84
- was removed from the demos along with the gate and would have to come back.
219
+ 1. ~~**No overlay scope.**~~ Restored — see [the overlay
220
+ scope](#the-overlay-scope--opening-the-thing-before-measuring-it) above.
85
221
  2. **No chrome/component split.** The old gate held the preview toolbar to zero violations and
86
222
  allowlisted the component scope separately. `/isolate/**` renders the demo alone, so there is
87
223
  little chrome to separate — but `/showcase/**` is scanned whole.
@@ -98,11 +234,16 @@ which changes what `color-contrast` resolves a background to). Two sweeps after
98
234
 
99
235
  ## Cost
100
236
 
101
- | | |
102
- | ---------------------------------- | ----------------------------------------------------------------------------------- |
103
- | full sweep | **3m20s** locally · **12m01s** on the self-hosted runner — 215 routes × 3 viewports |
104
- | sequential | ~55 min — the three viewport passes run concurrently, which is the whole difference |
105
- | showcase only (`--scope=showcase`) | 30s |
237
+ | | |
238
+ | ------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
239
+ | full sweep | **3m20s** locally · **12m01s** on the self-hosted runner — 215 routes × 3 viewports |
240
+ | sequential | ~55 min — the three viewport passes run concurrently, which is the whole difference |
241
+ | showcase only (`--scope=showcase`) | 30s |
242
+ | full sweep **with the overlay scope** | **2m14s** on the machine gh#643 item 4 was built on, same 215 × 3 — the runner number has not been re-measured |
243
+
244
+ The overlay pass is bounded by declaration: only the 7 declaring routes pay it (21 extra scans plus
245
+ their open steps), and no `/showcase/**` route declares one, so `--scope=showcase` — the merge lane —
246
+ is unchanged.
106
247
 
107
248
  `--shard=i/n` is in the script for the day the sweep outgrows the lane. Using it adds check-run
108
249
  names, which costs nothing here because none of them is in `REQUIRED_CI_CHECK_RUNS`.
@@ -61,7 +61,9 @@ export default function Demo() {
61
61
  <CardContent>
62
62
  <Popover open={filterOpen} onOpenChange={setFilterOpen}>
63
63
  <PopoverTrigger asChild>
64
- <Button variant="outline">
64
+ {/* data-axe-open: check:frame-axe presses this before its overlay scan, so the
65
+ panel is measured OPEN as well as closed. See docs/FRAME-A11Y-CI.md. */}
66
+ <Button data-axe-open variant="outline">
65
67
  <SlidersHorizontal />
66
68
  絞り込み
67
69
  </Button>
@@ -41,7 +41,9 @@ export default function Demo() {
41
41
  形式に整形され、 解釈できない入力は元の値へ復帰する。
42
42
  </CardDescription>
43
43
  </CardHeader>
44
- <CardContent>
44
+ {/* data-axe-open on the region: DatePicker owns its trigger DOM. The gate presses the
45
+ `[role="combobox"]` input inside, which is what opens the calendar. */}
46
+ <CardContent data-axe-open>
45
47
  <FormField id="issue-date" label="発行日" required>
46
48
  <DatePicker
47
49
  id="issue-date"
@@ -37,7 +37,9 @@ export default function Demo() {
37
37
  content. Radix owns keyboard focus and collision handling.
38
38
  </CardDescription>
39
39
  </CardHeader>
40
- <CardContent>
40
+ {/* data-axe-open on the region: the gate presses the first `[role="combobox"]` inside it,
41
+ which is the compound SelectTrigger. */}
42
+ <CardContent data-axe-open>
41
43
  <Flex direction="col" gap="md">
42
44
  <FormField
43
45
  id="compound-controlled"
@@ -90,7 +90,10 @@ export default function Demo() {
90
90
  Pass an options array; name= submits the value with the form.
91
91
  </CardDescription>
92
92
  </CardHeader>
93
- <CardContent>
93
+ {/* data-axe-open on the REGION, not the trigger: the data-driven Select renders its own
94
+ trigger and forwards no `data-*` to it, so the gate resolves the `[role="combobox"]`
95
+ inside this element and presses that. See docs/FRAME-A11Y-CI.md. */}
96
+ <CardContent data-axe-open>
94
97
  <FormField id="status" label="状態">
95
98
  <Select
96
99
  id="status"
@@ -64,7 +64,10 @@ export default function Demo() {
64
64
  <CardContent>
65
65
  <Dialog open={createOpen} onOpenChange={setCreateOpen}>
66
66
  <DialogTrigger asChild>
67
- <Button size="sm">仕訳新規作成</Button>
67
+ {/* data-axe-open: check:frame-axe presses this before its overlay scan. */}
68
+ <Button data-axe-open size="sm">
69
+ 仕訳新規作成
70
+ </Button>
68
71
  </DialogTrigger>
69
72
  <DialogContent className="max-w-lg">
70
73
  <DialogHeader>
@@ -104,7 +104,8 @@ export default function Demo() {
104
104
  <CardContent>
105
105
  <Sheet open={filterOpen} onOpenChange={setFilterOpen}>
106
106
  <SheetTrigger asChild>
107
- <Button variant="outline" size="sm">
107
+ {/* data-axe-open: check:frame-axe presses this before its overlay scan. */}
108
+ <Button data-axe-open variant="outline" size="sm">
108
109
  <SlidersHorizontal />
109
110
  詳細検索
110
111
  </Button>
@@ -69,7 +69,10 @@ function SwatchTile({ swatch, text }: { swatch: Swatch; text?: boolean }) {
69
69
  {/* ui-audit-disable-next-line no-hand-rolled-surface no-utility-layout no-utility-spacing — a swatch showing the token itself; the fill IS the subject */}
70
70
  <div className={`border-border flex h-14 items-end rounded-md border p-2 ${swatch.cls}`}>
71
71
  {text ? (
72
- <Text size="xs" mono className="leading-none">
72
+ /* `tone="inherit"` — the specimen's whole job is to show the token's PAIRED foreground,
73
+ and every other tone is an absolute colour. Without it `Text` repainted itself in
74
+ `--foreground` over `--primary` and measured 2.45:1 on this very page (gh#643). */
75
+ <Text size="xs" mono tone="inherit" className="leading-none">
73
76
  Aa 亜
74
77
  </Text>
75
78
  ) : null}
@@ -125,6 +125,69 @@ export default function Demo() {
125
125
  </Flex>
126
126
  </CardContent>
127
127
  </Card>
128
+
129
+ <Card>
130
+ <CardHeader>
131
+ <CardTitle level={2}>Product suffix — 「GoDX | ID」</CardTitle>
132
+ <CardDescription>
133
+ productSuffix は製品名を lockup の内側に置きます。マスターは
134
+ mark=&quot;godx-lockup&quot; の 1 つだけで、区切り線・その前後の間隔・size
135
+ スケール・light/dark
136
+ の切替はすべてパッケージのトークンが所有します(--logo-divider-color / -width /
137
+ -height / -alpha、--logo-product-suffix-gap / -color /
138
+ -font-weight)。製品名は「描いた図形」ではなく wordmark と同じ組版
139
+ (--logo-wordmark-font-size-* /
140
+ -font-family)です。別座標系のマスターを増やさず、製品が増えても
141
+ リリースが要らず、ローカライズできるためです。 区切り線の色はロゴタイプの ink
142
+ (--logo-godx-ink-color)の 0.25 alpha で、light では #c1c2cb(1.74:1、 公式キットの
143
+ #c5c8d6 = 1.64:1 とほぼ同じ)、dark では #515150(2.30:1)。 キットの固定値と違い dark
144
+ の対応があります。
145
+ </CardDescription>
146
+ </CardHeader>
147
+ <CardContent>
148
+ <Flex direction="col" gap="md">
149
+ {/* size は suffix にも効く — マークもロゴタイプも製品名も同じティアで動きます。 */}
150
+ <Flex direction="row" align="center" wrap gap="lg">
151
+ <Logo mark="godx-lockup" productSuffix="ID" size="xs" />
152
+ <Logo mark="godx-lockup" productSuffix="ID" size="sm" />
153
+ <Logo mark="godx-lockup" productSuffix="ID" />
154
+ <Logo mark="godx-lockup" productSuffix="ID" size="lg" />
155
+ </Flex>
156
+ {/* 将来の製品名も同じ API。追加のアセットもリリースも要りません。 */}
157
+ <Flex direction="row" align="center" wrap gap="lg">
158
+ <Logo mark="godx-lockup" productSuffix="Console" />
159
+ <Logo mark="godx-lockup" productSuffix="Admin" />
160
+ <Logo mark="godx-lockup" productSuffix="管理コンソール" />
161
+ </Flex>
162
+ {/* 描かれた "GoDX" は sr-only テキストで補われ、名前は "GoDX ID" になります。
163
+ * role="img" を使わないのは、asChild でリンクにしたとき link ロールを
164
+ * 上書きしてしまうためです。 */}
165
+ <Flex direction="col" gap="sm">
166
+ <Logo mark="godx-lockup" productSuffix="ID" size="lg" />
167
+ <Text size="2xs" mono tone="muted">
168
+ {`mark="godx-lockup" productSuffix="ID" → accessible name "GoDX ID"`}
169
+ </Text>
170
+ </Flex>
171
+ {/* 組版の wordmark と併用すると、suffix はその後ろに入ります。 */}
172
+ <Flex direction="col" gap="sm">
173
+ <Logo glyph="c" wordmark="CoreBooks" productSuffix="Admin" />
174
+ <Text size="2xs" mono tone="muted">
175
+ {`glyph + wordmark + productSuffix → name "CoreBooks Admin"`}
176
+ </Text>
177
+ </Flex>
178
+ {/* suffix がノードでも名前は壊れません。label は従来どおり上書きできます。 */}
179
+ <Logo
180
+ mark="godx-lockup"
181
+ productSuffix={<Text size="xs">ID</Text>}
182
+ label="GoDX ID ホーム"
183
+ />
184
+ {/* リンク化も同じ。lockup そのものが <a> になります。 */}
185
+ <Logo asChild mark="godx-lockup" productSuffix="ID">
186
+ <a href="#logo-product-suffix" />
187
+ </Logo>
188
+ </Flex>
189
+ </CardContent>
190
+ </Card>
128
191
  </Flex>
129
192
  </PageContainer>
130
193
  );
@@ -94,6 +94,37 @@ export default function Demo() {
94
94
  </CardContent>
95
95
  </Card>
96
96
 
97
+ <Card>
98
+ <CardHeader>
99
+ <CardTitle level={2}>tone=&quot;inherit&quot; · 面が決める</CardTitle>
100
+ <CardDescription>
101
+ 他の tone は絶対的なトークンを塗るため、色つきの面に置いた途端にページの文字色へ戻って
102
+ しまいます。inherit はその面が持つ対になる前景色をそのまま使う唯一のメンバーです。
103
+ これを入れる前、--primary の上の Text は --foreground を塗って 2.45:1
104
+ でした(gh#643)。
105
+ </CardDescription>
106
+ </CardHeader>
107
+ <CardContent>
108
+ <Flex direction="col" gap="md">
109
+ {/* ui-audit-disable-next-line no-hand-rolled-surface no-utility-layout no-utility-spacing — the COLOURED SURFACE is the subject here, exactly as in docs/foundation/colors.tsx */}
110
+ <div className="bg-primary text-primary-foreground rounded-md p-4">
111
+ <Flex direction="col" gap="xs">
112
+ <Heading level={3} tone="inherit">
113
+ 請求サマリー
114
+ </Heading>
115
+ <Text tone="inherit">今月の請求は締め処理まで完了しています。</Text>
116
+ <Text size="2xs" mono tone="inherit">
117
+ {`tone="inherit"`}
118
+ </Text>
119
+ </Flex>
120
+ </div>
121
+ <Text size="xs" tone="muted">
122
+ 同じ二行を tone=&quot;default&quot; で置くと、面ではなくページの文字色を塗ります。
123
+ </Text>
124
+ </Flex>
125
+ </CardContent>
126
+ </Card>
127
+
97
128
  <Card>
98
129
  <CardHeader>
99
130
  <CardTitle level={2}>Text sizes</CardTitle>