Skip to content

Commit dd636e9

Browse files
committed
refactor(expander): summary typography via ::part(summary), not tokens
Drop the --expander-summary-font-size/-line-height/-font-weight component tokens: `level` covers the supported variation, the weight never varies, and ::part(summary) is the documented escape hatch for bespoke header restyling. Component tokens stay reserved for values external CSS must re-point (tone, surface, dark mode). The per-level rules move back into the shadow sheet, generated from a single SUMMARY_TYPE_SCALE const — still one place to keep in sync with a-title.css. https://claude.ai/code/session_01A9PoyMtVzmBJshb5gneqVQ
1 parent d3c4639 commit dd636e9

4 files changed

Lines changed: 34 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Versions ending in `-dev.N` are pre-release builds published under the npm `dev`
99
## Unreleased
1010

1111
### Added
12-
- **New `Expander` component** (`<Expander>` / `<a-expander>`). A collapsible disclosure — a header that toggles a content region open/closed. Built from its own shadow DOM — a `<button>` summary + a grid content region (no native `<details>`) — so focus, keyboard activation (<kbd>Enter</kbd>/<kbd>Space</kbd>), and the WAI-ARIA disclosure pattern (`aria-expanded`, which also drives the CSS state) come from the platform button; the expand/collapse animation mirrors the docs-site disclosure (grid `0fr↔1fr`, clipped while animating, 200ms). `title` takes a string or a node (compose a `<Title>` for real heading semantics); `level` (1–6) applies the `<Title>` heading type scale to a string title. `priority` sets the surface — `secondary` (default) a subtle fill (`--bg-2` + `--border-5`), `primary` a more pronounced card (`--bg-4` + `--border-4`), `tertiary` transparent; `tone` (neutral default + brand/info/success/warning/critical, **or any literal CSS color** for a one-off custom tone — hue kept, lightness/chroma pinned via `oklch(from …)`, like `Button` / `Tag`) tints the text and filled surface from the theme palette (light/dark track automatically). Strictly controlled (`open` + `onToggle` — the component only follows the prop; a click *requests* a change through `onToggle(open)`, so a toggle can be rejected by not updating) or uncontrolled (`defaultOpen` — on the element, the presence-based `defaultopen` attribute, so the DOM carries no stale state attribute). Content is composed from two light-DOM children — `<a-expander-summary>` (the header, slotted into the shadow `<button>`) and `<a-expander-details>` (the collapsible body) — mirroring `<a-tag>`'s `label`/`value` sub-elements; collapsed content is `inert` so it leaves the tab order and a11y tree. `marker="outside"` (with `tertiary`) hangs the chevron in the left gutter so the title sits flush with surrounding content (the docs-header layout). Exposes `--expander-text`, the per-priority surface tokens (`--expander-bg`/`-secondary`/`-primary`, `--expander-border`/`-secondary`/`-primary`), and the summary typography (`--expander-summary-font-size`/`-line-height`/`-font-weight`, re-pointed per `level`); shadow internals are exposed as `::part(summary)` / `::part(content)` for styling beyond the variables (e.g. header `:hover`/`:focus-visible`, the chevron). Registered via the `@antadesign/anta/elements` barrel.
12+
- **New `Expander` component** (`<Expander>` / `<a-expander>`). A collapsible disclosure — a header that toggles a content region open/closed. Built from its own shadow DOM — a `<button>` summary + a grid content region (no native `<details>`) — so focus, keyboard activation (<kbd>Enter</kbd>/<kbd>Space</kbd>), and the WAI-ARIA disclosure pattern (`aria-expanded`, which also drives the CSS state) come from the platform button; the expand/collapse animation mirrors the docs-site disclosure (grid `0fr↔1fr`, clipped while animating, 200ms). `title` takes a string or a node (compose a `<Title>` for real heading semantics); `level` (1–6) applies the `<Title>` heading type scale to a string title. `priority` sets the surface — `secondary` (default) a subtle fill (`--bg-2` + `--border-5`), `primary` a more pronounced card (`--bg-4` + `--border-4`), `tertiary` transparent; `tone` (neutral default + brand/info/success/warning/critical, **or any literal CSS color** for a one-off custom tone — hue kept, lightness/chroma pinned via `oklch(from …)`, like `Button` / `Tag`) tints the text and filled surface from the theme palette (light/dark track automatically). Strictly controlled (`open` + `onToggle` — the component only follows the prop; a click *requests* a change through `onToggle(open)`, so a toggle can be rejected by not updating) or uncontrolled (`defaultOpen` — on the element, the presence-based `defaultopen` attribute, so the DOM carries no stale state attribute). Content is composed from two light-DOM children — `<a-expander-summary>` (the header, slotted into the shadow `<button>`) and `<a-expander-details>` (the collapsible body) — mirroring `<a-tag>`'s `label`/`value` sub-elements; collapsed content is `inert` so it leaves the tab order and a11y tree. `marker="outside"` (with `tertiary`) hangs the chevron in the left gutter so the title sits flush with surrounding content (the docs-header layout). Exposes `--expander-text` and the per-priority surface tokens (`--expander-bg`/`-secondary`/`-primary`, `--expander-border`/`-secondary`/`-primary`); shadow internals are exposed as `::part(summary)` / `::part(content)` for styling beyond the variables (header `:hover`/`:focus-visible`, the chevron, bespoke summary typography beyond `level`). Registered via the `@antadesign/anta/elements` barrel.
1313

1414
## 0.2.0 — June 9, 2026
1515

site/src/pages/components/expander.mdx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,6 @@ filled surfaces from the theme palette, so light / dark track for free.
235235
| `--expander-border` | Host border color. Follows the same per-priority surface; transparent on `tertiary`. |
236236
| `--expander-bg-secondary`<br />`--expander-border-secondary` | Secondary (default) surface — a subtle fill (`--bg-2`) + faint edge (`--border-5`), re-pointed per tone. |
237237
| `--expander-bg-primary`<br />`--expander-border-primary` | Primary surface — a more pronounced card (`--bg-4` + `--border-4`), re-pointed per tone. |
238-
| `--expander-summary-font-size`<br />`--expander-summary-line-height`<br />`--expander-summary-font-weight` | Summary (header) typography. Mirrors the `<Title>` type scale; `level` re-points size + line-height. |
239238
| `--expander-tone-source` | The color a custom (non-named) `tone` derives from. Set inline by the wrapper; set it yourself to drive the derived palette from your own variable. |
240239

241240
</Disclosure>
@@ -250,7 +249,7 @@ as [`::part`](https://developer.mozilla.org/en-US/docs/Web/CSS/::part):
250249

251250
| Part | What it is |
252251
|---|---|
253-
| `summary` | The header `<button>`. Reach states CSS variables can't — `::part(summary):hover`, `:focus-visible`, or the chevron via `::part(summary)::before`. |
252+
| `summary` | The header `<button>`. Reach what CSS variables can't — states (`::part(summary):hover`, `:focus-visible`), the chevron via `::part(summary)::before`, or bespoke header typography beyond what `level` offers. |
254253
| `content` | The collapsible body region. |
255254

256255
```css

src/elements/a-expander.css

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@
33
--expander-text: var(--text-2);
44
--expander-text-hover: var(--text-1);
55

6-
--expander-summary-font-size: 15px;
7-
--expander-summary-line-height: 20px;
8-
--expander-summary-font-weight: 584.62;
9-
106
--expander-bg-secondary: var(--bg-2);
117
--expander-border-secondary: var(--border-5);
128
--expander-bg-primary: var(--bg-4);
@@ -26,13 +22,6 @@
2622
background: var(--expander-bg);
2723
}
2824

29-
a-expander[level="1"] { --expander-summary-font-size: 28px; --expander-summary-line-height: 32px; }
30-
a-expander[level="2"] { --expander-summary-font-size: 24px; --expander-summary-line-height: 28px; }
31-
a-expander[level="3"] { --expander-summary-font-size: 20px; --expander-summary-line-height: 24px; }
32-
a-expander[level="4"] { --expander-summary-font-size: 17px; --expander-summary-line-height: 20px; }
33-
a-expander[level="5"] { --expander-summary-font-size: 15px; --expander-summary-line-height: 20px; }
34-
a-expander[level="6"] { --expander-summary-font-size: 13px; --expander-summary-line-height: 16px; }
35-
3625
a-expander-summary {
3726
display: block;
3827
min-width: 0;

src/elements/a-expander.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,14 @@ import './a-expander.css'
4747
*
4848
* - **Summary**: a real `<button>` (free focus + Enter/Space), reset to
4949
* inherit the host's box/text so it reads as a plain header row. Its
50-
* typography comes from the `--expander-summary-font-size` /
51-
* `-line-height` / `-font-weight` host tokens, re-pointed per `[level]`
52-
* in `a-expander.css` — that file mirrors the `a-title.css` type scale
53-
* (keep them in sync; `a-title` is CSS-only so there is no shared
54-
* constant to import).
50+
* typography (default + per-`[level]` rules) is generated from
51+
* `SUMMARY_TYPE_SCALE` below — the one place to keep in sync with the
52+
* `a-title.css` type scale (`a-title` is CSS-only, so there is no
53+
* shared constant to import). Deliberately NOT exposed as
54+
* `--expander-summary-*` tokens: `level` covers the supported
55+
* variation, the weight never varies, and `::part(summary)` is the
56+
* escape hatch for bespoke restyling — component tokens are reserved
57+
* for values external CSS must re-point (tone, surface, dark mode).
5558
* - **Chevron**: the button's `::before` — a mask painting with
5659
* `currentColor` (the inherited, possibly toned `--expander-text`);
5760
* dimmed at rest, full on hover/open, rotated 90° when open. Explicit
@@ -109,6 +112,25 @@ import './a-expander.css'
109112

110113
const ANIM_MS = 200
111114

115+
// Mirrors the h1–h6 scale in a-title.css (font-size / line-height, px).
116+
// Level 5 is the default summary typography; the weight matches <Title>.
117+
const SUMMARY_TYPE_SCALE: Record<string, [number, number]> = {
118+
'1': [28, 32],
119+
'2': [24, 28],
120+
'3': [20, 24],
121+
'4': [17, 20],
122+
'5': [15, 20],
123+
'6': [13, 16],
124+
}
125+
const SUMMARY_FONT_WEIGHT = 584.62
126+
127+
const SUMMARY_LEVEL_RULES = Object.entries(SUMMARY_TYPE_SCALE)
128+
.map(
129+
([level, [size, line]]) =>
130+
`:host([level="${level}"]) button { font-size: ${size}px; line-height: ${line}px; }`,
131+
)
132+
.join('\n ')
133+
112134
const CHEVRON =
113135
"url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'%3e%3cpath stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m9 18 6-6-6-6'/%3e%3c/svg%3e\")"
114136

@@ -134,12 +156,14 @@ const SHADOW_STYLE = `
134156
user-select: none;
135157
border-radius: 2px;
136158
outline-offset: 4px;
137-
font-size: var(--expander-summary-font-size);
138-
line-height: var(--expander-summary-line-height);
139-
font-weight: var(--expander-summary-font-weight);
159+
font-size: ${SUMMARY_TYPE_SCALE['5'][0]}px;
160+
line-height: ${SUMMARY_TYPE_SCALE['5'][1]}px;
161+
font-weight: ${SUMMARY_FONT_WEIGHT};
140162
letter-spacing: 0;
141163
}
142164
165+
${SUMMARY_LEVEL_RULES}
166+
143167
button::before {
144168
content: '';
145169
width: 16px;

0 commit comments

Comments
 (0)