@adia-ai/adia-ui-forge 0.8.61 → 0.8.63
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/plugin.json +2 -2
- package/.codex-plugin/plugin.json +3 -3
- package/CHANGELOG.md +309 -274
- package/README.md +54 -18
- package/__init__.py +8 -8
- package/agents/a2ui-maintenance-agent.corpus.json +7 -7
- package/agents/a2ui-maintenance-agent.md +6 -6
- package/agents/a2ui-planner-agent.md +11 -11
- package/agents/demo-audit-agent.corpus.json +4 -4
- package/agents/demo-audit-agent.md +8 -9
- package/agents/framework-checker.md +10 -11
- package/agents/framework-planner-agent.md +9 -10
- package/agents/gen-ui-review-agent.corpus.json +4 -4
- package/agents/gen-ui-review-agent.md +8 -8
- package/agents/package-release-agent.corpus.json +4 -4
- package/agents/package-release-agent.md +9 -10
- package/agents/primitive-authoring-agent.corpus.json +1 -1
- package/agents/primitive-authoring-agent.md +5 -5
- package/agents/routing-corpus.json +46 -46
- package/codex.interface.json +1 -1
- package/commands/gen-ui-review.md +1 -1
- package/commands/package-release.md +2 -2
- package/package.json +1 -1
- package/plugin.yaml +2 -2
- package/prompts/gen-ui-review.md +1 -1
- package/prompts/package-release.md +2 -2
- package/references/contracts/a2ui-mcp-surface.md +7 -7
- package/references/contracts/migration-guide-format.md +6 -7
- package/references/shared/content-trust.md +3 -5
- package/skills/a2ui-maintenance/SKILL.md +13 -16
- package/skills/a2ui-maintenance/evals/evals.json +2 -2
- package/skills/a2ui-maintenance/evals/routing-corpus.json +1 -1
- package/skills/a2ui-maintenance/references/anti-patterns.md +10 -10
- package/skills/a2ui-maintenance/references/chunk-authoring.md +11 -12
- package/skills/a2ui-maintenance/references/corpus-discipline.md +13 -14
- package/skills/a2ui-maintenance/references/data-model-reactivity.md +22 -25
- package/skills/a2ui-maintenance/references/eval-diagnostics.md +21 -21
- package/skills/a2ui-maintenance/references/format-extension-decisions.md +12 -13
- package/skills/a2ui-maintenance/references/leverage-rules.md +6 -6
- package/skills/a2ui-maintenance/references/mcp-pipeline-ops.md +9 -10
- package/skills/a2ui-maintenance/references/mcp-tool-reference.md +11 -12
- package/skills/a2ui-maintenance/references/pipeline-overview.md +33 -38
- package/skills/a2ui-maintenance/references/semantic-fail-lifting.md +13 -14
- package/skills/a2ui-maintenance/references/strategy-engines.md +14 -16
- package/skills/a2ui-maintenance/references/surface-lifecycle.md +10 -11
- package/skills/a2ui-maintenance/references/zettel-calibration.md +14 -16
- package/skills/component-md-authoring/SKILL.md +20 -21
- package/skills/component-md-authoring/agents/openai.yaml +1 -1
- package/skills/cross-harness-authoring-standards/SKILL.md +19 -21
- package/skills/cross-harness-authoring-standards/agents/openai.yaml +1 -1
- package/skills/cross-harness-compatibility-standards/SKILL.md +34 -35
- package/skills/cross-harness-compatibility-standards/agents/openai.yaml +1 -1
- package/skills/demo-audit/SKILL.md +38 -38
- package/skills/demo-audit/evals/routing-corpus.json +1 -1
- package/skills/demo-audit/references/admin-shell-anatomy.md +21 -25
- package/skills/demo-audit/references/app-shell-pitfalls.md +11 -13
- package/skills/demo-audit/references/auto-fix-allowlist.md +3 -3
- package/skills/demo-audit/references/card-anatomy-sweep.md +10 -11
- package/skills/demo-audit/references/chat-shell-anatomy.md +25 -26
- package/skills/demo-audit/references/editor-shell-anatomy.md +25 -28
- package/skills/demo-audit/references/html-attr-sweep.md +12 -13
- package/skills/demo-audit/references/mode7-status-battery.md +4 -4
- package/skills/demo-audit/references/native-leak-annotations.md +8 -8
- package/skills/demo-audit/references/output-contract-worked-example.md +8 -8
- package/skills/demo-audit/references/probe-discipline-and-escalation.md +2 -2
- package/skills/demo-audit/references/visual-probe-triage.md +21 -23
- package/skills/gen-ui-review/SKILL.md +27 -27
- package/skills/gen-ui-review/agents/openai.yaml +1 -1
- package/skills/gen-ui-review/evals/routing-corpus.json +1 -1
- package/skills/gen-ui-review/references/corpus-html-patterns.md +28 -29
- package/skills/gen-ui-review/references/exit-gate-mechanics.md +5 -5
- package/skills/gen-ui-review/references/lookup-maintenance.md +2 -2
- package/skills/gen-ui-review/references/loop-protocol.md +25 -25
- package/skills/gen-ui-review/references/rubric-cosmetic.md +10 -10
- package/skills/gen-ui-review/references/rubric-decompose.md +15 -16
- package/skills/gen-ui-review/references/rubric-score.md +23 -24
- package/skills/gen-ui-review/references/scorecard-worked-examples.md +12 -12
- package/skills/gen-ui-review/references/scores.schema.json +2 -2
- package/skills/llm-client-maintenance/SKILL.md +12 -12
- package/skills/llm-client-maintenance/evals/routing-corpus.json +1 -1
- package/skills/llm-client-maintenance/references/adapter-contract.md +27 -27
- package/skills/llm-client-maintenance/references/add-a-provider.md +25 -25
- package/skills/llm-client-maintenance/references/bridge-facade.md +31 -31
- package/skills/llm-client-maintenance/references/browser-proxy-boundary.md +17 -17
- package/skills/llm-client-maintenance/references/model-registry.md +14 -14
- package/skills/llm-client-maintenance/references/streaming-sse.md +21 -21
- package/skills/package-release/SKILL.md +21 -23
- package/skills/package-release/evals/evals.json +2 -2
- package/skills/package-release/evals/routing-corpus.json +2 -2
- package/skills/package-release/references/authorization-model.md +6 -8
- package/skills/package-release/references/changelog-discipline.md +25 -27
- package/skills/package-release/references/cut-procedure.md +129 -136
- package/skills/package-release/references/gates-catalog.md +42 -43
- package/skills/package-release/references/independent-package-release.md +14 -15
- package/skills/package-release/references/invariants-detail.md +9 -9
- package/skills/package-release/references/mechanization.md +11 -12
- package/skills/package-release/references/migration-guide-authoring.md +20 -20
- package/skills/package-release/references/notes-authoring.md +20 -20
- package/skills/package-release/references/recovery-paths.md +28 -28
- package/skills/primitive-authoring/SKILL.md +19 -19
- package/skills/primitive-authoring/agents/openai.yaml +1 -1
- package/skills/primitive-authoring/evals/evals.json +2 -2
- package/skills/primitive-authoring/evals/routing-corpus.json +2 -2
- package/skills/primitive-authoring/references/INDEX.md +22 -22
- package/skills/primitive-authoring/references/anti-patterns.md +76 -77
- package/skills/primitive-authoring/references/api-contract.md +63 -65
- package/skills/primitive-authoring/references/authoring-cycle.md +47 -50
- package/skills/primitive-authoring/references/canonical-pattern-index.md +118 -118
- package/skills/primitive-authoring/references/code-style.md +50 -52
- package/skills/primitive-authoring/references/common-gotchas.md +23 -23
- package/skills/primitive-authoring/references/composite-demo-protocol.md +42 -45
- package/skills/primitive-authoring/references/css-patterns.md +97 -97
- package/skills/primitive-authoring/references/for-attribute-event-contract.md +12 -12
- package/skills/primitive-authoring/references/form-control-sizing.md +16 -18
- package/skills/primitive-authoring/references/lifecycle-patterns.md +42 -42
- package/skills/primitive-authoring/references/llm-bridge.md +21 -21
- package/skills/primitive-authoring/references/module-promotion.md +51 -51
- package/skills/primitive-authoring/references/primitive-audit.md +16 -16
- package/skills/primitive-authoring/references/shell-patterns.md +81 -81
- package/skills/primitive-authoring/references/svg-authoring.md +62 -66
- package/skills/primitive-authoring/references/token-contract.md +45 -49
- package/skills/primitive-authoring/references/trait-pages.md +8 -8
- package/skills/primitive-authoring/references/worked-example.md +39 -39
- package/skills/primitive-authoring/references/yaml-contract.md +139 -143
- package/skills/site-deployment/SKILL.md +25 -26
- package/skills/site-deployment/agents/openai.yaml +1 -1
- package/skills/site-deployment/evals/routing-corpus.json +1 -1
- package/skills/site-deployment/references/deploy-playbooks.md +51 -53
- package/skills/site-docs-authoring/SKILL.md +14 -14
- package/skills/site-docs-authoring/agents/openai.yaml +1 -1
- package/skills/site-docs-authoring/evals/evals.json +1 -1
- package/skills/site-docs-authoring/evals/routing-corpus.json +1 -1
- package/skills/site-docs-authoring/intent.md +18 -18
- package/skills/ssr-compatibility/SKILL.md +24 -25
- package/skills/ssr-compatibility/agents/openai.yaml +1 -1
- package/skills/ssr-compatibility/evals/evals.json +1 -1
- package/skills/ssr-compatibility/evals/routing-corpus.json +1 -1
- package/skills/ssr-compatibility/references/consumer-workarounds.md +22 -23
- package/skills/ssr-compatibility/references/failure-shapes.md +84 -91
- package/skills/ssr-compatibility/references/guard-patterns.md +54 -58
- package/skills/ssr-compatibility/references/status-ledger.md +17 -17
- package/skills/ssr-compatibility/references/test-without-linkedom.md +26 -27
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# CSS Patterns
|
|
1
|
+
# CSS Patterns, Two-Block `@scope`, Variants, Modes, Tokens
|
|
2
2
|
|
|
3
3
|
Deep dive on AdiaUI's CSS architecture. Read when authoring a new component stylesheet, adding a variant that feels layout-shaped, or editing the token block of an existing component.
|
|
4
4
|
|
|
@@ -18,7 +18,7 @@ Every component CSS file has exactly this shape:
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
:scope {
|
|
21
|
-
/* ── Base styles
|
|
21
|
+
/* ── Base styles, consume component tokens only ── */
|
|
22
22
|
box-sizing: border-box;
|
|
23
23
|
display: inline-flex;
|
|
24
24
|
align-items: center;
|
|
@@ -46,13 +46,13 @@ Every component CSS file has exactly this shape:
|
|
|
46
46
|
**Why two blocks?**
|
|
47
47
|
|
|
48
48
|
- `:where(:scope)` has specificity `(0,0,0)`. Consumer overrides, theme providers, and nested surface rules all beat it cleanly.
|
|
49
|
-
- `:scope` has specificity `(0,1,0)
|
|
49
|
+
- `:scope` has specificity `(0,1,0)`, enough to beat `:where()` inside the same file, but low enough to compose across components without `!important`.
|
|
50
50
|
- The separation enforces the rule visually: tokens in the first block, styles in the second. A reviewer can spot a violation at a glance.
|
|
51
51
|
|
|
52
52
|
**Common mistake:** collapsing both into one `:scope` block.
|
|
53
53
|
|
|
54
54
|
```css
|
|
55
|
-
/* WRONG
|
|
55
|
+
/* WRONG, tokens and styles interleaved */
|
|
56
56
|
:scope {
|
|
57
57
|
--component-bg: var(--a-bg);
|
|
58
58
|
background: var(--component-bg);
|
|
@@ -64,17 +64,17 @@ When a parent tries to override `--component-bg`, specificity beats them. The ze
|
|
|
64
64
|
|
|
65
65
|
## Region elements never self-style (2026-09-01, ADR-0105)
|
|
66
66
|
|
|
67
|
-
Native `<header>`/`<section>`/`<footer>` and `<header-ui>`/`<section-ui>`/`<footer-ui>` (a CSS-only slot-routing anatomy stub
|
|
67
|
+
Native `<header>`/`<section>`/`<footer>` and `<header-ui>`/`<section-ui>`/`<footer-ui>` (a CSS-only slot-routing anatomy stub, no JS, no events, no `.css` file of its own) are **region elements**, not components in their own right. They carry zero layout CSS. Every grid, gap, sticky rule, and slot-vocabulary variant a region renders is written in the **closest host pattern's** `@scope (host-name)` block, `page-ui`, `card-ui`, `drawer-ui`, `modal-ui`, `anchor-bar`, the shells, matching the region by tag + attribute selector (`:scope > :where(header, header-ui)`, `:has(> [slot="action"])`, etc.), never in a region-owned stylesheet.
|
|
68
68
|
|
|
69
|
-
**If you're authoring a new host pattern that accepts region children, you MUST ship its `@scope` treatment for every region shape it accepts before it ships.** A host that accepts `<header-ui>` without a matching grid/sticky/slot-vocabulary block is not a lesser-but-valid host
|
|
69
|
+
**If you're authoring a new host pattern that accepts region children, you MUST ship its `@scope` treatment for every region shape it accepts before it ships.** A host that accepts `<header-ui>` without a matching grid/sticky/slot-vocabulary block is not a lesser-but-valid host, the region element structurally cannot supply the missing layout itself, so the host renders wrong (usually unstyled block flow, or, the observed failure mode, a stale layout that packs content into the wrong shape). gh#2760 is the live example: `page-ui`'s header-band `@scope` block didn't reproduce the retired `admin-page-header`'s action-cluster grid for a multi-item `[slot="action"]` containing a block-level `<tabs-ui>`, so tabs overlapped buttons instead of getting their own row, same markup, incomplete host coverage.
|
|
70
70
|
|
|
71
|
-
**Bare `<header>` vs `<header-ui
|
|
71
|
+
**Bare `<header>` vs `<header-ui>`**, both reach a host's tag-matched rules identically (`:where(header, header-ui, …)` gives them equal zero specificity). They diverge only on rules keyed off a named-slot attribute selector (`:has(> [slot="icon"])`, etc.), and there the divergence isn't mechanical (`slot=` is decorative everywhere, ADR-0033), it's documentation: `header-ui`'s own yaml is the only place the icon/heading/description/action vocabulary is written down and steered toward by `a2ui.rules`. Use bare `<header>`/`<section>`/`<footer>` only for unstructured default-slot content; use the `-ui` stub whenever content decomposes into the slot vocabulary, that's the only path an author or the A2UI generator discovers it exists.
|
|
72
72
|
|
|
73
|
-
A region element acquiring its own `.css` file (layout rules scoped to `header-ui` itself, not the host) is the anti-pattern this section exists to name
|
|
73
|
+
A region element acquiring its own `.css` file (layout rules scoped to `header-ui` itself, not the host) is the anti-pattern this section exists to name, see ADR-0105 for the full rationale and the corollary above.
|
|
74
74
|
|
|
75
75
|
Source: [ADR-0105](../../../../../../docs/ops/adr/adr-0105-region-elements-never-self-style.md).
|
|
76
76
|
|
|
77
|
-
## Variants vs modes
|
|
77
|
+
## Variants vs modes, the decision tree
|
|
78
78
|
|
|
79
79
|
The rule: **variants change tokens; modes change layout.**
|
|
80
80
|
|
|
@@ -94,28 +94,28 @@ Does your [attribute=value] need to change any of:
|
|
|
94
94
|
|
|
95
95
|
**Approved mode attributes** (as of the most recent contract update):
|
|
96
96
|
|
|
97
|
-
- `code-ui[inline]
|
|
98
|
-
- `divider-ui[vertical]
|
|
99
|
-
- `tabs-ui[orientation="vertical"]
|
|
100
|
-
- `input-ui / textarea-ui / select-ui / slider-ui[data-direction="row"]
|
|
101
|
-
- `toast-ui[position="..."]
|
|
102
|
-
- `nav-ui / pane-ui / cot-ui[collapsed]
|
|
103
|
-
- `list-ui[divider]
|
|
104
|
-
- `description-list-ui[layout="inline"]
|
|
105
|
-
- `chart-ui[type="sparkline"|"segments"]
|
|
106
|
-
- `chat-ui[data-role="user"|"assistant"]
|
|
107
|
-
- `timeline-ui[orientation="horizontal"]
|
|
108
|
-
- `timeline-ui[mode="steps"]
|
|
109
|
-
- `button-ui[block]
|
|
110
|
-
- `pagination-ui[variant="button"]
|
|
97
|
+
- `code-ui[inline]`, inline vs block display
|
|
98
|
+
- `divider-ui[vertical]`, flex-direction, width ↔ height swap
|
|
99
|
+
- `tabs-ui[orientation="vertical"]`, flex-direction swap
|
|
100
|
+
- `input-ui / textarea-ui / select-ui / slider-ui[data-direction="row"]`, grid rewrite
|
|
101
|
+
- `toast-ui[position="..."]`, fixed positioning corner
|
|
102
|
+
- `nav-ui / pane-ui / cot-ui[collapsed]`, width/height collapse
|
|
103
|
+
- `list-ui[divider]`, gap:0 for visual seam
|
|
104
|
+
- `description-list-ui[layout="inline"]`, grid-template change
|
|
105
|
+
- `chart-ui[type="sparkline"|"segments"]`, strip vs full layout
|
|
106
|
+
- `chat-ui[data-role="user"|"assistant"]`, bubble alignment
|
|
107
|
+
- `timeline-ui[orientation="horizontal"]`, flex-direction swap
|
|
108
|
+
- `timeline-ui[mode="steps"]`, flex-direction + step-counter layout
|
|
109
|
+
- `button-ui[block]`, block-fill with `display: flex; width: 100%`
|
|
110
|
+
- `pagination-ui[variant="button"]`, square 1:1 page buttons
|
|
111
111
|
|
|
112
|
-
This list is the single source of truth. If your mode isn't here, add it. If adding would feel weird, that's a signal the "mode" is actually a **sibling component
|
|
112
|
+
This list is the single source of truth. If your mode isn't here, add it. If adding would feel weird, that's a signal the "mode" is actually a **sibling component**, prefer `code-inline-ui` over `code-ui[inline]` unless the attribute genuinely toggles one surface between two states of the same thing.
|
|
113
113
|
|
|
114
|
-
**Orthogonal boolean combinations need every combination defined, not left as an undefined hybrid.** Two independently-toggleable boolean attributes on the same component (e.g. stat-ui's `band` and `bleed`) form an N² space; shipping three of the four combinations and leaving the fourth undefined means a consumer who reaches it gets whatever the cascade happens to produce, not a designed layout. ADR-0083 gave stat-ui's `band bleed` combination a real contract
|
|
114
|
+
**Orthogonal boolean combinations need every combination defined, not left as an undefined hybrid.** Two independently-toggleable boolean attributes on the same component (e.g. stat-ui's `band` and `bleed`) form an N² space; shipping three of the four combinations and leaving the fourth undefined means a consumer who reaches it gets whatever the cascade happens to produce, not a designed layout. ADR-0083 gave stat-ui's `band bleed` combination a real contract, the band bleeds inline-start/inline-end/block-end via the `--card-inset` negative-extent technique, with `[slot="change"]` overlaid as a z-ordered, `pointer-events: none` chip anchored block-start/inline-end. When you add a second orthogonal boolean to an existing single-boolean variant, audit all four quadrants before shipping, an unstyled hybrid is a defect, not a follow-up.
|
|
115
115
|
|
|
116
|
-
## Font-family floor
|
|
116
|
+
## Font-family floor, text-bearing primitives must anchor to a token
|
|
117
117
|
|
|
118
|
-
A primitive that renders text must NOT rely on `font: inherit` / `font-family: inherit` alone. Those carry **no default
|
|
118
|
+
A primitive that renders text must NOT rely on `font: inherit` / `font-family: inherit` alone. Those carry **no default**, the primitive inherits whatever the host page sets, so a consumer page with a broken or serif `font-family` (a dead token, a missing `--a-font-family`, a serif host document) makes the primitive's labels render in UA serif while token-anchored siblings (`text-ui`) stay correct. A confusing same-page split, the exact bug behind an embedded-app `<segmented-ui>` serif regression (25 primitives shared the flaw).
|
|
119
119
|
|
|
120
120
|
**Rule:** anchor `font-family` to a token, the way `text-ui` does:
|
|
121
121
|
|
|
@@ -125,7 +125,7 @@ A primitive that renders text must NOT rely on `font: inherit` / `font-family: i
|
|
|
125
125
|
--foo-font-family-default: var(--a-font-family-ui); /* UI-control font */
|
|
126
126
|
}
|
|
127
127
|
:scope {
|
|
128
|
-
font: inherit; /* keep
|
|
128
|
+
font: inherit; /* keep, resets style/variant/leading */
|
|
129
129
|
font-family: var(--foo-font-family, var(--foo-font-family-default));
|
|
130
130
|
}
|
|
131
131
|
}
|
|
@@ -133,24 +133,24 @@ A primitive that renders text must NOT rely on `font: inherit` / `font-family: i
|
|
|
133
133
|
|
|
134
134
|
- `font: inherit` may stay (it still resets `font-style` / `font-variant` / `line-height`); the `font-family` longhand AFTER it is the floor.
|
|
135
135
|
- Floor to `--a-font-family-ui` for chrome/controls (or `--a-body-family` for prose-like text, as `text-ui` does). The `var(--foo-font-family, …)` first arg is the per-component override hook.
|
|
136
|
-
- For composite controls (`select` / `combobox` / `tags-input` / `table`), floor the **host `:scope
|
|
137
|
-
- **Exception:** a contextual editor (`inline-edit`) or an optional centered label (`spinner`) SHOULD inherit to match surrounding content
|
|
136
|
+
- For composite controls (`select` / `combobox` / `tags-input` / `table`), floor the **host `:scope`**, internal fields/options/cells inherit it.
|
|
137
|
+
- **Exception:** a contextual editor (`inline-edit`) or an optional centered label (`spinner`) SHOULD inherit to match surrounding content, do NOT floor those (they're allowlisted in the audit).
|
|
138
138
|
|
|
139
139
|
**Enforced by** `npm run audit:font-family-floor:strict` (`scripts/dev/audit-font-family-floor.mjs`): flags any component CSS with `font: inherit` / `font-family: inherit` and no `font-family: var(…)` floor, plus dead `var(--a-font)` usage.
|
|
140
140
|
|
|
141
|
-
## Token consumption
|
|
141
|
+
## Token consumption, L3 over L2
|
|
142
142
|
|
|
143
143
|
The token stack has four layers:
|
|
144
144
|
|
|
145
|
-
- **L1 (primitives):** raw scale values
|
|
146
|
-
- **L2 (family semantics):** role tokens per family
|
|
147
|
-
- **L3 (state × role matrix):** `--a-<family>-{bg,fg,border}-{rest,hover,active,selected,disabled,invalid}
|
|
145
|
+
- **L1 (primitives):** raw scale values, `--a-blue-500`, `--a-gray-100`.
|
|
146
|
+
- **L2 (family semantics):** role tokens per family, `--a-primary`, `--a-danger`, `--a-success`, `--a-info`, `--a-warning`.
|
|
147
|
+
- **L3 (state × role matrix):** `--a-<family>-{bg,fg,border}-{rest,hover,active,selected,disabled,invalid}`, every family has a full matrix.
|
|
148
148
|
- **L4 (component tokens):** `--component-*`, defined in `:where(:scope)`.
|
|
149
149
|
|
|
150
150
|
**Rule:** L4 aliases L3, not L2.
|
|
151
151
|
|
|
152
152
|
```css
|
|
153
|
-
/* RIGHT
|
|
153
|
+
/* RIGHT, component token aliases from L3 */
|
|
154
154
|
:where(:scope) {
|
|
155
155
|
--button-bg: var(--a-primary-bg);
|
|
156
156
|
--button-bg-hover: var(--a-primary-bg-hover);
|
|
@@ -163,7 +163,7 @@ The token stack has four layers:
|
|
|
163
163
|
--button-bg-hover: var(--a-danger-bg-hover);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
/* WRONG
|
|
166
|
+
/* WRONG, variant body consumes L2 directly */
|
|
167
167
|
:scope[variant="danger"]:not([disabled]):hover {
|
|
168
168
|
--button-fg: var(--a-danger); /* ← L2 */
|
|
169
169
|
--button-border: var(--a-danger); /* ← L2 */
|
|
@@ -174,23 +174,23 @@ The token stack has four layers:
|
|
|
174
174
|
|
|
175
175
|
This was a real bug in `button.css` caught in a final audit pass.
|
|
176
176
|
|
|
177
|
-
## Raw values
|
|
177
|
+
## Raw values, what's allowed
|
|
178
178
|
|
|
179
179
|
- **Colors:** zero raw values in component CSS files.
|
|
180
180
|
- No `#hex`, `rgb()`, `rgba()`, `oklch()`, `hsl()`, named colors (`red`, `white`). Every color goes through a token.
|
|
181
|
-
- Exception: `styles/colors/semantics.css` and `styles/tokens.css
|
|
181
|
+
- Exception: `styles/colors/semantics.css` and `styles/tokens.css`, those ARE the raw values.
|
|
182
182
|
|
|
183
183
|
- **px values:**
|
|
184
184
|
- ≤ 2px: allowed for `stroke-width`, `border-width`, hairline details. Comment not required.
|
|
185
185
|
- ≥ 3px: forbidden in component base styles. Use `var(--a-space-*)`.
|
|
186
186
|
- Exception: component-intrinsic constants (e.g. a port-dot diameter, an icon size that must match a specific SVG coordinate). Each such literal needs a one-line comment justifying why.
|
|
187
187
|
|
|
188
|
-
- **Typography
|
|
189
|
-
- **Leading:** `--a-font-leading-*` is the single canonical scale
|
|
190
|
-
- **Mapping for the literals you will find in older code:** `1.3` and `1.4` both → `--a-font-leading-normal` (1.35); at the 12–14 px sizes where they occur, ±0.05 is ≤ 0.7 px
|
|
188
|
+
- **Typography, leading, tracking, weight (ADR-0052, ratified 2026-08-15, gh#1298):** zero bare literals in `packages/web-components/components/*/*.css`; every value goes through the one scale per property in `styles/type/scale.css`.
|
|
189
|
+
- **Leading:** `--a-font-leading-*` is the single canonical scale, `none: 1` · `tight: 1.05` · `snug: 1.2` · `normal: 1.35` · `relaxed: 1.5` · `loose: 1.6` (`scale.css:67-72`). The legacy flat `--a-leading-*` family (`none/tight/snug/normal` = 1/1.2/**1.3**/**1.5**) is **retired**, removed from `scale.css` with no value-preserving aliases, because `snug` and `normal` named different values in each family and an alias would silently restyle. Two families under shared step names was the root cause of the literal epidemic (55/131 component files hardcoding `line-height`): no token choice was obviously correct, so authors reached for a number.
|
|
190
|
+
- **Mapping for the literals you will find in older code:** `1.3` and `1.4` both → `--a-font-leading-normal` (1.35); at the 12–14 px sizes where they occur, ±0.05 is ≤ 0.7 px, below visual significance, and no new step is minted to ratify drift. Escape valve: genuinely multi-line body copy may map up to `--a-font-leading-relaxed` (1.5), case-by-case. Legacy `--a-leading-snug` (1.3) → `--a-font-leading-normal`; legacy `--a-leading-normal` (1.5) → `--a-font-leading-relaxed`, value-nearest, not name-preserving.
|
|
191
191
|
- **Tracking:** `0.05em` → `--a-font-tracking-wide` (0.04em); everything else was already on-scale (`--a-font-tracking-tight/snug/normal/wide/wider`, `scale.css:75-79`). **Weight:** numeric weights tokenize to `--a-weight-*` (`scale.css:47-52`).
|
|
192
|
-
- **Gate:** `npm run check:typography-tokens` (`scripts/audit/check-typography-tokens.mjs --strict`, in the `npm run check` aggregate) fails any bare numeric `line-height` (except `0`, the icon line-box-collapse idiom), any `em`-literal `letter-spacing`, any numeric `font-weight`, **and (gh#1496) any numeric literal used as a `var()` fallback** on one of those three properties or on a custom property whose own name names the role (`--alert-line-height`, `--foo-weight`)
|
|
193
|
-
- Shipped as a breaking change on the `0.8.40` wave (`--a-leading-*` were published stylesheet symbols; the ADR text names the wave `0.9.0
|
|
192
|
+
- **Gate:** `npm run check:typography-tokens` (`scripts/audit/check-typography-tokens.mjs --strict`, in the `npm run check` aggregate) fails any bare numeric `line-height` (except `0`, the icon line-box-collapse idiom), any `em`-literal `letter-spacing`, any numeric `font-weight`, **and (gh#1496) any numeric literal used as a `var()` fallback** on one of those three properties or on a custom property whose own name names the role (`--alert-line-height`, `--foo-weight`), `line-height: var(--slider-hint-lh, 1.4)` trips it exactly like a bare literal, because the fallback still computes to the raw number whenever the custom property is unset. When you author a fallback, fall back to a token (`var(--x, var(--a-font-leading-normal))`), not a number, the gate now enforces that rather than merely recommending it. Comments are stripped before scanning.
|
|
193
|
+
- Shipped as a breaking change on the `0.8.40` wave (`--a-leading-*` were published stylesheet symbols; the ADR text names the wave `0.9.0`, it shipped as `0.8.40`), with its migration-guide entry in the same cycle. Source: ADR-0052.
|
|
194
194
|
|
|
195
195
|
Example carve-out:
|
|
196
196
|
|
|
@@ -230,9 +230,9 @@ Example carve-out:
|
|
|
230
230
|
}
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
-
A cursory check might flag `--col-*` as "wrong" because the file is named `layout.css`. It's not wrong
|
|
233
|
+
A cursory check might flag `--col-*` as "wrong" because the file is named `layout.css`. It's not wrong, the **scope tag** determines the stem, not the filename.
|
|
234
234
|
|
|
235
|
-
**Cross-component token-fallback aliasing.** A new component's tokens can alias a sibling component's existing token ladder as their `var()` fallback instead of minting an independent scale
|
|
235
|
+
**Cross-component token-fallback aliasing.** A new component's tokens can alias a sibling component's existing token ladder as their `var()` fallback instead of minting an independent scale, when the two components share a visual role closely enough that re-theming one should re-theme the other in the same stroke. ADR-0083's stat-ui `[slot="change"]` chip mints six stat-scoped tokens (`--stat-change-*`) that fall back to chart.css's existing `--chart-chip-*` rungs:
|
|
236
236
|
|
|
237
237
|
```css
|
|
238
238
|
:where(:scope) {
|
|
@@ -241,11 +241,11 @@ A cursory check might flag `--col-*` as "wrong" because the file is named `layou
|
|
|
241
241
|
}
|
|
242
242
|
```
|
|
243
243
|
|
|
244
|
-
A consumer who re-themes chart chips re-themes the stat delta chip too, with no separate override required. Reach for this only when the aliasing component is a genuine visual sibling of the aliased one (same role, same page context)
|
|
244
|
+
A consumer who re-themes chart chips re-themes the stat delta chip too, with no separate override required. Reach for this only when the aliasing component is a genuine visual sibling of the aliased one (same role, same page context), otherwise it's spooky action at a distance when the aliased component's tokens change for an unrelated reason.
|
|
245
245
|
|
|
246
|
-
## Concentric-corner radius
|
|
246
|
+
## Concentric-corner radius, deriving an item's radius from its container
|
|
247
247
|
|
|
248
|
-
When a rounded container pads a rounded-corner item flush against its own edge
|
|
248
|
+
When a rounded container pads a rounded-corner item flush against its own edge, a popover listbox around `[role="option"]` rows, a menu popover around `menu-item-ui`, a flat item-radius token only reads as concentric with the container's own corner at the one padding value it happened to be tuned against. Change the density scale or the radius scale independently (a consumer re-theme, a `--a-density` step) and the two arcs drift apart.
|
|
249
249
|
|
|
250
250
|
**The formula.** Solve for the ITEM's radius; keep the container's padding fixed:
|
|
251
251
|
|
|
@@ -253,37 +253,37 @@ When a rounded container pads a rounded-corner item flush against its own edge
|
|
|
253
253
|
Ir = max(0, Cr − Cp)
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
-
- `Cr
|
|
257
|
-
- `Cp
|
|
258
|
-
- `Ir
|
|
256
|
+
- `Cr`, the container's own `border-radius` (an existing fixed radius-scale token, untouched).
|
|
257
|
+
- `Cp`, the container's own padding/inset (an existing fixed spacing-scale token, untouched, this pattern never derives padding).
|
|
258
|
+
- `Ir`, the item's `border-radius`, the only new derived value.
|
|
259
259
|
|
|
260
|
-
**Why solve for `Ir`, not `Cp`.** The tempting reverse direction
|
|
260
|
+
**Why solve for `Ir`, not `Cp`.** The tempting reverse direction, hold the item's radius fixed and derive the container's padding from it, needs a second term, `min(Ir, Ih/2)` (`Ih` = item height), to keep the derived padding from going negative once the item is short enough that its own radius would pill-clamp. That's the browser's own `border-radius` clamp (CSS Backgrounds §5.5, a radius past half an element's shorter side reduces to a pill) reimplemented by hand inside the padding formula. Solving for `Ir` instead gets that clamp for free: `max(0px, calc(Cr - Cp))` alone is correct at every item height, because the browser applies its pill-clamp to the declared `Ir` automatically, no `min()` term needed anywhere in the CSS.
|
|
261
261
|
|
|
262
|
-
**Companion rule
|
|
262
|
+
**Companion rule: the `min-height` floor is load-bearing, not optional.** The browser's pill-clamp cuts both ways: if the item is shorter than `2 × Ir`, the *effective* rendered radius clamps down even though the *declared* `Ir` is correct, the item silently stops reading as concentric, with no error and no visual-eval regression to catch it (a live-browser-only defect class, the same shape as this file's `display:contents` entry below). Pair every derived radius with a matching height floor:
|
|
263
263
|
|
|
264
264
|
```css
|
|
265
265
|
min-height: calc(2 * <item-radius-var>);
|
|
266
266
|
```
|
|
267
267
|
|
|
268
|
-
Ruled (Kim, 2026-08-24): enforce this floor rather than let it silently degrade
|
|
268
|
+
Ruled (Kim, 2026-08-24): enforce this floor rather than let it silently degrade, items get taller at large radius/density scales as the accepted tradeoff for exact concentricity, not a bug to route around.
|
|
269
269
|
|
|
270
|
-
**Worked example** (`select-ui`'s listbox
|
|
270
|
+
**Worked example** (`select-ui`'s listbox, the reported surface, gh#1956):
|
|
271
271
|
|
|
272
272
|
```css
|
|
273
|
-
/* Container
|
|
273
|
+
/* Container, Cr and Cp declared as LOCAL custom properties on the
|
|
274
274
|
popover's own top-layer rule. A top-layer popover usually can't inherit
|
|
275
275
|
the host element's @scope'd component tokens (a different, often
|
|
276
|
-
detached DOM subtree once popover-open)
|
|
276
|
+
detached DOM subtree once popover-open), declaring them locally here
|
|
277
277
|
sidesteps that; a local declaration DOES inherit down to a genuine DOM
|
|
278
278
|
child. */
|
|
279
279
|
select-ui [slot="listbox"] {
|
|
280
|
-
--select-listbox-padding: var(--a-space-1); /* Cp
|
|
281
|
-
--select-listbox-radius: var(--a-radius); /* Cr
|
|
280
|
+
--select-listbox-padding: var(--a-space-1); /* Cp, fixed */
|
|
281
|
+
--select-listbox-radius: var(--a-radius); /* Cr, fixed */
|
|
282
282
|
padding: var(--select-listbox-padding);
|
|
283
283
|
border-radius: var(--select-listbox-radius);
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
/* Item
|
|
286
|
+
/* Item, Ir derived; Cr/Cp inherited from the listbox above because
|
|
287
287
|
[role="option"] is a genuine DOM child of it (an appended option row). */
|
|
288
288
|
select-ui [slot="listbox"] [role="option"] {
|
|
289
289
|
--select-option-radius:
|
|
@@ -295,19 +295,19 @@ select-ui [slot="listbox"] [role="option"] {
|
|
|
295
295
|
|
|
296
296
|
(`packages/web-components/components/select/select.css:330-343` and `:378-386`.)
|
|
297
297
|
|
|
298
|
-
Express `Ir` as a live `calc()`/`max()` referencing the radius/space custom properties
|
|
298
|
+
Express `Ir` as a live `calc()`/`max()` referencing the radius/space custom properties, never a baked pixel value, so it holds across every `--a-radius-k` / `--a-density` scale change.
|
|
299
299
|
|
|
300
|
-
**Shipped in** (gh#1956, PR #1959
|
|
300
|
+
**Shipped in** (gh#1956, PR #1959, the reference implementation for this pattern):
|
|
301
301
|
|
|
302
|
-
- `select.css:330-343,378-386
|
|
303
|
-
- `combobox.css:209-221,254-264
|
|
304
|
-
- `tags-input.css:166-184,200-209
|
|
305
|
-
- `menu.css:51-65,90-92
|
|
306
|
-
- `context-menu.css:26-36,56-58
|
|
307
|
-
- `nav-group.css:328-340,358-365
|
|
308
|
-
- `calendar-picker.css:87` / `date-range-picker.css:267
|
|
302
|
+
- `select.css:330-343,378-386`, `[slot="listbox"]` vs. `[role="option"]`.
|
|
303
|
+
- `combobox.css:209-221,254-264`, `[data-listbox]`.
|
|
304
|
+
- `tags-input.css:166-184,200-209`, `[data-suggestions]`.
|
|
305
|
+
- `menu.css:51-65,90-92`, `[data-menu-popover]` vs. `menu-item-ui`.
|
|
306
|
+
- `context-menu.css:26-36,56-58`, `[data-context-menu-surface]`, which shares `menu-item-ui` rows with `menu.css`.
|
|
307
|
+
- `nav-group.css:328-340,358-365`, `[slot="popover"]` vs. `[role="option"]`. Previously gave option rows the container's own flat radius, a design inconsistency next to every sibling popover's distinct smaller item radius. Deriving `Ir` resolves it with no special-casing: under this direction (unlike the reverse "solve for `Cp`" direction, which floors padding to a hard 0px here and was reverted) the row's radius just comes out smaller than the container's automatically.
|
|
308
|
+
- `calendar-picker.css:87` / `date-range-picker.css:267`, `calendar-grid-ui`'s day cells (gh#1966), a variant shape: the item primitive is a *shared substrate* consumed by two different popover containers with different `Cr`/`Cp`, not a single component owning both container and item, so `Ir` is derived once per consumer (not once in the shared `calendar-grid.css`) via a `--calendar-grid-day-radius` override cascaded down. `date-range-picker`'s popover padding is asymmetric (`--date-range-picker-px` ≠ `-py`), a single circular radius can't be exactly concentric on both axes, so it derives from `min(px, py)`, the tighter constraint; harmless on the corner (bottom-left, with a preset rail present) that isn't actually flush. Also the first shipped case to pair the derived radius with the `min-height: calc(2 * Ir)` companion floor (`calendar-grid.css:199`), per the ruling above.
|
|
309
309
|
|
|
310
|
-
Audited, not applicable: `command-ui`, `drilldown-ui
|
|
310
|
+
Audited, not applicable: `command-ui`, `drilldown-ui`, item list sits inside a padded region behind a header, not flush against the container's own rounded corner, or the host carries no radius at all.
|
|
311
311
|
|
|
312
312
|
## Slot styling without `::slotted()`
|
|
313
313
|
|
|
@@ -323,7 +323,7 @@ AdiaUI is light-DOM. Slotted children are just children. Style them with attribu
|
|
|
323
323
|
font-weight: var(--a-weight-semibold);
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
/* WRONG
|
|
326
|
+
/* WRONG, ::slotted() is for shadow DOM */
|
|
327
327
|
::slotted([slot="icon"]) { ... }
|
|
328
328
|
```
|
|
329
329
|
|
|
@@ -360,24 +360,24 @@ When a component can contain itself (cards inside cards), step the background up
|
|
|
360
360
|
|
|
361
361
|
The `:scope card-ui` specificity `(0,1,1)` beats the inner scope's `:where(:scope)` initializer `(0,0,0)`, so the nested card picks up the bumped canvas. No JavaScript required.
|
|
362
362
|
|
|
363
|
-
## `:has()
|
|
363
|
+
## `:has()`, constrain to direct children when gating on slots
|
|
364
364
|
|
|
365
|
-
When a component uses `:has([slot="X"])` to toggle a layout (e.g. activate a grid when a slotted child is present), the selector matches any descendant. That collides with composite children like `<avatar-ui>` which owns an internal `<icon-ui slot="icon"
|
|
365
|
+
When a component uses `:has([slot="X"])` to toggle a layout (e.g. activate a grid when a slotted child is present), the selector matches any descendant. That collides with composite children like `<avatar-ui>` which owns an internal `<icon-ui slot="icon">`, dropping an avatar into a header you intended to render without an icon column will falsely activate it.
|
|
366
366
|
|
|
367
367
|
**Rule:** gate layout on `:has(> [slot="X"])`, not `:has([slot="X"])`.
|
|
368
368
|
|
|
369
369
|
```css
|
|
370
|
-
/* WRONG
|
|
370
|
+
/* WRONG, matches nested <icon-ui slot="icon"> inside an <avatar-ui> */
|
|
371
371
|
> header:has([slot="icon"]) { grid-template-columns: max-content 1fr; }
|
|
372
372
|
|
|
373
|
-
/* RIGHT
|
|
373
|
+
/* RIGHT, only activates for a direct-child [slot="icon"] */
|
|
374
374
|
> header:has(> [slot="icon"]) { grid-template-columns: max-content 1fr; }
|
|
375
375
|
```
|
|
376
376
|
|
|
377
377
|
This applies everywhere layout flips on slot presence:
|
|
378
378
|
|
|
379
379
|
```css
|
|
380
|
-
/* card-ui / drawer-ui header grid
|
|
380
|
+
/* card-ui / drawer-ui header grid, all :has() clauses are direct-child */
|
|
381
381
|
> header:has(> [slot="icon"]):has(> :is([slot="action"], [slot="close"])) {
|
|
382
382
|
grid-template-columns: max-content 1fr max-content;
|
|
383
383
|
}
|
|
@@ -392,23 +392,23 @@ This applies everywhere layout flips on slot presence:
|
|
|
392
392
|
|
|
393
393
|
## Conditional-render parts defeat `:scope >` (the `display:contents` wrapper)
|
|
394
394
|
|
|
395
|
-
The template engine wraps every **conditional render branch
|
|
395
|
+
The template engine wraps every **conditional render branch**, a `${cond ? … : null}` (or `?` / `.map()`) expression, in a `<span style="display:contents">`. The span generates no box (invisible in layout) but is a real DOM node, so a conditionally-rendered part is a **grandchild** of `:scope`, not a direct child. A `:scope > [data-part="X"]` rule on that part silently matches nothing, no error, passes `components --verify`, and renders un-styled only in a live browser (happy-dom won't catch it).
|
|
396
396
|
|
|
397
397
|
**Rule:** use a **descendant** combinator for any conditionally-rendered part; keep `:scope >` only for parts that render unconditionally.
|
|
398
398
|
|
|
399
399
|
```css
|
|
400
|
-
/* WRONG
|
|
400
|
+
/* WRONG, empty-state lives behind a `${isEmpty ? … : null}` branch,
|
|
401
401
|
so it's wrapped in <span style="display:contents"> and never matched */
|
|
402
402
|
:scope > [data-part="empty"] { display: grid; place-items: center; }
|
|
403
403
|
|
|
404
|
-
/* RIGHT
|
|
404
|
+
/* RIGHT, descendant combinator survives the display:contents wrapper */
|
|
405
405
|
:scope [data-part="empty"] { display: grid; place-items: center; }
|
|
406
406
|
|
|
407
407
|
/* static parts (always rendered) stay direct children */
|
|
408
408
|
:scope > [data-part="header"] { … }
|
|
409
409
|
```
|
|
410
410
|
|
|
411
|
-
Recurring class (integrations-page empty-state, onboarding-checklist complete CTA
|
|
411
|
+
Recurring class (integrations-page empty-state, onboarding-checklist complete CTA, bug-51 / bug-53). Full failure entry + recognition heuristic: [anti-patterns.md](anti-patterns.md) AP-S6.
|
|
412
412
|
|
|
413
413
|
## Sticky header/footer inside a flex-column scroll container
|
|
414
414
|
|
|
@@ -447,12 +447,12 @@ When a card-like container (drawer-ui, pane-ui, full-height cards) needs a heade
|
|
|
447
447
|
**Why this over a dedicated scroll wrapper:**
|
|
448
448
|
|
|
449
449
|
- No extra DOM element required.
|
|
450
|
-
- Multiple `[slot="body"]` siblings stack naturally
|
|
450
|
+
- Multiple `[slot="body"]` siblings stack naturally, the author can put dividers between sections, include sub-headers, etc.
|
|
451
451
|
- Sticky works inside `display: flex` flex-columns in all modern browsers.
|
|
452
452
|
|
|
453
453
|
**Gotcha:** the sticky background must be opaque. If the header is transparent, content scrolls visibly underneath. Match the sticky element's `background` to the panel's `--*-bg` token.
|
|
454
454
|
|
|
455
|
-
## Cascade layers
|
|
455
|
+
## Cascade layers, precedence is declared once, never fought per-rule
|
|
456
456
|
|
|
457
457
|
`styles/index.css` declares the single ordered list, before any layered rule:
|
|
458
458
|
|
|
@@ -460,12 +460,12 @@ When a card-like container (drawer-ui, pane-ui, full-height cards) needs a heade
|
|
|
460
460
|
@layer reset, tokens, elements, components, utilities, context, overrides;
|
|
461
461
|
```
|
|
462
462
|
|
|
463
|
-
The precedence law: a later layer beats an earlier layer **regardless of selector specificity**; within a layer, normal specificity applies. Component `@scope` rules live inside `@layer components
|
|
463
|
+
The precedence law: a later layer beats an earlier layer **regardless of selector specificity**; within a layer, normal specificity applies. Component `@scope` rules live inside `@layer components`, `@scope` sets proximity *inside* the layer, `@layer` sets inter-group order; they compose, they are not substitutes.
|
|
464
464
|
|
|
465
465
|
Consequences for component authors:
|
|
466
466
|
|
|
467
|
-
- **Consumer overrides always win
|
|
468
|
-
- **Utilities beat component defaults by layer order.** The global attribute API (`api/*`) sits in `utilities`, so a component cannot out-specific it
|
|
467
|
+
- **Consumer overrides always win**, unlayered consumer CSS, or `@layer overrides`, beats everything by layer order. Never add `:where()` wraps, matched-specificity selectors, or `!important` to "let the consumer win"; the layer order already guarantees it.
|
|
468
|
+
- **Utilities beat component defaults by layer order.** The global attribute API (`api/*`) sits in `utilities`, so a component cannot out-specific it, a component repurposing a global attribute (e.g. `color=` on a filled control) must opt out explicitly (see the `[color]`/`[weight]` trap below).
|
|
469
469
|
- **`@layer` governs rule precedence only.** The `var()` token chains (token indirection), genuine element-default `:where()`s, and allowlisted a11y `!important`s serve other mechanisms and are not retired by layers.
|
|
470
470
|
- `npm run check:cascade-layers` gates the model: canonical order declared once, `!important` ≤ allowlist, no precedence-`:where()` in layered files.
|
|
471
471
|
|
|
@@ -475,19 +475,19 @@ Source: ADR-0038.
|
|
|
475
475
|
|
|
476
476
|
One line each; every entry is a shipped bug.
|
|
477
477
|
|
|
478
|
-
- **`background: <color>` shorthand silently resets `background-clip`/`origin`/`position`/`size`.** For a state that changes only the color, use the `background-color` longhand
|
|
479
|
-
- **Modern `translate` / `scale` / `rotate` are independent properties, NOT `transform` aliases.** Writing `style.translate` and reading `getComputedStyle().transform` (or vice-versa) silently no-ops
|
|
478
|
+
- **`background: <color>` shorthand silently resets `background-clip`/`origin`/`position`/`size`.** For a state that changes only the color, use the `background-color` longhand, a base `background-clip: content-box` otherwise flips to `border-box` and the fill balloons (swiper-ui dots grew 6px → 16px pills).
|
|
479
|
+
- **Modern `translate` / `scale` / `rotate` are independent properties, NOT `transform` aliases.** Writing `style.translate` and reading `getComputedStyle().transform` (or vice-versa) silently no-ops, they are computed separately (spring-animate wrote translate, read the transform matrix, saw 0, never animated).
|
|
480
480
|
- **An OFFSETTING ancestor transform (`translate(-50%,-50%)`) breaks CSS anchor-positioning for top-layer popovers**; an identity `translateX(0)` does not.
|
|
481
|
-
- **A `@media` override with an equal-specificity selector must come AFTER its base rule in source order
|
|
482
|
-
- **One un-suffixed component token per property (`--card-bg`), read directly (`var(--card-bg)`).** The `-default` token-shadowing layer was reverted
|
|
483
|
-
- **Global `[color]`/`[weight]` presentational utilities override component color by `@layer` order
|
|
484
|
-
- **A grid `auto`/`max-content` track collapses to ~1px around a flex wrapper whose child carries the explicit width
|
|
485
|
-
- **Square/1:1 cells inheriting `--a-radius-md` render as circles
|
|
481
|
+
- **A `@media` override with an equal-specificity selector must come AFTER its base rule in source order**, earlier placement is silently ignored.
|
|
482
|
+
- **One un-suffixed component token per property (`--card-bg`), read directly (`var(--card-bg)`).** The `-default` token-shadowing layer was reverted, do not declare `--card-bg-default` fallback chains, and do not rely on ancestor surfaces overriding component-named tokens (that inheritance no longer works).
|
|
483
|
+
- **Global `[color]`/`[weight]` presentational utilities override component color by `@layer` order**, a filled control repurposing `color=` must opt out; conversely the global `[weight]` attribute does NOT override component-scoped font-weight (use a variant).
|
|
484
|
+
- **A grid `auto`/`max-content` track collapses to ~1px around a flex wrapper whose child carries the explicit width**, the child's width doesn't propagate through the wrapper's intrinsic size; set the width on the wrapper (CSS, or JS-mirrored via ResizeObserver).
|
|
485
|
+
- **Square/1:1 cells inheriting `--a-radius-md` render as circles**, use `--a-radius-sm` for small square cells.
|
|
486
486
|
|
|
487
487
|
## Anti-patterns specific to CSS
|
|
488
488
|
|
|
489
|
-
- **BEM class syntax
|
|
490
|
-
- **`::part()` / `::slotted()
|
|
491
|
-
- **Global selectors inside `@scope
|
|
492
|
-
- **`!important
|
|
493
|
-
- **Setting tokens at `:root
|
|
489
|
+
- **BEM class syntax**, `.component--variant__element`. Not allowed. Slot attribute selectors replace this pattern.
|
|
490
|
+
- **`::part()` / `::slotted()`**, shadow DOM syntax. AdiaUI is light DOM.
|
|
491
|
+
- **Global selectors inside `@scope`**, `body { ... }`, `html { ... }`, `* { ... }`. The scope is the component; don't reach outside.
|
|
492
|
+
- **`!important`**, ever. If you need it, the specificity layering is wrong. Fix the layering.
|
|
493
|
+
- **Setting tokens at `:root`**, tokens scoped to a component belong in `:where(:scope)`. Only cross-component semantic tokens live in `:root` / `styles/colors/semantics.css`.
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
Source of truth: [ADR-0079](../../../../../../docs/ops/adr/adr-0079-for-attribute-id-ref-and-events-only-interaction-contract.md)
|
|
4
4
|
(ratified 2026-08-20, executed by gh#1780). Read the ADR before adding a new
|
|
5
5
|
`for=`-bound pair or touching `table-toolbar-ui`/`chart-legend-ui`/
|
|
6
|
-
`tooltip-ui`/`context-menu-ui
|
|
6
|
+
`tooltip-ui`/`context-menu-ui`: this file is the routing pointer + the
|
|
7
7
|
fix-now checklist, not a restatement of the ruling.
|
|
8
8
|
|
|
9
9
|
## The one-line rule
|
|
10
10
|
|
|
11
|
-
`for=` means id-ref, everywhere in AdiaUI
|
|
11
|
+
`for=` means id-ref, everywhere in AdiaUI, no component may give it a
|
|
12
12
|
different resolution contract (`context-menu-ui`'s prior CSS-selector
|
|
13
13
|
reading was the violation this ADR ends). The interaction contract over any
|
|
14
|
-
`for=` binding is bubbling `CustomEvent`s in **both** directions
|
|
14
|
+
`for=` binding is bubbling `CustomEvent`s in **both** directions, never a
|
|
15
15
|
direct property write or method call across the binding.
|
|
16
16
|
`chart-legend-ui`↔`chart-ui` is the ratified model: the bound element
|
|
17
17
|
dispatches a bubbling event, the target listens on `document` filtered by
|
|
@@ -19,21 +19,21 @@ dispatches a bubbling event, the target listens on `document` filtered by
|
|
|
19
19
|
|
|
20
20
|
## Fix-now checklist for a `for=`-bound primitive
|
|
21
21
|
|
|
22
|
-
- `for=` resolves via `getElementById` (id-ref)
|
|
22
|
+
- `for=` resolves via `getElementById` (id-ref), never
|
|
23
23
|
`querySelectorAll`/a CSS-selector reading. A component needing
|
|
24
24
|
selector-flavored targeting names a DIFFERENT attribute (see
|
|
25
|
-
`context-menu-ui`'s `target-selector` below)
|
|
25
|
+
`context-menu-ui`'s `target-selector` below), it never overloads `for`.
|
|
26
26
|
- Every write the bound pair needs to make on each other is a bubbling
|
|
27
27
|
`CustomEvent`, dispatched at the resolved target (or document-delegated,
|
|
28
|
-
whichever the existing read direction already established)
|
|
28
|
+
whichever the existing read direction already established), never a
|
|
29
29
|
direct property write (`target.prop = …`) or method call
|
|
30
30
|
(`target.method()`) across the binding.
|
|
31
31
|
- `chart-legend-ui`, `chart-ui`, and `tooltip-ui[follows=pointer]` are
|
|
32
|
-
already verified compliant
|
|
32
|
+
already verified compliant, don't touch them to "align" with a new
|
|
33
33
|
pattern; the ADR names them explicitly out of scope for its own sweep.
|
|
34
34
|
- `table-toolbar-ui`→`table-ui`'s inbound command surface is
|
|
35
35
|
`toolbar-search` / `toolbar-filter-set` / `toolbar-filter-clear` /
|
|
36
|
-
`toolbar-columns-set` / `toolbar-paginate
|
|
36
|
+
`toolbar-columns-set` / `toolbar-paginate`, dispatched by the toolbar at
|
|
37
37
|
its resolved `#target`, applied by `table-ui` via the same public-API call
|
|
38
38
|
the direct write used to make. This is additive to `table-ui`'s contract:
|
|
39
39
|
any consumer may dispatch these events, not only `table-toolbar-ui`. The
|
|
@@ -43,8 +43,8 @@ dispatches a bubbling event, the target listens on `document` filtered by
|
|
|
43
43
|
alternative in the ADR).
|
|
44
44
|
- A component whose `for=` was CSS-selector-flavored (`context-menu-ui`'s
|
|
45
45
|
prior shape) renames the selector-flavored attribute to `target-selector`;
|
|
46
|
-
`for` survives only as a **deprecated back-compat alias
|
|
47
|
-
as a selector (never dual-purposed into id-ref sniffing
|
|
46
|
+
`for` survives only as a **deprecated back-compat alias**, still resolved
|
|
47
|
+
as a selector (never dual-purposed into id-ref sniffing, rejected in the
|
|
48
48
|
ADR as ambiguous), honored only when `target-selector` is unset, with a
|
|
49
49
|
one-shot `console.warn` pointing at the replacement. Document the alias as
|
|
50
50
|
deprecated in the yaml SoT, the class doc comment, and the examples.
|
|
@@ -52,11 +52,11 @@ dispatches a bubbling event, the target listens on `document` filtered by
|
|
|
52
52
|
## What NOT to do
|
|
53
53
|
|
|
54
54
|
- Don't invent a document-level delegated-listener shape for a pair that
|
|
55
|
-
already resolves a concrete target reference for its read direction
|
|
55
|
+
already resolves a concrete target reference for its read direction, a
|
|
56
56
|
targeted dispatch at the resolved element is simpler and still satisfies
|
|
57
57
|
"events, not direct writes" (rejected alternative in the ADR, re:
|
|
58
58
|
`table-toolbar-ui`).
|
|
59
59
|
- Don't dual-purpose one attribute name for two resolution contracts
|
|
60
|
-
(id-ref sometimes, selector sometimes)
|
|
60
|
+
(id-ref sometimes, selector sometimes): this is the exact same-name/
|
|
61
61
|
different-contract collision ADR-0053's no-shadowing doctrine forbids for
|
|
62
62
|
global attributes, and the defect this ADR exists to end.
|