@hegemonart/get-design-done 1.27.6 → 1.28.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +6 -3
- package/CHANGELOG.md +113 -0
- package/agents/design-verifier.md +17 -0
- package/package.json +5 -4
- package/reference/accessibility.md +4 -0
- package/reference/audit-scoring.md +14 -0
- package/reference/color-theory.md +279 -0
- package/reference/composition.md +349 -0
- package/reference/contrast-advanced.md +205 -0
- package/reference/design-system-guidance.md +2 -0
- package/reference/form-patterns.md +2 -0
- package/reference/i18n.md +554 -0
- package/reference/iconography.md +2 -0
- package/reference/motion-interpolate.md +1 -0
- package/reference/palette-catalog.md +2 -0
- package/reference/proportion-systems.md +267 -0
- package/reference/registry.json +42 -0
- package/reference/rtl-cjk-cultural.md +2 -0
- package/reference/schemas/mcp-gdd-tools.schema.json +381 -0
- package/reference/style-vocabulary.md +2 -0
- package/reference/typography.md +4 -0
- package/reference/visual-hierarchy-layout.md +4 -0
- package/scripts/install.cjs +42 -0
- package/scripts/lib/gsd-health-mirror/index.cjs +105 -0
- package/scripts/lib/gsd-health-mirror/index.d.cts +14 -0
- package/scripts/lib/install/mcp-register.cjs +235 -0
- package/scripts/lib/install/mcp-register.d.cts +64 -0
- package/scripts/lib/intel-store/index.cjs +55 -0
- package/scripts/lib/intel-store/index.d.cts +11 -0
- package/scripts/lib/mcp-tools-lint/index.cjs +216 -0
- package/scripts/lib/mcp-tools-lint/index.d.cts +74 -0
- package/scripts/lib/reflections-reader/index.cjs +107 -0
- package/scripts/lib/reflections-reader/index.d.cts +18 -0
- package/scripts/lib/roadmap-reader/index.cjs +81 -0
- package/scripts/lib/roadmap-reader/index.d.cts +13 -0
- package/scripts/lib/snapshot-reader/index.cjs +70 -0
- package/scripts/lib/snapshot-reader/index.d.cts +28 -0
- package/scripts/mcp-servers/gdd-mcp/README.md +66 -0
- package/scripts/mcp-servers/gdd-mcp/schemas/gdd_cycle_recap.schema.json +30 -0
- package/scripts/mcp-servers/gdd-mcp/schemas/gdd_decisions_list.schema.json +32 -0
- package/scripts/mcp-servers/gdd-mcp/schemas/gdd_events_tail.schema.json +22 -0
- package/scripts/mcp-servers/gdd-mcp/schemas/gdd_health.schema.json +30 -0
- package/scripts/mcp-servers/gdd-mcp/schemas/gdd_intel_get.schema.json +24 -0
- package/scripts/mcp-servers/gdd-mcp/schemas/gdd_learnings_digest.schema.json +22 -0
- package/scripts/mcp-servers/gdd-mcp/schemas/gdd_phase_current.schema.json +22 -0
- package/scripts/mcp-servers/gdd-mcp/schemas/gdd_phases_list.schema.json +31 -0
- package/scripts/mcp-servers/gdd-mcp/schemas/gdd_plans_list.schema.json +33 -0
- package/scripts/mcp-servers/gdd-mcp/schemas/gdd_reflections_latest.schema.json +21 -0
- package/scripts/mcp-servers/gdd-mcp/schemas/gdd_status.schema.json +23 -0
- package/scripts/mcp-servers/gdd-mcp/schemas/gdd_telemetry_query.schema.json +23 -0
- package/scripts/mcp-servers/gdd-mcp/server.ts +317 -0
- package/scripts/mcp-servers/gdd-mcp/tools/gdd_cycle_recap.ts +37 -0
- package/scripts/mcp-servers/gdd-mcp/tools/gdd_decisions_list.ts +33 -0
- package/scripts/mcp-servers/gdd-mcp/tools/gdd_events_tail.ts +26 -0
- package/scripts/mcp-servers/gdd-mcp/tools/gdd_health.ts +19 -0
- package/scripts/mcp-servers/gdd-mcp/tools/gdd_intel_get.ts +32 -0
- package/scripts/mcp-servers/gdd-mcp/tools/gdd_learnings_digest.ts +23 -0
- package/scripts/mcp-servers/gdd-mcp/tools/gdd_phase_current.ts +29 -0
- package/scripts/mcp-servers/gdd-mcp/tools/gdd_phases_list.ts +26 -0
- package/scripts/mcp-servers/gdd-mcp/tools/gdd_plans_list.ts +39 -0
- package/scripts/mcp-servers/gdd-mcp/tools/gdd_reflections_latest.ts +25 -0
- package/scripts/mcp-servers/gdd-mcp/tools/gdd_status.ts +31 -0
- package/scripts/mcp-servers/gdd-mcp/tools/gdd_telemetry_query.ts +27 -0
- package/scripts/mcp-servers/gdd-mcp/tools/index.ts +75 -0
- package/scripts/mcp-servers/gdd-mcp/tools/shared.ts +134 -0
- package/skills/explore/SKILL.md +31 -0
- package/skills/health/SKILL.md +36 -0
- package/skills/next/SKILL.md +28 -3
- package/skills/progress/SKILL.md +21 -6
- package/skills/resume/SKILL.md +26 -1
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: composition
|
|
3
|
+
type: layout
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
phase: 28
|
|
6
|
+
tags: [composition, golden-ratio, fibonacci, focal-point, eye-flow]
|
|
7
|
+
last_updated: 2026-05-18
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Composition — Pre-Gestalt Layout Fundamentals
|
|
11
|
+
|
|
12
|
+
The existing [visual hierarchy and layout reference](./visual-hierarchy-layout.md) covers shadow, z-index, whitespace, asymmetry, and the 12-column grid — the *applied* surface of layout. This file gives the upstream foundation those rules silently assume: rule of thirds, the golden ratio and root rectangles, Fibonacci, focal-point construction, visual-weight calculus, optical-vs-mathematical centering, and the Z / F / Gutenberg eye-flow patterns. Where `visual-hierarchy-layout.md` says "place the CTA where the eye lands", this file replaces that hand-wave with explicit grids, ratios, weight formulas, and detection signatures an audit can grep for.
|
|
13
|
+
|
|
14
|
+
This is the file an agent should consult any time it is *constructing* a layout — choosing a grid, placing a focal point, balancing two halves of a composition, centering a glyph next to text, or deciding which eye-flow archetype a page should follow.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Rule of Thirds
|
|
19
|
+
|
|
20
|
+
The rule of thirds divides any canvas into a 3×3 grid with two horizontal and two vertical lines at the 33% and 67% marks. The four intersections of those lines are the *power points* — the locations the eye lands when scanning a composition. Placing a focal element exactly on a power point produces a layout that reads as deliberate and balanced; placing it at dead-center produces a layout that reads as static and posed. The rule is not a law; it is a default that holds until a stronger compositional intent overrides it (centered hero, symmetric mirror, single-axis radial).
|
|
21
|
+
|
|
22
|
+
**Audit detection signature.** Grep for grid declarations using third-fractions, then check whether a focal element (large heading, primary CTA, hero image) sits near one of the four intersections:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Find grids using third-fractions
|
|
26
|
+
grep -rE "grid-template-columns:\s*1fr\s+1fr\s+1fr|33%|66%|33\.33%|66\.66%" src/
|
|
27
|
+
|
|
28
|
+
# Then inspect: does a [data-focal], a primary CTA, or an h1 land near one of those gridlines?
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<!-- Hero layout — CTA placed at the lower-right power point -->
|
|
33
|
+
<section class="hero">
|
|
34
|
+
<div class="hero__copy">
|
|
35
|
+
<h1>Build it once. Ship it everywhere.</h1>
|
|
36
|
+
<p>Lead with the value, not the brand.</p>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="hero__cta">
|
|
39
|
+
<button>Get Started</button>
|
|
40
|
+
</div>
|
|
41
|
+
</section>
|
|
42
|
+
|
|
43
|
+
<style>
|
|
44
|
+
.hero {
|
|
45
|
+
display: grid;
|
|
46
|
+
grid-template-columns: 1fr 1fr 1fr; /* thirds — vertical gridlines at 33% / 67% */
|
|
47
|
+
grid-template-rows: 1fr 1fr 1fr; /* thirds — horizontal gridlines at 33% / 67% */
|
|
48
|
+
min-height: 80vh;
|
|
49
|
+
}
|
|
50
|
+
.hero__copy {
|
|
51
|
+
grid-column: 1 / 3; /* fills the left two-thirds */
|
|
52
|
+
grid-row: 2 / 3; /* sits on the upper-third horizontal line */
|
|
53
|
+
align-self: center;
|
|
54
|
+
}
|
|
55
|
+
.hero__cta {
|
|
56
|
+
grid-column: 3 / 4; /* rightmost third */
|
|
57
|
+
grid-row: 3 / 4; /* lower-third row */
|
|
58
|
+
align-self: end;
|
|
59
|
+
justify-self: end; /* lower-right power point (67%, 67%) */
|
|
60
|
+
}
|
|
61
|
+
</style>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The CTA sits at the lower-right intersection — the natural Z-pattern terminus (see [§Eye-Flow Patterns](#eye-flow-patterns)). The headline anchors the upper-left third where reading begins.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Golden Ratio and Root Rectangles
|
|
69
|
+
|
|
70
|
+
Beyond thirds, four irrational ratios govern compositions that need to feel *proportional* rather than *gridded*: φ (the golden ratio), √2, √3, and √5. Each rectangle has a different subdivision behavior — when you remove the largest possible square from inside it, the remainder has a specific relationship to the original. φ produces another φ-rectangle (infinitely self-similar — the source of the golden spiral). √2 produces a rectangle that, when folded in half, is still √2 (the math behind A0 / A1 / A2 paper). √3 produces a √3 / 2 rectangle. √5 produces two squares plus a φ-rectangle and is the bridge from integer roots back to φ via the identity `φ = (1 + √5) / 2`.
|
|
71
|
+
|
|
72
|
+
### φ-Grid (Golden Ratio)
|
|
73
|
+
|
|
74
|
+
φ ≈ 1.618. A φ-rectangle has its short side to long side in ratio `1 : 1.618`. When the same ratio governs the relationship between a sidebar and main content, an image and its caption block, or a heading and its body text, the layout reads as *naturally weighted* — neither cramped nor empty. The catch is that φ is opinionated: it pulls compositions toward warmth and editorial feel, away from grid-locked rigidity. Use it where the design wants to feel hand-tuned, not where it must align to a strict baseline grid.
|
|
75
|
+
|
|
76
|
+
```css
|
|
77
|
+
/* φ-grid — sidebar:content ratio of 1:1.618 */
|
|
78
|
+
.layout {
|
|
79
|
+
display: grid;
|
|
80
|
+
grid-template-columns: 1fr 1.618fr; /* φ */
|
|
81
|
+
gap: var(--space-6);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Image card with φ-proportioned caption block */
|
|
85
|
+
.card {
|
|
86
|
+
display: grid;
|
|
87
|
+
grid-template-rows: 1.618fr 1fr; /* image : caption = φ : 1 */
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Root Rectangles — √2, √3, √5
|
|
92
|
+
|
|
93
|
+
| Ratio | Value | Subdivision property | UI fit |
|
|
94
|
+
| ----- | ------ | ------------------------------------------------------- | --------------------------------------------------------------- |
|
|
95
|
+
| √2 | 1.414 | Halving produces another √2 rectangle (ISO paper sizes) | Editorial / print-adjacent UI; documents, articles, long-reads |
|
|
96
|
+
| √3 | 1.732 | Subdivides into three √3 / 2 rectangles | Music, data, spreadsheet-adjacent surfaces with triadic rhythm |
|
|
97
|
+
| √5 | 2.236 | Decomposes into 2 squares + a φ-rectangle | Bridge to φ; very wide hero panels, marquee bands |
|
|
98
|
+
| φ | 1.618 | Removing the largest square leaves another φ-rectangle | Editorial, naturalistic, "hand-tuned" feel |
|
|
99
|
+
|
|
100
|
+
```css
|
|
101
|
+
/* √2 rectangle — print-adjacent article card */
|
|
102
|
+
.article-card {
|
|
103
|
+
aspect-ratio: 1 / 1.414; /* √2 — same proportion as A4 paper */
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* √3 rectangle — triadic data tile */
|
|
107
|
+
.data-tile {
|
|
108
|
+
aspect-ratio: 1 / 1.732;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Dynamic Symmetry
|
|
113
|
+
|
|
114
|
+
Dynamic symmetry (Jay Hambidge, 1920) draws *armatures* inside root rectangles: the diagonal of the whole rectangle plus perpendiculars dropped from each corner to that diagonal. The intersections form anchor points for focal elements. The technique is older than the rule of thirds, more flexible, and produces compositions that hold together at different sizes (responsive layouts) because the armature is ratio-based, not pixel-based. In UI, dynamic symmetry shows up implicitly any time a hero image and its caption follow the diagonal of a φ or √2 rectangle.
|
|
115
|
+
|
|
116
|
+
### Fibonacci
|
|
117
|
+
|
|
118
|
+
The Fibonacci sequence — `1, 1, 2, 3, 5, 8, 13, 21, 34, 55, …` — approaches φ as the ratio between consecutive terms (8 / 5 = 1.6; 13 / 8 = 1.625; 21 / 13 = 1.615). The square-rectangle subdivision pattern uses Fibonacci numbers as side lengths: a 1×1 square next to a 1×1 square forms a 1×2, append a 2×2 to make a 3×2, append a 3×3 to make a 5×3, and so on — each new square's side is the previous two added together. The diagonal through these squares traces the golden spiral.
|
|
119
|
+
|
|
120
|
+
In UI, Fibonacci numbers show up in two places: (a) spacing scales — `4 8 12 20 32 52 84` is a Fibonacci-flavored scale (and see [./visual-hierarchy-layout.md §Whitespace as Design Element](./visual-hierarchy-layout.md) for the applied scale); (b) content sizing where natural proportion matters more than strict alignment (a 5-column / 3-column / 2-column nested layout reads as proportional because consecutive Fibonacci pairs approach φ). Fibonacci is the *integer-friendly approximation of φ* — use it when you want φ's feel but need round numbers for token systems.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Focal-Point Construction
|
|
125
|
+
|
|
126
|
+
Every composition declares 0, 1, or 2+ focal points. A composition with 0 focal points reads as a pattern or texture — fine for backgrounds, wrong for content. A composition with too many focal points reads as noisy and unfocused. Choosing the right count is the first compositional decision; placing them on a power point or armature anchor is the second.
|
|
127
|
+
|
|
128
|
+
### Single-Focal
|
|
129
|
+
|
|
130
|
+
One element dominates — significantly larger, higher contrast, more isolated, or more saturated than every sibling. The eye lands once and stays. Fits archetypes where there is exactly one decision to make or one piece of information to absorb.
|
|
131
|
+
|
|
132
|
+
- **UI archetypes:** landing hero, empty state, error page, sign-in / sign-up form, paywall, confirmation modal, onboarding step.
|
|
133
|
+
- **Audit detection:** one element scores ≥ 1.5× the visual weight of every other element on the page (see [§Visual-Weight Calculus](#visual-weight-calculus)).
|
|
134
|
+
- **Common failure:** decorative imagery competing with the primary CTA — image and CTA weight scores within 10%, eye bounces between them.
|
|
135
|
+
|
|
136
|
+
### Dual-Focal
|
|
137
|
+
|
|
138
|
+
Two elements compete intentionally — same weight, placed at opposite power points or mirrored across an axis. The eye is invited to compare. Fits archetypes where the user is choosing between exactly two paths.
|
|
139
|
+
|
|
140
|
+
- **UI archetypes:** pricing compare view (basic vs. pro), before/after slider, plan-A vs. plan-B, A/B testimonial pair, fork-in-the-road CTA (Login | Sign Up).
|
|
141
|
+
- **Audit detection:** exactly two elements with visual weights within 10% of each other and weights ≥ 2× the third-heaviest element; they sit on opposing thirds-power-points or mirror axes.
|
|
142
|
+
- **Common failure:** the two focal elements drift in weight as one gets a "recommended" badge — what was dual-focal becomes single-focal with a decorative competitor.
|
|
143
|
+
|
|
144
|
+
### Distributed
|
|
145
|
+
|
|
146
|
+
Three or more elements share weight — none dominates, all are roughly equal. The eye scans rather than locks on. Fits archetypes built around browse-and-select rather than read-and-act.
|
|
147
|
+
|
|
148
|
+
- **UI archetypes:** dashboard (multiple cards / KPIs / charts), gallery, product grid, settings index, file browser, kanban board.
|
|
149
|
+
- **Audit detection:** ≥ 3 elements with visual weights all within 25% of each other; total visual weight is high but no single element exceeds 1.3× the median.
|
|
150
|
+
- **Common failure:** one card accidentally gains a colored background and becomes a de facto focal point; the layout's "grid of equals" reads as "one promoted item plus its supporting cast".
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Visual-Weight Calculus
|
|
155
|
+
|
|
156
|
+
Visual weight is the *perceived heaviness* of an element — how strongly the eye is pulled to it relative to neighbors. It is the product of four factors, each normalized to 0..1:
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
weight = size × contrast × isolation × complexity
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
- **Size** (0..1): the element's area normalized against the largest element on the page. A hero headline at full container width might be 1.0; a footer link at 12px might be 0.05.
|
|
163
|
+
- **Contrast** (0..1): luminance contrast against the immediate background, normalized against the page's maximum contrast pair. Black on white is 1.0; mid-gray on light-gray might be 0.2.
|
|
164
|
+
- **Isolation** (0..1): empty-space margin around the element, normalized against the largest margin on the page. Generous whitespace lifts the score; cramped neighbors lower it.
|
|
165
|
+
- **Complexity** (0..1): internal structure — an image with detail scores higher than a flat color block of the same size; a button with an icon plus a label plus a chevron scores higher than a plain text link.
|
|
166
|
+
|
|
167
|
+
The formula is multiplicative because each factor is necessary — an element with massive size but zero contrast (white text on white) has zero visual weight, regardless of isolation or complexity.
|
|
168
|
+
|
|
169
|
+
### Worked Example — 3 elements in a landing hero
|
|
170
|
+
|
|
171
|
+
Suppose a hero contains a headline, a primary CTA, and a secondary text link.
|
|
172
|
+
|
|
173
|
+
| Element | Size | Contrast | Isolation | Complexity | Weight |
|
|
174
|
+
| ---------------------- | ----- | -------- | --------- | ---------- | ----------------------------- |
|
|
175
|
+
| Headline (H1, 64px) | 0.90 | 0.95 | 0.80 | 0.30 | `0.90 × 0.95 × 0.80 × 0.30` = **0.205** |
|
|
176
|
+
| Primary CTA (button) | 0.25 | 0.90 | 0.70 | 0.60 | `0.25 × 0.90 × 0.70 × 0.60` = **0.095** |
|
|
177
|
+
| Secondary text link | 0.10 | 0.40 | 0.30 | 0.20 | `0.10 × 0.40 × 0.30 × 0.20` = **0.002** |
|
|
178
|
+
|
|
179
|
+
The headline dominates (weight ≈ 0.2), the CTA is secondary (≈ 0.1, half the headline), the text link is near-invisible (≈ 0.002, two orders of magnitude lighter). This is correct for a hero that wants the user to read the headline, then act on the CTA, with the text link as a low-stakes escape hatch.
|
|
180
|
+
|
|
181
|
+
### "Balanced" — defined numerically
|
|
182
|
+
|
|
183
|
+
A two-sided composition is **balanced** when the sum of visual weights on each side of the optical center is within ~20% of the other. Three or more elements distributed are balanced when no single element exceeds 1.3× the median weight (see distributed-focal detection above).
|
|
184
|
+
|
|
185
|
+
### Audit detection signature for imbalance
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# Pseudo-procedure for a layout auditor:
|
|
189
|
+
# 1. Identify every visible element ≥ 16px tall / 16px wide.
|
|
190
|
+
# 2. Score each on the four axes (size, contrast, isolation, complexity).
|
|
191
|
+
# 3. Multiply to get weight.
|
|
192
|
+
# 4. Sum weights on the left and right of the optical center.
|
|
193
|
+
# 5. If left_sum > 1.5 × right_sum (or right > 1.5 × left), flag IMBALANCED.
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The 1.5× threshold catches obvious imbalance; the 20%-of-each-other rule is the *target* a balanced composition aims for. The gap between 20% and 50% is where a human designer's eye is needed — the formula declares "not obviously broken", not "definitively good".
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Optical vs. Mathematical Centering
|
|
201
|
+
|
|
202
|
+
The pixel center of a bounding box is rarely the visual center of the *thing inside the box*. Glyphs have asymmetric ink distribution; icons have asymmetric stroke weight; characters have descenders, ascenders, cap-height, and x-height that do not all line up with the box edges. Mathematical centering — `display: flex; align-items: center; justify-content: center;` — produces a result that *looks* off-center to the eye in three common cases:
|
|
203
|
+
|
|
204
|
+
- An icon glyph with directional weight (a play triangle pointing right has more ink on the left half of its bounding box and reads as shifted-left when math-centered).
|
|
205
|
+
- A button label aligned next to an icon — the label's x-height pulls the label's optical center *below* the icon's optical center.
|
|
206
|
+
- Mixed cap-height + x-height text aligned to a baseline — capitals look "too high" because their cap-height extends above the x-height where the eye expects the line to live.
|
|
207
|
+
|
|
208
|
+
### Asymmetric Glyph Weight — the −1px nudge
|
|
209
|
+
|
|
210
|
+
A right-pointing play triangle visually balances when its bounding box is shifted ~1 px (or 1.5–2 px at larger sizes) to the *right* of the mathematical center. The shift accounts for the empty wedge on the triangle's right side.
|
|
211
|
+
|
|
212
|
+
```css
|
|
213
|
+
/* Mathematically centered play button — looks shifted-LEFT to the eye */
|
|
214
|
+
.btn-play .icon-play { transform: translateX(0); }
|
|
215
|
+
|
|
216
|
+
/* Optically centered — −1px to nudge the visually-heavy edge toward the center */
|
|
217
|
+
.btn-play .icon-play {
|
|
218
|
+
transform: translateX(1px); /* compensate the empty wedge on the right */
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* At larger glyph sizes the nudge scales — roughly 2–3% of the glyph width */
|
|
222
|
+
.btn-play-lg .icon-play {
|
|
223
|
+
transform: translateX(2px);
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
See [./iconography.md](./iconography.md) §1 "Optical Sizing & Stroke Weight" for the broader rules governing stroke-weight optics. The play-triangle case is the prototypical example; the same logic applies to chevrons, asymmetric arrows, and any glyph with a directional point.
|
|
228
|
+
|
|
229
|
+
### Cap-Height vs. X-Height Alignment
|
|
230
|
+
|
|
231
|
+
When a label sits next to an icon, the icon should align to the **cap-height** of capital letters in the label — not the x-height of lowercase, not the baseline. The eye reads the icon's center against the strongest vertical anchor in the type, and cap-height is that anchor. Aligning to x-height makes the icon look "low"; aligning to baseline makes it look "too low".
|
|
232
|
+
|
|
233
|
+
```css
|
|
234
|
+
/* Button with leading icon — align icon to cap-height of label */
|
|
235
|
+
.btn {
|
|
236
|
+
display: inline-flex;
|
|
237
|
+
align-items: center; /* approximate cap-height center */
|
|
238
|
+
gap: 0.5em;
|
|
239
|
+
line-height: 1; /* tighten so cap-height ≈ box-center */
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.btn__icon {
|
|
243
|
+
width: 1em; /* match cap-height, not x-height */
|
|
244
|
+
height: 1em;
|
|
245
|
+
display: inline-block;
|
|
246
|
+
vertical-align: -0.1em; /* fine-tune — icon-by-icon optical nudge */
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* For icon-plus-text where the type has a large x-height ratio (Inter, IBM Plex):
|
|
250
|
+
align-items: baseline is wrong (sinks the icon).
|
|
251
|
+
align-items: center with line-height: 1 keeps the icon at cap-center. */
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The cap-height anchor logic is one half of the story; the other half — modular scale, x-height ratios per typeface — lives in [./typography.md](./typography.md) §Type Scale Systems and §Modular Scale.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Eye-Flow Patterns
|
|
259
|
+
|
|
260
|
+
A page is not read pixel-by-pixel. The eye follows one of three default patterns shaped by reading direction (LTR Western languages assumed; RTL mirrors these horizontally), content density, and the kind of decision the user is making. Designing *against* the dominant pattern produces friction; designing *with* it produces effortless scanning.
|
|
261
|
+
|
|
262
|
+
### Z-Pattern — landing pages, conversion flows
|
|
263
|
+
|
|
264
|
+
The Z-pattern fits sparse, hero-led pages with a clear call to action. The eye lands top-left (logo / brand), sweeps top-right (secondary nav / brand CTA), diagonals down-left (hero headline / body), then sweeps bottom-right (primary CTA terminus). Each anchor of the Z gets one element; the diagonal is the "story" connecting them.
|
|
265
|
+
|
|
266
|
+
```txt
|
|
267
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
268
|
+
│ [Logo] ─────────────────────────────────→ [Nav | Login] │ 1. top-left → top-right
|
|
269
|
+
│ │
|
|
270
|
+
│ │
|
|
271
|
+
│ ╲ │
|
|
272
|
+
│ ╲ │ 2. diagonal sweep
|
|
273
|
+
│ ╲ │
|
|
274
|
+
│ ╲ │
|
|
275
|
+
│ │
|
|
276
|
+
│ [Headline] │ 3. bottom-left
|
|
277
|
+
│ [Body] │
|
|
278
|
+
│ Lorem ipsum dolor sit amet ────────────→ [ Get Started ] │ 4. bottom-right CTA
|
|
279
|
+
└──────────────────────────────────────────────────────────────┘
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Pair with single-focal-point construction (the CTA at the lower-right power point — same coordinate as the rule-of-thirds example above).
|
|
283
|
+
|
|
284
|
+
### F-Pattern — content-heavy, scanning surfaces
|
|
285
|
+
|
|
286
|
+
The F-pattern fits dense pages a user scans rather than reads — search results, news feeds, documentation, settings pages, listings. The eye sweeps horizontally across the top, drops to a shorter horizontal sweep mid-page, then runs vertically down the left edge sampling row-openers. Headings, leading icons, and the first 2–3 words of each row do the heaviest signaling work; deep right-side content gets skipped.
|
|
287
|
+
|
|
288
|
+
```txt
|
|
289
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
290
|
+
│ ════════════════════════════════════════════════════════ │ 1. top sweep (full width)
|
|
291
|
+
│ ─ list item 1 ─ ── ── ── ── ── ── ── ── ── ── ── ── ── ── │
|
|
292
|
+
│ ──── ─── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── │
|
|
293
|
+
│ ══════════════════════════════ ─── ── ── ── ── ── │ 2. mid sweep (partial)
|
|
294
|
+
│ ─ list item 2 │
|
|
295
|
+
│ ──── ─── ── ── ── ── ── ── ── ── │
|
|
296
|
+
│ ─ list item 3 │
|
|
297
|
+
│ ─── ── ── ── ── ── ── ── ── ── ── │
|
|
298
|
+
│ ─ list item 4 │ 3. left-edge sample
|
|
299
|
+
│ ─ list item 5 │
|
|
300
|
+
│ ─ list item 6 │
|
|
301
|
+
└──────────────────────────────────────────────────────────────┘
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Pair with distributed-focal-point construction. Front-load row-openers (titles, leading icons, status indicators) on the left edge. Avoid burying critical information on the right side of rows; the eye does not look there.
|
|
305
|
+
|
|
306
|
+
### Gutenberg Diagram — editorial, reading-heavy
|
|
307
|
+
|
|
308
|
+
The Gutenberg diagram applies to text-dense reading surfaces — long-form articles, blog posts, terms-of-service pages, documentation prose. The eye follows a *reading gravity* from the top-left (primary optical area) diagonally to the bottom-right (terminal area), and the two off-diagonal quadrants — top-right (strong fallow area) and bottom-left (weak fallow area) — receive much less attention. Placing critical information in the fallow areas guarantees it goes unread.
|
|
309
|
+
|
|
310
|
+
```txt
|
|
311
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
312
|
+
│ ★ Primary Optical Area │ ✕ Strong Fallow Area │
|
|
313
|
+
│ (eye lands here first) │ (skipped on first scan) │
|
|
314
|
+
│ │ │
|
|
315
|
+
│ Heading │ side note / decorative │
|
|
316
|
+
│ Lead paragraph... │ │
|
|
317
|
+
│ ────────────────────────────────────────────── │
|
|
318
|
+
│ │ │
|
|
319
|
+
│ ✕ Weak Fallow Area │ ★ Terminal Area │
|
|
320
|
+
│ (rarely returned to) │ (eye comes to rest here) │
|
|
321
|
+
│ │ │
|
|
322
|
+
│ │ Conclusion / CTA │
|
|
323
|
+
└──────────────────────────────────────────────────────────────┘
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Pair with single-focal-point construction. The terminal area is the natural home for a "next" action — a Read More link, a Subscribe CTA, a tip-jar button. Critical content does NOT go in either fallow area.
|
|
327
|
+
|
|
328
|
+
### Choosing the pattern
|
|
329
|
+
|
|
330
|
+
| UI archetype | Eye-flow pattern | Focal-point construction |
|
|
331
|
+
| ----------------------------------------------------- | ----------------- | ------------------------ |
|
|
332
|
+
| Landing page, sign-up, hero-led marketing | Z | Single |
|
|
333
|
+
| Pricing compare, before/after, plan-A vs plan-B | Z (mirrored) | Dual |
|
|
334
|
+
| Search results, news feed, settings index, dashboard | F | Distributed |
|
|
335
|
+
| Documentation, listing, kanban, gallery | F | Distributed |
|
|
336
|
+
| Long-form article, blog post, terms of service | Gutenberg | Single (terminal CTA) |
|
|
337
|
+
| Email newsletter, editorial layout | Gutenberg | Single or dual |
|
|
338
|
+
|
|
339
|
+
For RTL languages (Arabic, Hebrew, Urdu, Farsi) the Z-pattern and Gutenberg diagram mirror horizontally; the F-pattern's left-edge becomes a right-edge sample. See [./visual-hierarchy-layout.md §Asymmetry and Rhythm](./visual-hierarchy-layout.md) for the applied rhythm rules that pair with each pattern.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Cross-References
|
|
344
|
+
|
|
345
|
+
- [./visual-hierarchy-layout.md](./visual-hierarchy-layout.md) — §Compositional Grids (responsive column + baseline grid) and §Asymmetry and Rhythm; composition is the upstream foundation that file assumes.
|
|
346
|
+
- [./iconography.md](./iconography.md) — §1 Optical Sizing & Stroke Weight; the optical-centering rules in this file apply directly to icon glyphs.
|
|
347
|
+
- [./typography.md](./typography.md) — §Type Scale Systems and §Modular Scale; the cap-height vs. x-height alignment rule depends on those scale relationships.
|
|
348
|
+
|
|
349
|
+
Reciprocal inbound cross-links land in Phase 28-06 (additive-only, D-06).
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: contrast-advanced
|
|
3
|
+
type: heuristic
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
phase: 28
|
|
6
|
+
tags: [contrast, apca, wcag-3, accessibility]
|
|
7
|
+
last_updated: 2026-05-18
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Contrast Advanced — APCA (WCAG 3 Draft)
|
|
11
|
+
|
|
12
|
+
WCAG 2.1 / 2.2 contrast (4.5:1 body, 3:1 large text and non-text UI) is owned by [`./accessibility.md`](./accessibility.md) §WCAG 2.1 AA — Required Thresholds. This file owns APCA — the WCAG 3 draft perceptual contrast model — which materially misranks WCAG 2.1 verdicts on thin, large, and colored text. APCA is currently part of the WCAG 3 draft (Silver), not yet at candidate-recommendation stage; threshold values and the math can shift before ratification. Treat APCA as a **design-quality layer** that you stack on top of WCAG 2.1 AA certification, not as a replacement for it.
|
|
13
|
+
|
|
14
|
+
This is the file an agent should consult any time it is auditing a contrast pair that fails perceptually despite passing WCAG 2.1, or passes WCAG 2.1 with a margin that "feels wrong" — almost always one of: thin body text in a mid-gray, large colored text on white, or saturated text on a saturated background. Where WCAG 2.1 says "compute the luminance ratio and check the threshold", this file replaces that hand-wave with explicit perceptual thresholds, three worked misrank cases, and a heuristic mapping back to the legacy ratio so a single audit can satisfy both standards.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## APCA Lc Thresholds
|
|
19
|
+
|
|
20
|
+
APCA reports contrast as **Lc** — a perceptual lightness-contrast score on a scale that runs roughly **−108 to +106**. The sign carries directional meaning: a **positive Lc** denotes darker text on a lighter background (the common case for body copy), and a **negative Lc** denotes lighter text on a darker background (the common case for white-on-dark UI). Many practitioners report the **absolute value** `|Lc|` against the threshold — that is the convention this file uses below. Always confirm whether a calculator reports signed or unsigned Lc before comparing to a threshold.
|
|
21
|
+
|
|
22
|
+
The threshold ladder mirrors the WCAG-2.1 floor-by-use-case structure, but the breakpoints are set against perceptual contrast rather than luminance ratio, so the rank ordering between pairs sometimes flips relative to a WCAG-2.1 audit.
|
|
23
|
+
|
|
24
|
+
| Lc threshold | Use case | Rationale |
|
|
25
|
+
| ------------ | ---------------------- | -------------------------------------------------------------------------- |
|
|
26
|
+
| `Lc 75` | Body text (small) | Small glyphs at body weight need the most perceptual lift to remain legible |
|
|
27
|
+
| `Lc 60` | Large text | Larger glyph area tolerates lower perceptual contrast without legibility loss |
|
|
28
|
+
| `Lc 45` | Non-text UI | Buttons, borders, icons; functional but not body copy |
|
|
29
|
+
| `Lc 30` | Decorative / accent | Logos, accent dividers, brand marks; non-essential to comprehension |
|
|
30
|
+
|
|
31
|
+
A few practical notes on reading the ladder:
|
|
32
|
+
|
|
33
|
+
- **Sign convention.** `|Lc 75|` is the standard target whether the pair is dark-on-light (positive) or light-on-dark (negative). Do not confuse a calculator that returns `Lc −75` with one returning `Lc 75` — the magnitude is the same; only the polarity differs.
|
|
34
|
+
- **Weight and size sensitivity.** APCA's body-text threshold (`Lc 75`) is calibrated for small text at regular weight. Larger or heavier glyphs may legitimately pass at lower magnitudes; APCA's published lookup tables map exact weight × size cells to minimum Lc, with `Lc 60` and `Lc 45` covering the common "large text" and "non-text UI" buckets respectively.
|
|
35
|
+
- **Decorative is not "exempt".** `Lc 30` is the floor for elements where comprehension is not required (a brand watermark, a divider hairline). Anything a user must read or interact with belongs at `Lc 45` or above.
|
|
36
|
+
|
|
37
|
+
### Worked anchor pairs at each threshold
|
|
38
|
+
|
|
39
|
+
Holding the threshold ladder in mind is easier with one canonical pair anchoring each row. These pairs are reference points only — the calculator should always be consulted before shipping:
|
|
40
|
+
|
|
41
|
+
```txt
|
|
42
|
+
Lc 75 body: #1A1A1A on #FFFFFF → APCA ~|95|, WCAG ~17:1 (over-clears both standards)
|
|
43
|
+
Lc 60 large: #333333 on #FFFFFF → APCA ~|85|, WCAG ~12.6:1 (clears both standards)
|
|
44
|
+
Lc 45 non-text UI: #5C5C5C on #FFFFFF → APCA ~|68|, WCAG ~7:1 (clears both; tightens at colored variants)
|
|
45
|
+
Lc 30 decorative: #888888 on #FFFFFF → APCA ~|48|, WCAG ~4.5:1 (clears WCAG body; APCA-decorative only)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The pattern is monotonic on neutral pairs — darker foregrounds raise both WCAG ratio and APCA Lc together. The interesting divergences only appear once color, weight, or size enters the comparison, which is the subject of the next section.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Why 4.5:1 Misranks Thin / Large / Colored Text
|
|
53
|
+
|
|
54
|
+
WCAG 2.1's `4.5:1` is a **luminance contrast ratio**: a log-based ratio of the relative luminances of the lighter and darker pixels, calibrated against a perfectly white-vs-perfectly-black comparison. The formula is `(L1 + 0.05) / (L2 + 0.05)` where `L1`/`L2` are sRGB relative luminances, and it knows nothing about font weight, glyph size, or the hue of either side of the pair.
|
|
55
|
+
|
|
56
|
+
APCA models perceptual contrast: lightness on a perceptually uniform scale, with adjustments for font weight, glyph area, and the directional bias of human contrast sensitivity (we read dark-on-light differently from light-on-dark). The two models agree most of the time on solid black-on-white body copy. They **disagree** in three predictable failure modes — and in each case it is APCA that tracks the human-readable reality:
|
|
57
|
+
|
|
58
|
+
- **Thin mid-grays on white** read worse than the ratio suggests (APCA flags; WCAG passes).
|
|
59
|
+
- **Large saturated text on white** reads better than the ratio suggests (WCAG passes by a wide margin; APCA still passes but the margin is much narrower).
|
|
60
|
+
- **Saturated-on-saturated pairs** read worse than the ratio suggests, because human contrast sensitivity collapses when both sides carry strong hue at similar luminance (APCA flags clearly; WCAG sits ambiguously near the line).
|
|
61
|
+
|
|
62
|
+
Three worked examples make the math concrete. Ratios and Lc values below are **illustrative reference points** from the published APCA calculator at `apcacontrast.com` and a standard WCAG 2.1 contrast checker; production audits MUST recompute against a maintained calculator (APCA's tables update as the WCAG 3 draft advances) and MUST cite the calculator version + spec snapshot date for reproducibility.
|
|
63
|
+
|
|
64
|
+
### Example 1 — Thin mid-gray on white
|
|
65
|
+
|
|
66
|
+
```txt
|
|
67
|
+
Foreground: #666666 (rgb 102, 102, 102)
|
|
68
|
+
Background: #FFFFFF
|
|
69
|
+
Glyph context: body text, 16px, regular weight
|
|
70
|
+
|
|
71
|
+
WCAG 2.1 ratio: ~5.74:1 → PASSES 4.5:1 (body)
|
|
72
|
+
APCA Lc: ~|62| → FAILS Lc 75 (body)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The luminance ratio crosses the 4.5:1 floor comfortably; on paper this pair is compliant. Perceptually, mid-gray body text on white runs out of lift well before the ratio would predict: the dark side is too light to anchor the glyph edges, and at body weight the strokes are thin enough that the eye loses crisp edge contrast. APCA's `Lc 75` body threshold catches this; WCAG 2.1's flat ratio does not. The fix is to darken the foreground (toward `#5A5A5A` or lower) or thicken the weight; both raise Lc.
|
|
76
|
+
|
|
77
|
+
### Example 2 — Large colored text on white
|
|
78
|
+
|
|
79
|
+
```txt
|
|
80
|
+
Foreground: #0066CC (rgb 0, 102, 204)
|
|
81
|
+
Background: #FFFFFF
|
|
82
|
+
Glyph context: large heading, 24px, bold
|
|
83
|
+
|
|
84
|
+
WCAG 2.1 ratio: ~6.72:1 → PASSES 4.5:1 and 3:1
|
|
85
|
+
APCA Lc: ~|78| → PASSES Lc 60 (large)
|
|
86
|
+
Disagreement: margin direction
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Both standards pass this pair, but they disagree on **how much margin** the design has. WCAG 2.1 reports a comfortable 6.72:1 — well above the 3:1 large-text floor and even above the 4.5:1 body floor — which encourages the designer to read this as "high contrast, plenty of headroom". APCA reports `|Lc 78|`, which clears the large-text threshold (`Lc 60`) but is only marginally above the body threshold (`Lc 75`). The simple luminance ratio over-rates saturated blue against white because the formula collapses chroma into luminance; APCA tracks the perceptual reality that the blue glyph edges read with less crispness than a pure black would at the same ratio. The audit lesson is: WCAG ratios on saturated colored text systematically overstate available contrast, and a designer who reduces saturation or shifts the hue trusting the ratio will erode legibility before the ratio reports a problem.
|
|
90
|
+
|
|
91
|
+
### Example 3 — Saturated text on saturated background
|
|
92
|
+
|
|
93
|
+
```txt
|
|
94
|
+
Foreground: #FF6600 (rgb 255, 102, 0)
|
|
95
|
+
Background: #0033AA (rgb 0, 51, 170)
|
|
96
|
+
Glyph context: large UI label, 18px, semibold
|
|
97
|
+
|
|
98
|
+
WCAG 2.1 ratio: ~3.39:1 → FAILS 4.5:1; marginally PASSES 3:1 (large/UI)
|
|
99
|
+
APCA Lc: ~|42| → FAILS Lc 45 (non-text UI)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The luminance ratio sits in the ambiguous gap: it fails body but marginally clears the large-text/UI floor. A WCAG-2.1-only audit might accept this for a button label or a chip. APCA reports `|Lc 42|`, below the `Lc 45` non-text UI threshold and well below the `Lc 60` large-text threshold — the pair is **not** safe for either use. The simple ratio over-rates this pair because two highly saturated colors at similar perceived lightness produce strong chromatic difference but weak lightness contrast; human contrast sensitivity for reading depends primarily on lightness, so the eye reads the boundary as fuzzier than the ratio suggests. APCA exposes the perceptual deficit; WCAG 2.1 hides it.
|
|
103
|
+
|
|
104
|
+
The three examples generalise to a heuristic an auditor can apply by hand: **when a pair involves thin text, large colored text, or saturated-on-saturated, distrust the WCAG ratio and re-check with APCA.** When both standards agree (solid black on white, dark navy on white, white on solid black) the ratio is reliable.
|
|
105
|
+
|
|
106
|
+
### Why the math diverges
|
|
107
|
+
|
|
108
|
+
The structural reason the two models disagree on the three failure modes above is worth naming explicitly, because it explains why the disagreement is **predictable**, not random.
|
|
109
|
+
|
|
110
|
+
- **WCAG 2.1 uses sRGB relative luminance.** The ratio formula collapses each color's R/G/B channels into a single luminance value via a fixed gamma-decoded weighting (`0.2126·R + 0.7152·G + 0.0722·B` after sRGB-to-linear). That luminance is then compared as a log ratio with the `+0.05` offset. The formula is hue-blind: a saturated blue and a mid-gray of equal luminance produce the same ratio against any background. It is also weight-blind and size-blind: a 4.5:1 pair is reported as 4.5:1 whether the text is 8px hairline or 96px black.
|
|
111
|
+
- **APCA uses perceptually weighted lightness.** It first transforms both colors into a perceptually uniform lightness space, then computes a signed contrast that accounts for the directional bias (dark-on-light vs light-on-dark) of human contrast sensitivity. Critically, the **threshold** APCA compares to is not a single number — it is a lookup keyed on the font weight × glyph size at which the pair will be rendered. A 16px regular-weight body pair targets `Lc 75`; a 24px bold pair targets `Lc 60`; a 14px hairline pair targets a value higher than `Lc 75` because the strokes are thinner.
|
|
112
|
+
|
|
113
|
+
The three failure modes line up against this structural difference: thin text fails because WCAG is weight-blind, large colored text passes-with-overstated-margin because WCAG is hue-blind, and saturated-on-saturated fails because the simple luminance ratio aliases strong chromatic contrast with lightness contrast. These are not edge cases the spec authors missed — they are the cost of the simpler 2.1 model in exchange for cheaper computation and easier hand-audit. APCA pays the perceptual cost; WCAG 2.1 pays the simplicity dividend.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## When to Use APCA vs WCAG 2.1 AA
|
|
118
|
+
|
|
119
|
+
The two standards are **not** an either/or choice for production work. APCA is informationally better on perceptual edge cases; WCAG 2.1 AA is the contractually enforceable baseline for accessibility compliance. The defensible production pattern is **dual-target compliance**: ship designs that satisfy both, fall back to a principled tiebreaker only when the two disagree.
|
|
120
|
+
|
|
121
|
+
### Dual-target compliance pattern
|
|
122
|
+
|
|
123
|
+
1. **Default audit floor: WCAG 2.1 AA.** Body text ≥ 4.5:1, large text and non-text UI ≥ 3:1. This is the floor for accessibility certification, procurement contracts, public-sector compliance, and any audit a third party will run.
|
|
124
|
+
2. **Design-quality layer: APCA Lc thresholds.** Body `Lc 75`, large `Lc 60`, non-text UI `Lc 45`, decorative `Lc 30`. This is the floor for perceptual quality and for the three misrank cases above (thin, large-colored, colored-on-colored).
|
|
125
|
+
3. **Tiebreaker when the two disagree.** Prefer **satisfying both** — almost every pair has a small foreground or background adjustment that clears both simultaneously. When forced to choose, prefer APCA for **body text** (perceptual legibility matters more than the legacy ratio when a user is reading) and prefer WCAG 2.1 AA for **non-text UI** (focus rings, button borders, icon glyphs — where contractual certification matters more than the perceptual edge case).
|
|
126
|
+
|
|
127
|
+
The order is intentional. The WCAG 2.1 AA floor is the legally and contractually defensible baseline; treating APCA as a *replacement* would shrink the compliance surface and break audits run by third parties using legacy tools. Treating APCA as an *additive* design-quality layer expands the design surface without breaking compliance.
|
|
128
|
+
|
|
129
|
+
### Common audit-finding patterns
|
|
130
|
+
|
|
131
|
+
When the dual-target pattern is applied in practice, a small set of finding patterns surfaces repeatedly. Naming them lets an audit triage the result rather than re-deriving the verdict from raw numbers each time.
|
|
132
|
+
|
|
133
|
+
- **`apca-flags-thin-body`** — WCAG 2.1 body passes (≥ 4.5:1), APCA fails `Lc 75`. Mid-gray body copy on white; the dominant fix is darkening the foreground by 1-2 modular steps on the lightness axis.
|
|
134
|
+
- **`wcag-margin-overstated`** — both standards pass, but APCA reports a margin substantially narrower than the WCAG ratio implies. Common on saturated colored text on white. Action: do not lean further into the apparent WCAG headroom (e.g., by lowering chroma or shifting hue toward background); the perceptual margin is already thinner than the ratio reports.
|
|
135
|
+
- **`saturated-on-saturated-trap`** — WCAG 2.1 sits ambiguously in the 3:1-to-4.5:1 band, APCA fails clearly. High-saturation color-on-color pairs. The dominant fix is widening the lightness gap (push foreground darker or background lighter), not adjusting the hue choice.
|
|
136
|
+
- **`focus-ring-wcag-pass-apca-borderline`** — non-text UI pair clears WCAG 2.1 `3:1` but sits at or just below APCA `Lc 45`. Common on light-gray focus rings around white inputs. Action: APCA-aware designers raise the ring contrast even though WCAG would let it ship.
|
|
137
|
+
- **`light-on-dark-asymmetry`** — same `|Lc|` magnitude reads differently dark-on-light vs light-on-dark; pairs that pass dark-mode body can fail when polarity-flipped to light-mode body. Audit both polarities independently rather than assuming symmetry.
|
|
138
|
+
|
|
139
|
+
These patterns are useful labels for the dual-target dashboard view: an audit can report "3 `apca-flags-thin-body` findings, 1 `saturated-on-saturated-trap` finding, 0 hard WCAG 2.1 fails" and the design team immediately knows the corrective work concentrates on the body-text color choice, not on the WCAG-side certification.
|
|
140
|
+
|
|
141
|
+
### Draft-status caveat
|
|
142
|
+
|
|
143
|
+
APCA is currently in the WCAG 3 draft. The threshold values cited above (`Lc 75 / 60 / 45 / 30`), the calculator implementation, and the lookup-table mapping of weight × size to minimum Lc can all shift before WCAG 3 reaches candidate recommendation. Reproducible audits MUST cite:
|
|
144
|
+
|
|
145
|
+
- The APCA calculator version used (e.g., `apcacontrast.com` build `0.1.9 W3`)
|
|
146
|
+
- The WCAG 3 draft snapshot date used as the spec reference
|
|
147
|
+
- The conversion table version used (the table in the next section is heuristic and approximate)
|
|
148
|
+
|
|
149
|
+
Any audit that does not cite these is not reproducible — a re-audit six months later may produce different verdicts on the same pairs, not because the design changed but because the underlying spec advanced.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Lc ↔ WCAG 2.1 Conversion Table
|
|
154
|
+
|
|
155
|
+
The table below is an **approximate heuristic** for legacy interop — it lets an auditor running a WCAG 2.1 contrast checker spot which pairs are likely to satisfy APCA-equivalent perceptual contrast and which are likely to fall short. APCA and WCAG 2.1 measure fundamentally different things (perceptual lightness contrast vs luminance ratio), so a precise one-to-one mapping does not exist; the table is calibrated against the common case of solid neutral text on a solid neutral background, and drifts in either direction once saturated hue enters the pair.
|
|
156
|
+
|
|
157
|
+
| APCA Lc threshold | WCAG 2.1 ratio (approx) | Common case |
|
|
158
|
+
| ----------------- | ----------------------- | --------------------------------- |
|
|
159
|
+
| `Lc 75` | `~7:1` | Body text, strict (AAA-equivalent) |
|
|
160
|
+
| `Lc 60` | `~4.5:1` | Body text minimum (AA body floor) |
|
|
161
|
+
| `Lc 45` | `~3:1` | Large text / non-text UI |
|
|
162
|
+
| `Lc 30` | `~2:1` | Decorative / accent |
|
|
163
|
+
|
|
164
|
+
Use this table in two directions:
|
|
165
|
+
|
|
166
|
+
- **WCAG-pass-to-APCA check.** A pair that satisfies WCAG 2.1 AA body (`4.5:1`) maps to roughly `Lc 60` — which is APCA's *large text* floor, not its body floor. A WCAG-2.1 body-compliant design is **not** automatically APCA body-compliant. Re-check body text against `Lc 75`.
|
|
167
|
+
- **APCA-pass-to-WCAG check.** A pair that satisfies APCA body (`Lc 75`) maps to roughly `7:1` — well above WCAG 2.1 AA's `4.5:1` floor. APCA body-compliant designs are almost always WCAG 2.1 AA body-compliant; the reverse is not true.
|
|
168
|
+
|
|
169
|
+
The asymmetry is the practical lesson: **APCA is the stricter floor for body text.** A design that targets APCA body and ships through a WCAG 2.1 AA audit will pass both with margin. A design that targets WCAG 2.1 AA body and is then APCA-audited will frequently surface "passes WCAG, fails APCA Lc 75" findings on thin and colored text — those findings are real, not noise.
|
|
170
|
+
|
|
171
|
+
The conversion drifts at the saturated-hue edges. On saturated-on-saturated pairs (Example 3 above), the WCAG ratio over-rates contrast relative to APCA Lc; the heuristic ratio in the table reads optimistic. On thin gray body text (Example 1), the WCAG ratio also over-rates — `5.74:1` maps to `~Lc 62` in the table-extrapolated direction, but the measured Lc was `~62` and the threshold was `75`, so the pair still fails APCA body despite passing the table-implied WCAG-equivalent. **Treat the table as a screening tool, not as a substitute for re-running the actual APCA calculator on the actual pair.**
|
|
172
|
+
|
|
173
|
+
### Applying the table to the three worked examples
|
|
174
|
+
|
|
175
|
+
Walking the three misrank cases through the conversion table makes the screening-tool behaviour explicit:
|
|
176
|
+
|
|
177
|
+
```txt
|
|
178
|
+
Example 1 — #666666 on #FFFFFF (thin body)
|
|
179
|
+
WCAG ratio: ~5.74:1 → table-row ~Lc 60 (just above body floor)
|
|
180
|
+
APCA Lc: ~|62| → matches the table row
|
|
181
|
+
Verdict: APCA body threshold is Lc 75; the table-mapped Lc 60 fails body
|
|
182
|
+
Reading: table screening agrees with the direct APCA measurement here
|
|
183
|
+
|
|
184
|
+
Example 2 — #0066CC on #FFFFFF (large colored)
|
|
185
|
+
WCAG ratio: ~6.72:1 → table-row ~Lc 75 (body-strict)
|
|
186
|
+
APCA Lc: ~|78| → close to the table row
|
|
187
|
+
Verdict: Both standards pass; APCA margin tighter than ratio suggests
|
|
188
|
+
Reading: table screening would have caught this had body been the target
|
|
189
|
+
|
|
190
|
+
Example 3 — #FF6600 on #0033AA (saturated-on-saturated)
|
|
191
|
+
WCAG ratio: ~3.39:1 → table-row between Lc 30 and Lc 45 (decorative-to-UI)
|
|
192
|
+
APCA Lc: ~|42| → matches the lower end of that band
|
|
193
|
+
Verdict: APCA non-text UI threshold is Lc 45; this falls below
|
|
194
|
+
Reading: table screening flags this as risky; direct APCA confirms
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
In two of the three cases the table screening converges on the same verdict the direct APCA calculator produces. In the third (large colored text), the table screening *would* have caught the tight margin had the target been body text rather than large text — exactly the kind of design-quality consideration the dual-target pattern is designed to surface. The table is therefore a useful first-pass filter for designers working in a WCAG-2.1-centric tool stack, with the explicit understanding that any pair the screening surfaces as borderline must be re-run against the actual APCA calculator before a decision ships.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Cross-References
|
|
202
|
+
|
|
203
|
+
- [`./accessibility.md`](./accessibility.md) §WCAG 2.1 AA — Required Thresholds: legacy luminance-ratio floor; pair with APCA Lc for dual-target compliance.
|
|
204
|
+
|
|
205
|
+
> The reciprocal inbound cross-link from [`./accessibility.md`](./accessibility.md) (a "see also: APCA / WCAG 3 draft" pointer into the §WCAG 2.1 / 2.2 section) lands in Phase 28-06 (additive-only, per D-06) and is not present at the time this file ships.
|
|
@@ -33,6 +33,8 @@ Examples:
|
|
|
33
33
|
- `--space-8: 8px`
|
|
34
34
|
- `--font-size-16: 16px`
|
|
35
35
|
|
|
36
|
+
**See:** [`./proportion-systems.md`](./proportion-systems.md) for the whole-UI proportion system covering 4pt/8pt/√2 baseline grids, baseline-grid lock, vertical rhythm, and modular relationships across type / spacing / sizing / radius scales — the primitive tokens above are the codified output of that system.
|
|
37
|
+
|
|
36
38
|
Primitive tokens should never be used directly in component code. They exist only to feed the semantic layer. This constraint is essential: if components reference primitive tokens directly, you lose the ability to theme them without modifying component code.
|
|
37
39
|
|
|
38
40
|
### Semantic Layer (Roles)
|
|
@@ -10,6 +10,8 @@ Wroblewski's eye-tracking research measured the number of eye fixations required
|
|
|
10
10
|
|
|
11
11
|
**Top-aligned labels** are the default for most forms. Completion time is fastest because the label and input form a single vertical unit. They also accommodate longer translated strings without breaking layout, making them the correct default for internationalised products. Use top-aligned labels whenever form complexity is moderate to high or field types are mixed.
|
|
12
12
|
|
|
13
|
+
**See:** [`./i18n.md`](./i18n.md) §Locale Formatting for the full `Intl.*` family (`NumberFormat`, `DateTimeFormat`, `PluralRules`, `RelativeTimeFormat`, `ListFormat`) over hand-rolled string concatenation — never assemble locale-aware strings via template literals. §Text Expansion explains the +30–40% LTR budget that top-aligned labels accommodate.
|
|
14
|
+
|
|
13
15
|
**Left-aligned labels** create the slowest completion time because the eye must saccade horizontally from label to field on every row. They earn their place only when scannability of values matters more than speed — settings pages, data-entry tables, or read-heavy forms where users compare label and value together. In these contexts the horizontal alignment aids review, not input.
|
|
14
16
|
|
|
15
17
|
**Floating labels (Material 3 style)** are an acceptable middle ground when screen real estate is severely constrained. The label begins as visible hint text above the field and remains visible (shrunk, repositioned) after the user starts typing. This pattern is distinct from — and incompatible with — placeholder-as-label. Never use placeholder text as the sole label: when the field receives focus the placeholder disappears, leaving the user to recall what was asked. This is a WCAG 2.1 failure under criterion 1.3.5 (Identify Input Purpose) and a general usability failure on any form longer than three fields.
|