@expressivecss/mcp-server 0.4.1 → 0.5.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.
@@ -1,303 +1,403 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedBy": "mcp/expressivecss/scripts/sync-guides.mjs",
4
- "frameworkVersion": "0.15.0",
3
+ "generatedBy": "scripts/gen-expressivecss-skill.mjs",
4
+ "frameworkVersion": "0.16.0",
5
5
  "guides": [
6
6
  {
7
7
  "file": "accordion.md",
8
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Accordion\nStacked disclosures that open one section of content at a time, on native details.\n\nComponent ID: `accordion`\n\n[Component documentation](https://www.expressivecss.com/accordion.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/accordion.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Account, Billing and Security as three peer views of a settings page are tabs. An accordion would hide two of them behind headers when people switch between them.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#accordion`): Joined tiles on native details and summary, exclusive groups through the name attribute, a leading icon, a turning chevron and an animated open where the engine supports it.\n\nWeb adaptation: Native <details> and <summary>; the browser owns the toggle, its keyboard support and its expanded state.\n\nKnown boundary: Tile corners, the chevron turn, exclusive groups and the expanded state Chromium reports are checked in a browser; the open animation, enlarged text and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#accordion).\n\n#### Contract\n\nA `.accordion` holds `<details>` items. An item's `<summary>` is its header, and everything after the summary is its panel. The browser owns the behavior: the summary is a button that reports its expanded state, Enter and Space toggle it, and `open` sets the initial state. Give the items one shared `name` to make them exclusive, so opening one closes the others. CSS only; there is no JavaScript.\n\nThe items join as tiles. Outer corners take the full shape and inner corners the grouped shape, and an open item rounds every corner. Opening animates in engines that can transition to `auto` height, and opens at once elsewhere or under reduced motion. Keep links and buttons out of the summary, which is already a button; put actions in the panel.\n\n#### Syntax\n\n```html\n<div class=\"accordion\">\n <details name=\"faq\" open>\n <summary>Can I change my plan later?</summary>\n <p>Yes. The new plan starts with your next billing period.</p>\n </details>\n <details name=\"faq\">\n <summary>How do I export my data?</summary>\n <p>Open Settings, then Export. The download includes every project you own.</p>\n </details>\n</div>\n```\n\n#### Also documented\n\nThe Accordion section of the [API reference](https://www.expressivecss.com/llm.md) also covers Leading icon, Tokens.\n\n#### Rules\n\n- `accordion-items-are-details`: Write every accordion item as <details> with a <summary>. The element is the disclosure: a div with a click handler has no expanded state and no keyboard support.\n- `accordion-summary-holds-no-controls`: A <summary> is already the toggle button. A link or control inside it is a control nested in a control: pressing it also opens or closes the item, and screen readers flatten it into the summary name. Put actions in the panel.\n- `accordion-summary-state-is-native`: The browser exposes <summary> as a button and reports its expanded state from the open attribute. An authored role=\"button\" repeats it, and an authored aria-expanded goes stale the first time the item toggles.\n"
8
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Accordion\nStacked disclosures that open one section of content at a time, on native details.\n\nComponent ID: `accordion`\n\n[Component documentation](https://www.expressivecss.com/accordion.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/accordion.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Account, Billing and Security as three peer views of a settings page are tabs. An accordion would hide two of them behind headers when people switch between them.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#accordion`): Joined tiles on native details and summary, exclusive groups through the name attribute, a leading icon, a turning chevron and an animated open where the engine supports it.\n\nWeb adaptation: Native <details> and <summary>; the browser owns the toggle, its keyboard support and its expanded state.\n\nKnown boundary: Tile corners, the chevron turn, exclusive groups and the expanded state Chromium reports are checked in a browser; the open animation, enlarged text and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#accordion).\n\n#### Contract\n\nA `.accordion` holds `<details>` items. An item's `<summary>` is its header, and everything after the summary is its panel. The browser owns the behavior: the summary is a button that reports its expanded state, Enter and Space toggle it, and `open` sets the initial state. Give the items one shared `name` to make them exclusive, so opening one closes the others. CSS only; there is no JavaScript.\n\nThe items join as tiles. Outer corners take the full shape and inner corners the grouped shape, and an open item rounds every corner. Opening animates in engines that can transition to `auto` height, and opens at once elsewhere or under reduced motion. Keep links and buttons out of the summary, which is already a button; put actions in the panel.\n\n#### Syntax\n\n```html\n<div class=\"accordion\">\n <details name=\"faq\" open>\n <summary>Can I change my plan later?</summary>\n <p>Yes. The new plan starts with your next billing period.</p>\n </details>\n <details name=\"faq\">\n <summary>How do I export my data?</summary>\n <p>Open Settings, then Export. The download includes every project you own.</p>\n </details>\n</div>\n```\n\n#### Also documented\n\nThe Accordion section of the [API reference](https://www.expressivecss.com/llm.md) also covers Leading icon, Tokens.\n\n#### Rules\n\n- `accordion-items-are-details`: Write every accordion item as <details> with a <summary>. The element is the disclosure: a div with a click handler has no expanded state and no keyboard support.\n- `accordion-summary-holds-no-controls`: A <summary> is already the toggle button. A link or control inside it is a control nested in a control: pressing it also opens or closes the item, and screen readers flatten it into the summary name. Put actions in the panel.\n- `accordion-summary-state-is-native`: The browser exposes <summary> as a button and reports its expanded state from the open attribute. An authored role=\"button\" repeats it, and an authored aria-expanded goes stale the first time the item toggles.\n"
9
+ },
10
+ {
11
+ "file": "alert.md",
12
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Alert\nA message that stays in the page next to the content it is about, with optional actions.\n\nComponent ID: `alert`\n\n[Component documentation](https://www.expressivecss.com/alert.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/alert.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: \"Saved\" after pressing Save is a snackbar. \"You're offline; edits sync later\" stays true until the connection returns, so it is an alert above the editor.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory. Replaces the removed banner.\n\nUpstream: web-extension-inventory-reviewed (2026-10-15); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-15, `llm.md#alert`): A tonal container with an optional leading icon, a heading and text, an end-aligned actions row, a close button and a critical error-container tone.\n\nWeb adaptation: A plain div around authored text and buttons; a changing alert puts role=\"status\" or role=\"alert\" on its text, never on the controls.\n\nKnown boundary: The layout, both tones, enlarged-text wrapping and the authored role are checked in a browser; live announcements and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#alert).\n\n#### Contract\n\nAn `.alert` holds an optional leading icon, then the message: a heading, a `<p>`, or both. It stays until the problem goes away or the user dismisses it. Use a snackbar for brief feedback after an action, and a dialog when the user must decide before going on. CSS only; there is no JavaScript.\n\n#### Syntax\n\n```html\n<div class=\"alert\">\n <span class=\"material-symbols\" aria-hidden=\"true\">info</span>\n <p>Your trial ends in 3 days. <a href=\"/plans\">See plans</a></p>\n</div>\n```\n\n#### Also documented\n\nThe Alert section of the [API reference](https://www.expressivecss.com/llm.md) also covers Critical, Actions and close, Announcing changes, Error summary, Tokens.\n\n#### Rules\n\n- `alert-is-not-the-banner-landmark`: role=\"banner\" is the page header landmark, not a message. Remove it: a static alert needs no role, and a changing one puts role=\"status\" or role=\"alert\" on its text.\n- `alert-live-region-holds-no-controls`: Put role=\"status\" or role=\"alert\" on the alert's text, not on an alert that holds buttons or links. A live region reads its controls' labels as part of the message.\n- `error-summary-form-is-novalidate`: Add novalidate to a form with an .error-summary. Without it the browser stops an invalid submit before the submit event, so the summary never fills and only the first field's bubble shows.\n- `error-summary-has-a-list`: Give the .error-summary an empty <ul>. The Forms behavior writes one link per invalid field into it.\n"
9
13
  },
10
14
  {
11
15
  "file": "app-bar.md",
12
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### App bar\nMaterial Design 3 top app bars, from the HTML.\n\nComponent ID: `app-bar`\n\n[Component documentation](https://www.expressivecss.com/navbar.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/navbar.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/app-bars/overview)\n\nExample: A Home/Search/Profile row belongs in navigation, even if placed at the top. Keep the app bar for the title and screen actions.\n\n#### Material mapping\n\nRelationship: component. Google replaces baseline medium/large bars with flexible variants. ExpressiveCSS medium and large already mean medium-flexible and large-flexible; keep those classes.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/app-bars/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/app-bars/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/app-bars/guidelines).\n\nSupport (2026-09-17, `llm.md#navbar`): Small, medium flexible, large flexible, and search app bars.\n\nWeb adaptation: Native header/nav composition; AppBar owns scroll collapse and search expansion.\n\nKnown boundary: Native zoom, screen-reader announcements, contrast themes and physical mobile keyboard behavior remain pending manual review.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#app-bar).\n\n#### Contract\n\nThe bar is the markup. A `<header>` whose child is a `<nav>` is a top app bar. Top app bars stay pinned to the top by default, without a helper class. The heading is the headline; wrap it in `<hgroup>` with a `<p>` for a subtitle. Icon-only links and buttons are the leading and trailing actions. A `<menu>` (or `<ul>`) holds text destinations. A `<search class=\"search-bar\">` in the nav is the search app bar. There is no `navbar`, `nav-wrapper`, or `brand-logo` class.\n\nSmall is 64dp at `title-large`. `medium` is the Expressive medium-flexible bar: 112dp / `headline-medium`, 136dp with a subtitle. `large` is large-flexible: 120dp / `display-small`, 152dp with a subtitle. `AppBar` (started by `AutoInit()`) collapses those two on scroll and opens the related search view when the search field is selected. Icons are 24dp in a 48dp target and inherit the header color so a fill + `on-*` pair stays readable.\n\nNavigation rails and tabs are separate components. Do not nest either one in the header.\n\n#### Syntax\n\n```html\n<header>\n <nav aria-label=\"Main\">\n <button type=\"button\" aria-label=\"Menu\">\n <span class=\"material-symbols\" aria-hidden=\"true\">menu</span>\n </button>\n <h2>Title</h2>\n <button type=\"button\" aria-label=\"Search\"><span class=\"material-symbols\" aria-hidden=\"true\">search</span></button>\n <button type=\"button\" aria-label=\"More\"><span class=\"material-symbols\" aria-hidden=\"true\">more_vert</span></button>\n </nav>\n</header>\n```\n\n#### Also documented\n\nThe Navbar section of the [API reference](https://www.expressivecss.com/llm.md) also covers Small, Destinations, Center-aligned, Medium and large flexible, Subtitle, Collapse, Fixed, Color, Menu, Search, Responsive navigation.\n\n#### Rules\n\nNo component-specific `semantics.json` rules apply to this guide.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
16
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### App bar\nMaterial Design 3 top app bars, from the HTML.\n\nComponent ID: `app-bar`\n\n[Component documentation](https://www.expressivecss.com/navbar.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/navbar.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/app-bars/overview)\n\nExample: A Home/Search/Profile row belongs in navigation, even if placed at the top. Keep the app bar for the title and screen actions.\n\n#### Material mapping\n\nRelationship: component. Google replaces baseline medium/large bars with flexible variants. ExpressiveCSS medium and large already mean medium-flexible and large-flexible; keep those classes.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/app-bars/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/app-bars/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/app-bars/guidelines).\n\nSupport (2026-09-17, `llm.md#navbar`): Small, medium flexible, large flexible, and search app bars.\n\nWeb adaptation: Native header/nav composition; AppBar owns scroll collapse and search expansion.\n\nKnown boundary: Native zoom, screen-reader announcements, contrast themes and physical mobile keyboard behavior remain pending manual review.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#app-bar).\n\n#### Contract\n\nThe bar is the markup. A `<header>` whose child is a `<nav>` is a top app bar. Top app bars stay pinned to the top by default, without a helper class. The heading is the headline; wrap it in `<hgroup>` with a `<p>` for a subtitle. Icon-only links and buttons are the leading and trailing actions. A `<menu>` (or `<ul>`) holds text destinations. A `<search class=\"search-bar\">` in the nav is the search app bar. There is no `navbar`, `nav-wrapper`, or `brand-logo` class.\n\nSmall is 64dp at `title-large`. `medium` is the Expressive medium-flexible bar: 112dp / `headline-medium`, 136dp with a subtitle. `large` is large-flexible: 120dp / `display-small`, 152dp with a subtitle. `AppBar` (started by `AutoInit()`) collapses those two on scroll and opens the related search view when the search field is selected. Icons are 24dp in a 48dp target and inherit the header color so a fill + `on-*` pair stays readable.\n\nNavigation rails and tabs are separate components. Do not nest either one in the header.\n\n#### Syntax\n\n```html\n<header>\n <nav aria-label=\"Main\">\n <button type=\"button\" aria-label=\"Menu\">\n <span class=\"material-symbols\" aria-hidden=\"true\">menu</span>\n </button>\n <h2>Title</h2>\n <button type=\"button\" aria-label=\"Search\"><span class=\"material-symbols\" aria-hidden=\"true\">search</span></button>\n <button type=\"button\" aria-label=\"More\"><span class=\"material-symbols\" aria-hidden=\"true\">more_vert</span></button>\n </nav>\n</header>\n```\n\n#### Also documented\n\nThe Navbar section of the [API reference](https://www.expressivecss.com/llm.md) also covers Small, Destinations, Center-aligned, Medium and large flexible, Subtitle, Collapse, Fixed, Color, Menu, Search, Responsive navigation.\n\n#### Rules\n\nNo component-specific `semantics.json` rules apply to this guide.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
13
17
  },
14
18
  {
15
19
  "file": "aura.md",
16
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Aura\nA light that runs around the border of the one button or card a screen most needs you to see.\n\nComponent ID: `aura`\n\n[Component documentation](https://www.expressivecss.com/aura.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/aura.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A pricing page with one recommended plan gets one aura on that plan's card. Putting an aura on every plan, or on each new item in a list, removes the contrast the effect depends on; use a badge for those.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-08); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-08, `llm.md#aura`): A turning light on a ring around one wrapped element, with dual, rainbow and glow styles, token color, width, corner and speed, a reversed turn in RTL, a static ring under reduced motion and a Highlight ring in forced colors.\n\nWeb adaptation: A wrapper whose pseudo-element draws a masked conic gradient; it adds nothing to the accessibility tree.\n\nKnown boundary: The ring geometry, the turning angle, its reverse in RTL, its stop under reduced motion and the absence of the ring from the accessibility tree are checked in a browser; forced colors and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#aura).\n\n#### Contract\n\nAn `.aura` wraps one element and runs a light around a 2px ring just outside it. Use it on the button, card or panel a screen most needs the user to notice, and keep it to one per screen. `dual` sends primary and tertiary lights around opposite sides, `rainbow` turns the whole hue wheel, and `glow` draws a blurred halo behind the element in place of a ring; give a glowing element an opaque container. The ring has 12px corners to match a card and is fully rounded around a button; set `--md-comp-aura-shape` to the corner size of anything else. Reduced motion stops the turning and keeps the ring. A shadow root ignores `@property`, so with the sheet adopted only there the light turns in 7.2deg steps. CSS only; there is no JavaScript.\n\nThe light is decoration drawn by a pseudo-element, so the wrapper adds nothing to the accessibility tree. Say in the text why the element matters, for example with a \"Recommended\" heading. In forced colors the ring is a solid `Highlight` outline.\n\n#### Syntax\n\n```html\n<div class=\"aura\">\n <button class=\"filled\">Upgrade</button>\n</div>\n\n<div class=\"aura rainbow\">\n <article>\n <h3>Pro plan</h3>\n <p>Unlimited projects and priority support.</p>\n </article>\n</div>\n```\n\n#### Also documented\n\nThe Aura section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `aura-wraps-one-element`: An .aura wraps one element. Give each highlighted element its own .aura, and keep to one per screen.\n"
20
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Aura\nA light that runs around the border of the one button or card a screen most needs you to see.\n\nComponent ID: `aura`\n\n[Component documentation](https://www.expressivecss.com/aura.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/aura.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A pricing page with one recommended plan gets one aura on that plan's card. Putting an aura on every plan, or on each new item in a list, removes the contrast the effect depends on; use a badge for those.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-08); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-08, `llm.md#aura`): A turning light on a ring around one wrapped element, with dual, rainbow and glow styles, token color, width, corner and speed, a reversed turn in RTL, a static ring under reduced motion and a Highlight ring in forced colors.\n\nWeb adaptation: A wrapper whose pseudo-element draws a masked conic gradient; it adds nothing to the accessibility tree.\n\nKnown boundary: The ring geometry, the turning angle, its reverse in RTL, its stop under reduced motion and the absence of the ring from the accessibility tree are checked in a browser; forced colors and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#aura).\n\n#### Contract\n\nAn `.aura` wraps one element and runs a light around a 2px ring just outside it. Use it on the button, card or panel a screen most needs the user to notice, and keep it to one per screen. `dual` sends primary and tertiary lights around opposite sides, `rainbow` turns the whole hue wheel, and `glow` draws a blurred halo behind the element in place of a ring; give a glowing element an opaque container. The ring has 12px corners to match a card and is fully rounded around a button; set `--md-comp-aura-shape` to the corner size of anything else. Reduced motion stops the turning and keeps the ring. A shadow root ignores `@property`, so with the sheet adopted only there the light turns in 7.2deg steps. CSS only; there is no JavaScript.\n\nThe light is decoration drawn by a pseudo-element, so the wrapper adds nothing to the accessibility tree. Say in the text why the element matters, for example with a \"Recommended\" heading. In forced colors the ring is a solid `Highlight` outline.\n\n#### Syntax\n\n```html\n<div class=\"aura\">\n <button class=\"filled\">Upgrade</button>\n</div>\n\n<div class=\"aura rainbow\">\n <article>\n <h3>Pro plan</h3>\n <p>Unlimited projects and priority support.</p>\n </article>\n</div>\n```\n\n#### Also documented\n\nThe Aura section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `aura-wraps-one-element`: An .aura wraps one element. Give each highlighted element its own .aura, and keep to one per screen.\n"
17
21
  },
18
22
  {
19
23
  "file": "autocomplete.md",
20
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Autocomplete\nSuggest values under a text field as the user types.\n\nComponent ID: `autocomplete`\n\n[Component documentation](https://www.expressivecss.com/autocomplete.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/autocomplete.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Related Google guidance](https://m3.material.io/components/menus/overview)\n\n#### Material mapping\n\nRelationship: related. Related guidance is not a dedicated component specification.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/menus/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/menus/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/menus/guidelines).\n\nSupport (2026-09-13, `llm.md#autocomplete`): Single and multi-select suggestions.\n\nWeb adaptation: Runtime combobox behavior attached to a text field; no standalone upstream component claim.\n\nKnown boundary: Generated status-info does not announce new suggestion counts or asynchronous results.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#autocomplete).\n\n#### Contract\n\nAutocomplete preserves the original suggestion dataset and selected display label while editing. Escape and Tab close from the input, cancel pending opening, and keep listbox options outside sequential Tab order.\n\n`isOpen` follows the suggestion menu through rerenders and dismissal. Selection changes emit a bubbling, composed `change` event from the input, so delegated form listeners can observe them.\n\nAdd `autocomplete` to a text input inside a `.field`. `AutoInit()` starts every `.autocomplete` except `no-autoinit`, but the default `data` list is empty — pass options (or call `init`) to give it something to suggest.\n\n#### Syntax\n\n```html\n<div class=\"field\">\n <span class=\"material-symbols prefix\" aria-hidden=\"true\">textsms</span>\n <input type=\"text\" id=\"autocomplete-input\" class=\"autocomplete\" placeholder=\" \">\n <label for=\"autocomplete-input\">Autocomplete</label>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `data` | Array | `[]` | Suggestion list. Each item needs an `id`; `text`, `image`, and `description` are optional. |\n| `isMultiSelect` | Boolean | `false` | If true, several values can be selected. `onAutocomplete` receives an array. |\n| `maxMenuHeight` | String | `'300px'` | Max height of the suggestion menu. |\n| `i18n` | Object | See below | Partial overrides for live status messages. |\n| `onAutocomplete` | Function | `null` | Called after a selection (and when a default value is applied). Receives the selected entries. |\n| `onSearch` | Function | filters `id` and `text` | Called when input events change the search text, including paste, replacement and deletion without key events. Load or filter data, then call `setMenuItems`. |\n| `minLength` | Number | `1` | Characters required before suggestions open. `0` shows the list on click or focus. |\n| `menuOptions` | Object | see note | Options for Menu. Defaults include `autoFocus: false`, `closeOnClick: false`, and `coverTrigger: false`. |\n| `allowUnsafeHTML` | Boolean | `false` | If true, matched text is inserted as HTML. Only use sanitized data. |\n| `selected` | Array | `[]` | Initial selected ids, strings or numbers. An id without loaded data displays the id. Pass the selected ids to `setMenuItems` with the loaded data to resolve their labels. |\n\n#### Methods\n\n- `.open()`: Open the suggestion menu if the input meets `minLength`.\n- `.close()`: Close the suggestion menu.\n- `.selectOption()`: Select (or toggle, when multi-select) the entry with this id.\n- `.setMenuItems()`: Replace the visible suggestions. Optionally pass selected ids and whether to open the menu (default `true`).\n- `.destroy()`: Destroy the plugin instance, remove the menu, and tear down its event handlers.\n\n#### Also documented\n\nThe Autocomplete section of the [API reference](https://www.expressivecss.com/llm.md) also covers Initialization, Properties.\n\n#### Rules\n\n- `autocomplete-options-are-options`: The suggestion list is a listbox; every entry in it is an option.\n- `field-container-class`: The field container is `.field`. `.input-field` matches nothing in the sheet - the only `.input-field` rule is `.chips.input-field`.\n- `field-icon-not-i`: <i> means idiomatic text, not icon. Use <span class=\"material-symbols\">.\n- `field-icon-hidden`: The ligature is read aloud. A field icon is decoration; the label names the control.\n- `field-icon-side`: A field icon must name its side with .prefix or .suffix. The positional fallback counts elements of the same *type*, which only ever worked for <i>.\n- `field-label-for`: A label that does not wrap its control has to name it. The floating label sits *after* the control and cannot wrap it, so it always needs `for`; a label that does wrap its control (the file input trigger) does not.\n- `field-control-id`: Without an id the label has nothing to point at.\n- `field-supporting-text-id`: Supporting and error text has to be referenceable to be announced with the control.\n- `field-supporting-text-linked`: A field with supporting text must point at it with aria-describedby, or the text is never read out with the control.\n- `password-toggle-reports-its-state`: Give a .password-toggle aria-pressed=\"false\". The behavior flips it, and its name stays \"Show password\", so the pressed state is how a screen reader user hears that the password is visible.\n- `otp-autocompletes`: Set autocomplete=\"one-time-code\" on a one-time code input so the browser can offer the code from a message.\n- `otp-is-not-a-number`: A code is not a quantity: type=\"number\" drops leading zeros and adds a spinner. Use type=\"text\" with inputmode=\"numeric\".\n"
24
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Autocomplete\nSuggest values under a text field as the user types.\n\nComponent ID: `autocomplete`\n\n[Component documentation](https://www.expressivecss.com/autocomplete.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/autocomplete.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Related Google guidance](https://m3.material.io/components/menus/overview)\n\n#### Material mapping\n\nRelationship: related. Related guidance is not a dedicated component specification.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/menus/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/menus/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/menus/guidelines).\n\nSupport (2026-09-13, `llm.md#autocomplete`): Single and multi-select suggestions.\n\nWeb adaptation: Runtime combobox behavior attached to a text field; no standalone upstream component claim.\n\nKnown boundary: Generated status-info does not announce new suggestion counts or asynchronous results.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#autocomplete).\n\n#### Contract\n\nAutocomplete preserves the original suggestion dataset and selected display label while editing. Escape and Tab close from the input, cancel pending opening, and keep listbox options outside sequential Tab order.\n\n`isOpen` follows the suggestion menu through rerenders and dismissal. Selection changes emit a bubbling, composed `change` event from the input, so delegated form listeners can observe them.\n\nAdd `autocomplete` to a text input inside a `.field`. `AutoInit()` starts every `.autocomplete` except `no-autoinit`, but the default `data` list is empty — pass options (or call `init`) to give it something to suggest.\n\n#### Syntax\n\n```html\n<div class=\"field\">\n <span class=\"material-symbols prefix\" aria-hidden=\"true\">textsms</span>\n <input type=\"text\" id=\"autocomplete-input\" class=\"autocomplete\" placeholder=\" \">\n <label for=\"autocomplete-input\">Autocomplete</label>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `data` | Array | `[]` | Suggestion list. Each item needs an `id`; `text`, `image`, and `description` are optional. |\n| `isMultiSelect` | Boolean | `false` | If true, several values can be selected. `onAutocomplete` receives an array. |\n| `maxMenuHeight` | String | `'300px'` | Max height of the suggestion menu. |\n| `i18n` | Object | See below | Partial overrides for live status messages. |\n| `onAutocomplete` | Function | `null` | Called after a selection (and when a default value is applied). Receives the selected entries. |\n| `onSearch` | Function | filters `id` and `text` | Called with the text, the instance and an `AbortSignal` when input events change the search text, including paste, replacement and deletion without key events. Load or filter data, then call `setMenuItems`. The signal aborts when the search text changes or the autocomplete is destroyed. |\n| `allowCreate` | Boolean | `false` | If true, text that matches no listed suggestion exactly ends the list in an `Add \"text\"` row. Choosing it calls `onCreate` and fires `expressive:create`; the selection does not change. |\n| `onCreate` | Function | `null` | Called with the trimmed text and the instance when the `Add` row is chosen. |\n| `searchDelay` | Number | `0` | Milliseconds to wait after typing pauses before calling `onSearch`, so remote data is fetched once per pause. A search on focus runs at once. Blur, `destroy()` and choosing a single result cancel a pending search. `0` searches on every input. |\n| `minLength` | Number | `1` | Characters required before suggestions open. `0` shows the list on click or focus. |\n| `menuOptions` | Object | see note | Options for Menu. Defaults include `autoFocus: false`, `closeOnClick: false`, and `coverTrigger: false`. |\n| `allowUnsafeHTML` | Boolean | `false` | If true, each data entry's `text` is inserted as HTML. Ids always stay text. Only use sanitized data. |\n| `selected` | Array | `[]` | Initial selected ids, strings or numbers. An id without loaded data displays the id. Pass the selected ids to `setMenuItems` with the loaded data to resolve their labels. |\n\n#### Methods\n\n- `.open()`: Open the suggestion menu if the input meets `minLength`.\n- `.close()`: Close the suggestion menu.\n- `.selectOption()`: Select (or toggle, when multi-select) the entry with this id.\n- `.setMenuItems()`: Replace the visible suggestions. Optionally pass selected ids and whether to open the menu (default `true`).\n- `.destroy()`: Destroy the plugin instance, remove the menu, and tear down its event handlers.\n\n#### Also documented\n\nThe Autocomplete section of the [API reference](https://www.expressivecss.com/llm.md) also covers Initialization, Properties.\n\n#### Rules\n\n- `autocomplete-options-are-options`: The suggestion list is a listbox; every entry in it is an option.\n- `field-container-class`: The field container is `.field`. `.input-field` matches nothing in the sheet - the only `.input-field` rule is `.chips.input-field`.\n- `field-icon-not-i`: <i> means idiomatic text, not icon. Use <span class=\"material-symbols\">.\n- `field-icon-hidden`: The ligature is read aloud. A field icon is decoration; the label names the control.\n- `field-icon-side`: A field icon must name its side with .prefix or .suffix. Fields have no positional fallback.\n- `field-label-for`: A label that does not wrap its control has to name it. The floating label sits *after* the control and cannot wrap it, so it always needs `for`; a label that does wrap its control (the file input trigger) does not.\n- `field-control-id`: Without an id the label has nothing to point at.\n- `field-supporting-text-id`: Supporting and error text has to be referenceable to be announced with the control.\n- `field-supporting-text-linked`: A field with supporting text must point at it with aria-describedby, or the text is never read out with the control.\n- `password-toggle-reports-its-state`: Give a .password-toggle aria-pressed=\"false\". The behavior flips it, and its name stays \"Show password\", so the pressed state is how a screen reader user hears that the password is visible.\n- `otp-autocompletes`: Set autocomplete=\"one-time-code\" on a one-time code input so the browser can offer the code from a message.\n- `otp-is-not-a-number`: A code is not a quantity: type=\"number\" drops leading zeros and adds a spinner. Use type=\"text\" with inputmode=\"numeric\".\n"
21
25
  },
22
26
  {
23
27
  "file": "avatar.md",
24
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Avatar\nA person or account as a circular image, initials or icon, alone or in an overlapping group.\n\nComponent ID: `avatar`\n\n[Component documentation](https://www.expressivecss.com/avatar.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/avatar.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A red dot for unread messages on the inbox icon is a badge. An avatar would suggest a person rather than a count.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#avatar`): Image, initials and icon avatars in three sizes, and overlapping groups with a surface ring and a count avatar.\n\nWeb adaptation: An <img> or a <span> with role=\"img\" and a name; initials scale with the size token.\n\nKnown boundary: Sizes, the circle, the group overlap and named initials are checked in a browser; image loading failures, enlarged text and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#avatar).\n\n#### Contract\n\nA `.avatar` is an `<img>`, or a `<span>` holding initials or an icon. It is 40px by default; `small` is 32px and `large` is 56px, and the initials and icon scale with it. Give an image alt text naming the person, or `alt=\"\"` when the name is already beside it. Initials read as separate letters, so give a `<span>` avatar `role=\"img\"` and an `aria-label`, or `aria-hidden=\"true\"` when the name is visible next to it. CSS only; there is no JavaScript.\n\n#### Syntax\n\n```html\n<img class=\"avatar\" src=\"ada.jpg\" alt=\"Ada Lovelace\">\n<span class=\"avatar\" role=\"img\" aria-label=\"Grace Hopper\">GH</span>\n<span class=\"avatar large\" role=\"img\" aria-label=\"Guest\">\n <span class=\"material-symbols\" aria-hidden=\"true\">person</span>\n</span>\n```\n\n#### Also documented\n\nThe Avatar section of the [API reference](https://www.expressivecss.com/llm.md) also covers Avatar group, Tokens.\n\n#### Rules\n\n- `avatar-image-has-alt`: Give an avatar image alt text naming the person, or alt=\"\" when their name is already beside it.\n- `avatar-is-an-image-or-hidden`: Initials read as separate letters and an icon reads as its ligature name. Give a non-image avatar role=\"img\" and a name, or aria-hidden=\"true\" when the person is named next to it.\n- `avatar-image-role-is-named`: An avatar with role=\"img\" needs aria-label or aria-labelledby. Its initials are hidden from the name computation of an image.\n"
28
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Avatar\nA person or account as a circular image, initials or icon, alone or in an overlapping group.\n\nComponent ID: `avatar`\n\n[Component documentation](https://www.expressivecss.com/avatar.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/avatar.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A red dot for unread messages on the inbox icon is a badge. An avatar would suggest a person rather than a count.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#avatar`): Image, initials and icon avatars in three sizes, and overlapping groups with a surface ring and a count avatar.\n\nWeb adaptation: An <img> or a <span> with role=\"img\" and a name; initials scale with the size token.\n\nKnown boundary: Sizes, the circle, the group overlap and named initials are checked in a browser; image loading failures, enlarged text and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#avatar).\n\n#### Contract\n\nA `.avatar` is an `<img>`, or a `<span>` holding initials or an icon. It is 40px by default; `small` is 32px and `large` is 56px, and the initials and icon scale with it. Give an image alt text naming the person, or `alt=\"\"` when the name is already beside it. Initials read as separate letters, so give a `<span>` avatar `role=\"img\"` and an `aria-label`, or `aria-hidden=\"true\"` when the name is visible next to it. CSS only; there is no JavaScript.\n\n#### Syntax\n\n```html\n<img class=\"avatar\" src=\"ada.jpg\" alt=\"Ada Lovelace\">\n<span class=\"avatar\" role=\"img\" aria-label=\"Grace Hopper\">GH</span>\n<span class=\"avatar large\" role=\"img\" aria-label=\"Guest\">\n <span class=\"material-symbols\" aria-hidden=\"true\">person</span>\n</span>\n```\n\n#### Also documented\n\nThe Avatar section of the [API reference](https://www.expressivecss.com/llm.md) also covers Avatar group, Tokens.\n\n#### Rules\n\n- `avatar-image-has-alt`: Give an avatar image alt text naming the person, or alt=\"\" when their name is already beside it.\n- `avatar-is-an-image-or-hidden`: Initials read as separate letters and an icon reads as its ligature name. Give a non-image avatar role=\"img\" and a name, or aria-hidden=\"true\" when the person is named next to it.\n- `avatar-image-role-is-named`: An avatar with role=\"img\" needs aria-label or aria-labelledby. Its initials are hidden from the name computation of an image.\n"
29
+ },
30
+ {
31
+ "file": "back-to-top.md",
32
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Back to top\nA link that appears once a long page has scrolled down, and returns to the top with focus on the main content.\n\nComponent ID: `back-to-top`\n\n[Component documentation](https://www.expressivecss.com/back-to-top.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/back-to-top.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A long changelog page takes a back-to-top link at the bottom corner; a short settings page needs none.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-25); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-25, `llm.md#back-to-top`): An in-page link styled with the FAB classes, hidden and inert until the window scrolls past a threshold option (one viewport height by default), a focused link kept until focus leaves, a smooth scroll to the top that is instant under reduced motion, focus moved to the href target with a temporary tabindex, the body as the fallback target, modified and already prevented clicks left to the browser, a threshold that is not a CSS length replaced by the default with a warning, markup options, offset and stacking tokens, and a link that still jumps to its target without the script.\n\nWeb adaptation: An IntersectionObserver on an empty element at the threshold instead of a scroll listener. The hidden state is the inert attribute plus visibility: hidden, with a fade in and an immediate hide, so a script that starts after the first paint does not show the link fading out, and a page without the script never hides the link.\n\nKnown boundary: The threshold, the return to the top with focus on the target by click and Enter, the hidden state's Tab order, hit testing and ARIA snapshot, a focused link kept through Home, a script that starts after the first paint, reduced motion, markup options, placement in both directions and the link without the script are checked in Chromium, Firefox and WebKit; spoken output, forced colors and real phones remain unverified. Only the window's scroll is watched.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#back-to-top).\n\n#### Contract\n\nWrite an `<a class=\"back-to-top\">` whose `href` names the page's main content, give it an `aria-label`, and add the FAB classes `button circle extra` for its look. `AutoInit()` starts every `.back-to-top`. The link sits in the bottom inline-end corner, `position: fixed`, and stays hidden until the window has scrolled past `threshold`, one viewport height by default. It hides again above it, unless it has focus: a focused link stays until focus leaves, so pressing Home on it does not drop focus to the body. Without the script the link is always visible and jumps to its target, as any in-page link does. Until the script starts it is also visible at the top of the page, so call `AutoInit()` before the first paint, from a script at the end of the body, to keep it from showing there.\n\n#### Syntax\n\n```html\n<main id=\"main\">…</main>\n<a class=\"back-to-top button circle extra\" href=\"#main\" aria-label=\"Back to top\">\n <span class=\"material-symbols\" aria-hidden=\"true\">arrow_upward</span>\n</a>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `threshold` | String | `'100vh'` | How far the window scrolls before the link shows, as a CSS length from the top of the page. Viewport units follow window resizes. A value that is not a CSS length falls back to `'100vh'` with a warning. |\n\n#### Methods\n\n- `.destroy()`: Removes the observer and the threshold element, and any `tabindex` the script added, and shows the link again.\n\n#### Also documented\n\nThe Back to top section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `back-to-top-is-an-in-page-link`: Make .back-to-top an <a> whose href names an element on the page, such as #main. The link works without the script, and the script focuses that element.\n- `back-to-top-is-named`: The link usually holds only an icon. Give it an aria-label such as \"Back to top\".\n- `back-to-top-inert-is-not-authored`: Hidden is dynamic state, so the framework owns it. BackToTop sets inert while the page is above the threshold and removes it past it; authoring it hides the link from a page without the script.\n"
25
33
  },
26
34
  {
27
35
  "file": "badges.md",
28
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Badges\nNotifications, counts, or status on navigation items and icons.\n\nComponent ID: `badges`\n\n[Component documentation](https://www.expressivecss.com/badges.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/badges.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/badges/overview)\n\nExample: An unread count belongs on the inbox icon as a badge. A selectable Unread filter needs a chip or selection control with its own name and state.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/badges/overview).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/badges/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/badges/guidelines).\n\nSupport (2026-09-13, `llm.md#badges`): Small dot and large count badges.\n\nWeb adaptation: Expose counts through the enclosing control name when its icon is hidden.\n\nKnown boundary: A named count fixture is checked; changing counts, clipping and spoken updates remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#badges).\n\n#### Contract\n\nA `<span class=\"badge\">` is the badge. Empty is the **small** 6dp dot; text is the **large** 16dp stadium. Nest it in the icon so it sits on the upper trailing edge of the 24dp glyph. Limit the label to four characters, including `+`. The default mapping is `error` / `on-error`.\n\nNesting has a consequence worth stating: **a badge inside a hidden icon is hidden with it.** `aria-hidden=\"true\"` covers the whole subtree, and `aria-hidden=\"false\"` on a descendant does not undo that — so a count that only exists inside a decorative icon is a count nobody hears. Two ways out, depending on what surrounds it:\n\n- Inside a control, put the count in the control's name — `<a aria-label=\"Inbox, 3 unread\">` — and leave the icon decorative. - Standing alone, make the icon the image: `role=\"img\"` with a label that includes the count.\n\n#### Syntax\n\n```html\n<span class=\"material-symbols\" role=\"img\" aria-label=\"Mail, unread\">mail<span class=\"badge\"></span></span>\n<span class=\"material-symbols\" role=\"img\" aria-label=\"Mail, 1 unread\">mail<span class=\"badge\">1</span></span>\n<span class=\"material-symbols\" role=\"img\" aria-label=\"Mail, 999+ unread\">mail<span class=\"badge\">999+</span></span>\n```\n\n#### Also documented\n\nThe Badges section of the [API reference](https://www.expressivecss.com/llm.md) also covers Small, Large, On navigation, In a list, Color.\n\n#### Rules\n\n- `badge-count-survives-a-hidden-icon`: A badge inside a hidden icon is hidden with it - aria-hidden=\"false\" on a descendant does not undo that. Put the count in the control name (aria-label=\"Inbox, 3 unread\"), or move the badge out of the icon into the flow.\n"
36
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Badges\nNotifications, counts, or status on navigation items and icons.\n\nComponent ID: `badges`\n\n[Component documentation](https://www.expressivecss.com/badges.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/badges.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/badges/overview)\n\nExample: An unread count belongs on the inbox icon as a badge. A selectable Unread filter needs a chip or selection control with its own name and state.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/badges/overview).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/badges/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/badges/guidelines).\n\nSupport (2026-09-13, `llm.md#badges`): Small dot and large count badges.\n\nWeb adaptation: Expose counts through the enclosing control name when its icon is hidden.\n\nKnown boundary: A named count fixture is checked; changing counts, clipping and spoken updates remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#badges).\n\n#### Contract\n\nA `<span class=\"badge\">` is the badge. Empty is the **small** 6dp dot; text is the **large** 16dp stadium. Nest it in the icon so it sits on the upper trailing edge of the 24dp glyph. Limit the label to four characters, including `+`. The default mapping is `error` / `on-error`.\n\nNesting has a consequence worth stating: **a badge inside a hidden icon is hidden with it.** `aria-hidden=\"true\"` covers the whole subtree, and `aria-hidden=\"false\"` on a descendant does not undo that — so a count that only exists inside a decorative icon is a count nobody hears. Two ways out, depending on what surrounds it:\n\n- Inside a control, put the count in the control's name — `<a aria-label=\"Inbox, 3 unread\">` — and leave the icon decorative. - Standing alone, make the icon the image: `role=\"img\"` with a label that includes the count.\n\n#### Syntax\n\n```html\n<span class=\"material-symbols\" role=\"img\" aria-label=\"Mail, unread\">mail<span class=\"badge\"></span></span>\n<span class=\"material-symbols\" role=\"img\" aria-label=\"Mail, 1 unread\">mail<span class=\"badge\">1</span></span>\n<span class=\"material-symbols\" role=\"img\" aria-label=\"Mail, 999+ unread\">mail<span class=\"badge\">999+</span></span>\n```\n\n#### Also documented\n\nThe Badges section of the [API reference](https://www.expressivecss.com/llm.md) also covers Small, Large, On navigation, In a list, Color.\n\n#### Rules\n\n- `badge-count-survives-a-hidden-icon`: A badge inside a hidden icon is hidden with it - aria-hidden=\"false\" on a descendant does not undo that. Put the count in the control name (aria-label=\"Inbox, 3 unread\"), or move the badge out of the icon into the flow.\n"
29
37
  },
30
38
  {
31
39
  "file": "bar-chart.md",
32
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Bar chart\nRanked or long-labelled categories drawn as horizontal bars from a data table, grouped, stacked or as a meter.\n\nComponent ID: `bar-chart`\n\n[Component documentation](https://www.expressivecss.com/bar-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/bar-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Visitors from five traffic sources, longest first, is a bar chart with values inside the bars. Visitors per month for a year is a column chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-07); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-07, `llm.md#bar-chart`): Horizontal bars drawn from zero from a figure's data table, grouped by row or stacked, with split, track, values and sparkline modifiers, negative values, gaps, a fixed value range, row labels, a legend, and a pointer and keyboard tooltip with a highlight band.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the bars and their values are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, grouped, stacked and split geometry, values, gaps, negative values, the value range, RTL, the tooltip, the band, keyboard movement and destroy are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. There are no value axis labels.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#bar-chart).\n\n#### Contract\n\nA `<figure class=\"bar-chart\">` takes the same `<figcaption>` and `<table>` as a column chart. `AutoInit()` draws each row as a band of bars growing from zero along the inline axis, one bar per series, with the row's label at the inline start, a legend when there is more than one series, and a highlight band and tooltip that follow the pointer. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"bar-chart track values\">\n <figcaption>Visitors by source</figcaption>\n <table>\n <thead>\n <tr><th>Source</th><th>Visitors</th></tr>\n </thead>\n <tbody>\n <tr><th>Search</th><td data-value=\"900000\">900K</td></tr>\n <tr><th>Social</th><td data-value=\"650000\">650K</td></tr>\n <tr><th>Email</th><td data-value=\"420000\">420K</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Bar chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `bar-chart-is-a-figure-with-a-table`: Write .bar-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `bar-chart-has-a-caption`: Name a .bar-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
40
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Bar chart\nRanked or long-labelled categories drawn as horizontal bars from a data table, grouped, stacked or as a meter.\n\nComponent ID: `bar-chart`\n\n[Component documentation](https://www.expressivecss.com/bar-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/bar-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Visitors from five traffic sources, longest first, is a bar chart with values inside the bars. Visitors per month for a year is a column chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-07); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-07, `llm.md#bar-chart`): Horizontal bars drawn from zero from a figure's data table, grouped by row or stacked, with split, track, values and sparkline modifiers, negative values, gaps, a fixed value range, row labels, round value axis labels with .axis, a legend, and a pointer and keyboard tooltip with a highlight band.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the bars and their values are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, grouped, stacked and split geometry, values, gaps, negative values, the value range, RTL, the tooltip, the band, keyboard movement and destroy are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. There are no value axis labels.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#bar-chart).\n\n#### Contract\n\nA `<figure class=\"bar-chart\">` takes the same `<figcaption>` and `<table>` as a column chart. `AutoInit()` draws each row as a band of bars growing from zero along the inline axis, one bar per series, with the row's label at the inline start, a legend when there is more than one series, and a highlight band and tooltip that follow the pointer. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"bar-chart track values\">\n <figcaption>Visitors by source</figcaption>\n <table>\n <thead>\n <tr><th>Source</th><th>Visitors</th></tr>\n </thead>\n <tbody>\n <tr><th>Search</th><td data-value=\"900000\">900K</td></tr>\n <tr><th>Social</th><td data-value=\"650000\">650K</td></tr>\n <tr><th>Email</th><td data-value=\"420000\">420K</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Bar chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `bar-chart-is-a-figure-with-a-table`: Write .bar-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `bar-chart-has-a-caption`: Name a .bar-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
33
41
  },
34
42
  {
35
43
  "file": "bottom-sheet.md",
36
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Bottom sheet\nSecondary content anchored to the bottom of the screen.\n\nComponent ID: `bottom-sheet`\n\n[Component documentation](https://www.expressivecss.com/bottom-sheet.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/bottom-sheet.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. [Google guidance](https://m3.material.io/components/bottom-sheets/overview)\n\n- expanded window: replace-with [side-sheet](./side-sheet.md). Persistent secondary content fits beside the main task.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/bottom-sheets/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/bottom-sheets/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/bottom-sheets/guidelines).\n\nSupport (2026-09-18, `llm.md#bottom-sheet`): Standard and modal bottom sheets.\n\nWeb adaptation: Native dialog.show()/showModal(); shared runtime handles dragging and dismissal.\n\nKnown boundary: The shared handle dismisses rather than cycling sheet heights. Long-content scrolling and enlarged-text action access are checked; native zoom and physical touch remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#bottom-sheet).\n\n#### Contract\n\nA recent flick dismisses the sheet even if the pointer stops moving at release. For short drags, pausing for at least 100ms or reversing direction lets it snap back.\n\nA `dialog.bottom-sheet` (or `.bottom`) is secondary content anchored to the bottom. Use it on Compact and Medium windows. `showModal()` is the modal variant (scrim). `show()` is the standard variant (no scrim). Same sheet either way: `surface-container-low`, 28dp top corners, 640dp max, 56dp side inset from the Medium breakpoint, 72dp top inset, 32×4 drag handle in a 48dp hit target. Drag the handle down to dismiss. A named `<button>` handle also dismisses on click, tap, <kbd>Enter</kbd>, or <kbd>Space</kbd>. A decorative handle needs another non-drag pointer and keyboard dismiss path, such as a `form method=\"dialog\"` close button. Verify native Escape against the opening mode, browser, and `closedby` policy.\n\n#### Syntax\n\n```html\n<dialog class=\"bottom-sheet\" aria-labelledby=\"open-file-title\">\n <h2 id=\"open-file-title\">Open file</h2>\n <div>…</div>\n <form method=\"dialog\">\n <button type=\"submit\" class=\"text\" value=\"cancel\">Cancel</button>\n </form>\n</dialog>\n```\n\n#### Rules\n\n- `dialog-is-named`: A <dialog> takes no name from its heading. Point aria-labelledby at that heading, or give it an aria-label - otherwise it opens announced as just \"dialog\".\n- `drag-handle-is-hidden-or-a-control`: A drag handle has no text and reports nothing, so on any element but a <button> it is decoration and needs aria-hidden=\"true\". Left exposed it arrives as an unlabelled blank in the reading order. Make it a <button> with a name if it is meant to be operated.\n- `drag-handle-button-is-not-hidden`: aria-hidden on a <button> hides it from assistive technology without taking it out of the tab order, so keyboard focus lands on something that is not there. Decoration is a <span>; a control stays exposed.\n- `drag-handle-button-is-named`: The handle is a bar drawn in CSS - there is no text inside it to be named by. A handle worth making a control is worth an aria-label saying what it moves.\n"
44
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Bottom sheet\nSecondary content anchored to the bottom of the screen.\n\nComponent ID: `bottom-sheet`\n\n[Component documentation](https://www.expressivecss.com/bottom-sheet.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/bottom-sheet.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. [Google guidance](https://m3.material.io/components/bottom-sheets/overview)\n\n- expanded window: replace-with [side-sheet](./side-sheet.md). Persistent secondary content fits beside the main task.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/bottom-sheets/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/bottom-sheets/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/bottom-sheets/guidelines).\n\nSupport (2026-09-18, `llm.md#bottom-sheet`): Standard and modal bottom sheets, with partial and full heights for .expandable sheets.\n\nWeb adaptation: Native dialog.show()/showModal(); shared runtime handles dragging and dismissal.\n\nKnown boundary: An .expandable sheet has a partial and a full height and no others. Long-content scrolling and enlarged-text action access are checked; native zoom and physical touch remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#bottom-sheet).\n\n#### Contract\n\nA recent flick dismisses the sheet even if the pointer stops moving at release. For short drags, pausing for at least 100ms or reversing direction lets it snap back.\n\nA `dialog.bottom-sheet` is secondary content anchored to the bottom. Use it on Compact and Medium windows. `showModal()` is the modal variant (scrim). `show()` is the standard variant (no scrim). Same sheet either way: `surface-container-low`, 28dp top corners, 640dp max, 56dp side inset from the Medium breakpoint, 72dp top inset, 32×4 drag handle in a 48dp hit target. Drag the handle down to dismiss. A named `<button>` handle also dismisses on click, tap, <kbd>Enter</kbd>, or <kbd>Space</kbd>. A decorative handle needs another non-drag pointer and keyboard dismiss path, such as a `form method=\"dialog\"` close button. Verify native Escape against the opening mode, browser, and `closedby` policy.\n\n#### Syntax\n\n```html\n<dialog class=\"bottom-sheet\" aria-labelledby=\"open-file-title\">\n <h2 id=\"open-file-title\">Open file</h2>\n <div>…</div>\n <form method=\"dialog\">\n <button type=\"submit\" class=\"text\" value=\"cancel\">Cancel</button>\n </form>\n</dialog>\n```\n\n#### Rules\n\n- `dialog-is-named`: A <dialog> takes no name from its heading. Point aria-labelledby at that heading, or give it an aria-label - otherwise it opens announced as just \"dialog\".\n- `drag-handle-is-hidden-or-a-control`: A drag handle has no text and reports nothing, so on any element but a <button> it is decoration and needs aria-hidden=\"true\". Left exposed it arrives as an unlabelled blank in the reading order. Make it a <button> with a name if it is meant to be operated.\n- `drag-handle-button-is-not-hidden`: aria-hidden on a <button> hides it from assistive technology without taking it out of the tab order, so keyboard focus lands on something that is not there. Decoration is a <span>; a control stays exposed.\n- `drag-handle-button-is-named`: The handle is a bar drawn in CSS - there is no text inside it to be named by. A handle worth making a control is worth an aria-label saying what it moves.\n"
37
45
  },
38
46
  {
39
47
  "file": "breadcrumbs.md",
40
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Breadcrumbs\nThe path to this page, from the HTML.\n\nComponent ID: `breadcrumbs`\n\n[Component documentation](https://www.expressivecss.com/breadcrumbs.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/breadcrumbs.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Overview/Activity/Billing are peers, not ancestors. Use tabs for their same-page panels, not a breadcrumb trail.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-09-13); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. Full visual parity and spoken output unverified.\n\nSupport (2026-09-13, `llm.md#breadcrumbs`): Breadcrumb links and app-bar composition.\n\nWeb adaptation: Labelled navigation with an ordered list and current-page link.\n\nKnown boundary: Named navigation and current-page markup are checked; CSS separator speech and long-path reflow remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#breadcrumbs).\n\n#### Contract\n\nShow the current location when the page sits several layers deep.\n\nBreadcrumbs are a good way to display your current location. This is usually used when you have multiple layers of content.\n\n#### Syntax\n\n```html\n<nav aria-label=\"Breadcrumb\">\n <ol>\n <li><a href=\"/\">Home</a></li>\n <li><a href=\"/library\">Library</a></li>\n <li><a href=\"/library/data\" aria-current=\"page\">Data</a></li>\n </ol>\n</nav>\n```\n\n#### Also documented\n\nThe Breadcrumbs section of the [API reference](https://www.expressivecss.com/llm.md) also covers Basic, Navbar style.\n\n#### Rules\n\n- `breadcrumb-is-ordered-list`: Crumbs are an ordered list - nav > ol > li > a. Loose anchors give no count and no position, so \"3 of 4\" is never announced.\n- `breadcrumb-marks-current`: The last crumb is the page you are on; it needs aria-current=\"page\" to say so.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
48
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Breadcrumbs\nThe path to this page, from the HTML.\n\nComponent ID: `breadcrumbs`\n\n[Component documentation](https://www.expressivecss.com/breadcrumbs.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/breadcrumbs.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Overview/Activity/Billing are peers, not ancestors. Use tabs for their same-page panels, not a breadcrumb trail.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-09-13); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. Full visual parity and spoken output unverified.\n\nSupport (2026-09-13, `llm.md#breadcrumbs`): Breadcrumb links and app-bar composition.\n\nWeb adaptation: Labelled navigation with an ordered list and current-page link.\n\nKnown boundary: Named navigation and current-page markup are checked; CSS separator speech and long-path reflow remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#breadcrumbs).\n\n#### Contract\n\nShow the current location when the page sits several layers deep.\n\nBreadcrumbs are a good way to display your current location. This is usually used when you have multiple layers of content.\n\n#### Syntax\n\n```html\n<nav aria-label=\"Breadcrumb\">\n <ol>\n <li><a href=\"/\">Home</a></li>\n <li><a href=\"/library\">Library</a></li>\n <li><a href=\"/library/data\" aria-current=\"page\">Data</a></li>\n </ol>\n</nav>\n```\n\n#### Also documented\n\nThe Breadcrumbs section of the [API reference](https://www.expressivecss.com/llm.md) also covers Basic, Navbar style.\n\n#### Rules\n\n- `breadcrumb-is-ordered-list`: Crumbs are an ordered list - nav > ol > li > a. Loose anchors give no count and no position, so \"3 of 4\" is never announced.\n- `breadcrumb-marks-current`: The last crumb is the page you are on; it needs aria-current=\"page\" to say so.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
41
49
  },
42
50
  {
43
51
  "file": "button-groups.md",
44
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Button groups\nRelated buttons that bump and reshape against each other, in two variants and five sizes.\n\nComponent ID: `button-groups`\n\n[Component documentation](https://www.expressivecss.com/button-groups.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/button-groups.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/button-groups/overview)\n\nExample: Standard groups fit Cut/Copy/Paste commands; connected groups fit toggle choices. A pressed button does not submit a radio value: use native inputs for form values, or explicitly integrate persistence.\n\n#### Material mapping\n\nRelationship: component. Google recommends the connected button group instead of segmented buttons. Match toggle state, required selection and persistence explicitly; native form submission is not supplied by aria-pressed.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/button-groups/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/button-groups/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/button-groups/guidelines).\n\nSupport (2026-09-18, `llm.md#button-groups`): Standard and connected groups; commands and opt-in selection.\n\nWeb adaptation: Runtime coordinates neighboring widths and selection; CSS handles connected shapes.\n\nKnown boundary: Native zoom, spoken output and physical-device behavior remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#button-groups).\n\n#### Contract\n\nA Material 3 Expressive button group places related commands or toggle buttons in one visual cluster. The root is a `<div class=\"button-group\">`; direct children can be common buttons or `.icon-button` controls. `ButtonGroup` is initialized by `Expressive.AutoInit()` to coordinate standard press widths. Selection management is opt-in through `data-selection`.\n\nCommon buttons and icon buttons both inherit group sizing and participate in standard growth and connected shape morphing. Standard press growth is 15% of the item's measured width and is taken from its immediate neighbors, keeping the group's total width stable. An explicit size class on a child overrides the group size. Use a visible container style: filled, tonal, outlined, or elevated for common buttons; filled, tonal, or outlined for icon buttons. Material warns against text buttons and standard icon buttons in groups because their transparent containers hide the shape interaction.\n\nAction-group items are direct controls: a `<button>`, or an `<a class=\"button\" href>` when it navigates. Selection groups accept only native `<button type=\"button\">` children, each with `aria-pressed`. A wrapper loses the gap and connected corners. Every icon-only item carries its own accessible name because the icon is `aria-hidden`.\n\n#### Syntax\n\n```html\n<div class=\"button-group\" data-selection=\"multiple\" role=\"group\" aria-label=\"Text formatting\">\n <button type=\"button\" class=\"button tonal\" aria-pressed=\"false\">\n <span class=\"material-symbols\" aria-hidden=\"true\">format_bold</span>\n Bold\n </button>\n <button type=\"button\" class=\"button tonal\" aria-pressed=\"false\">\n <span class=\"material-symbols\" aria-hidden=\"true\">format_italic</span>\n Italic\n </button>\n</div>\n```\n\n#### Rules\n\n- `button-group-is-not-an-authored-composite-widget`: A button group takes no composite role; its buttons are reached with Tab, not arrow keys. `role=\"group\"` with an aria-label is the role that fits, and it promises no keyboard model.\n- `button-group-items-are-controls`: Every item in a group is a control the user can reach, and one the sheet styles as a button: a <button>, or an <a class=\"button\" href> when it navigates. A bare <a href> passes for a control and renders as a link - none of the group's corners, press behaviour or inherited size reach it. A wrapper element is refused too: the gap and the connected corners are written against direct children, so a nested <div> loses both.\n- `button-group-items-have-containers`: Material warns against text buttons in a group because they have no visible container for the shape interaction. Use filled, tonal, outlined, or elevated buttons.\n- `button-group-icon-items-have-containers`: Material warns against standard icon buttons in a group because they have no visible container for the shape interaction. Use a filled, tonal, or outlined icon button.\n- `button-group-selection-mode-is-valid`: ButtonGroup supports data-selection=\"single\" or data-selection=\"multiple\". Any other value exposes toggle state without a defined selection model.\n- `button-group-required-needs-selection`: data-selection-required only has meaning on a single- or multiple-selection ButtonGroup.\n- `button-group-connected-is-selectable`: Material connected button groups contain toggleable choices. Add data-selection=\"single|multiple\", or use the standard group for one-shot actions.\n- `button-group-selection-items-are-buttons`: Selection items are native buttons. Links navigate and cannot represent toggle-button state.\n- `button-group-selection-button-type`: A selection button inside a form must not submit it; set type=\"button\".\n- `button-group-selection-state-is-pressed`: Author each toggle button's initial aria-pressed value. ButtonGroup maintains that state after initialization.\n- `button-group-selection-state-is-boolean`: A toggle button's initial aria-pressed value is true or false; mixed is not a Button Group state.\n- `button-group-selection-does-not-use-choice-state`: These are toggle buttons, not radios, checkboxes, options, or tabs. Expose their state with aria-pressed.\n- `button-group-action-does-not-claim-selection`: aria-pressed requires ButtonGroup selection behavior. Add data-selection=\"single|multiple\" or remove the toggle state.\n"
52
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Button groups\nRelated buttons that bump and reshape against each other, in two variants and five sizes.\n\nComponent ID: `button-groups`\n\n[Component documentation](https://www.expressivecss.com/button-groups.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/button-groups.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/button-groups/overview)\n\nExample: Standard groups fit Cut/Copy/Paste commands; connected groups fit toggle choices. A pressed button does not submit a radio value: use native inputs for form values, or explicitly integrate persistence.\n\n#### Material mapping\n\nRelationship: component. Google recommends the connected button group instead of segmented buttons. Match toggle state, required selection and persistence explicitly; native form submission is not supplied by aria-pressed.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/button-groups/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/button-groups/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/button-groups/guidelines).\n\nSupport (2026-09-18, `llm.md#button-groups`): Standard and connected groups; commands and opt-in selection.\n\nWeb adaptation: Runtime coordinates neighboring widths and selection; CSS handles connected shapes.\n\nKnown boundary: Native zoom, spoken output and physical-device behavior remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#button-groups).\n\n#### Contract\n\nA Material 3 Expressive button group places related commands or toggle buttons in one visual cluster. The root is a `<div class=\"button-group\">`; direct children can be common buttons or `.icon-button` controls. `ButtonGroup` is initialized by `Expressive.AutoInit()` to coordinate standard press widths. Selection management is opt-in through `data-selection`.\n\nCommon buttons and icon buttons both inherit group sizing and participate in standard growth and connected shape morphing. Standard press growth is 15% of the item's measured width and is taken from its immediate neighbors, keeping the group's total width stable. An explicit size class on a child overrides the group size. Use a visible container style: filled, tonal, outlined, or elevated for common buttons; filled, tonal, or outlined for icon buttons. Material warns against text buttons and standard icon buttons in groups because their transparent containers hide the shape interaction.\n\nAction-group items are direct controls: a `<button>`, or an `<a class=\"button\" href>` when it navigates. Selection groups accept only native `<button type=\"button\">` children, each with `aria-pressed`. A wrapper loses the gap and connected corners. Every icon-only item carries its own accessible name because the icon is `aria-hidden`.\n\n#### Syntax\n\n```html\n<div class=\"button-group\" data-selection=\"multiple\" role=\"group\" aria-label=\"Text formatting\">\n <button type=\"button\" class=\"button tonal\" aria-pressed=\"false\">\n <span class=\"material-symbols\" aria-hidden=\"true\">format_bold</span>\n Bold\n </button>\n <button type=\"button\" class=\"button tonal\" aria-pressed=\"false\">\n <span class=\"material-symbols\" aria-hidden=\"true\">format_italic</span>\n Italic\n </button>\n</div>\n```\n\n#### Rules\n\n- `button-group-is-not-an-authored-composite-widget`: A button group takes no composite role; its buttons are reached with Tab, not arrow keys. `role=\"group\"` with an aria-label is the role that fits, and it promises no keyboard model.\n- `button-group-items-are-controls`: Every item in a group is a control the user can reach, and one the sheet styles as a button: a <button>, or an <a class=\"button\" href> when it navigates. A bare <a href> passes for a control and renders as a link - none of the group's corners, press behaviour or inherited size reach it. A wrapper element is refused too: the gap and the connected corners are written against direct children, so a nested <div> loses both.\n- `button-group-items-have-containers`: Material warns against text buttons in a group because they have no visible container for the shape interaction. Use filled, tonal, outlined, or elevated buttons.\n- `button-group-icon-items-have-containers`: Material warns against standard icon buttons in a group because they have no visible container for the shape interaction. Use a filled, tonal, or outlined icon button.\n- `button-group-selection-mode-is-valid`: ButtonGroup supports data-selection=\"single\" or data-selection=\"multiple\". Any other value exposes toggle state without a defined selection model.\n- `button-group-required-needs-selection`: data-selection-required only has meaning on a single- or multiple-selection ButtonGroup.\n- `button-group-connected-is-selectable`: Material connected button groups contain toggleable choices. Add data-selection=\"single|multiple\", or use the standard group for one-shot actions.\n- `button-group-selection-items-are-buttons`: Selection items are native buttons. Links navigate and cannot represent toggle-button state.\n- `button-group-selection-button-type`: A selection button inside a form must not submit it; set type=\"button\".\n- `button-group-selection-state-is-pressed`: Author each toggle button's initial aria-pressed value. ButtonGroup maintains that state after initialization.\n- `button-group-selection-state-is-boolean`: A toggle button's initial aria-pressed value is true or false; mixed is not a Button Group state.\n- `button-group-selection-does-not-use-choice-state`: These are toggle buttons, not radios, checkboxes, options, or tabs. Expose their state with aria-pressed.\n- `button-group-action-does-not-claim-selection`: aria-pressed requires ButtonGroup selection behavior. Add data-selection=\"single|multiple\" or remove the toggle state.\n"
45
53
  },
46
54
  {
47
55
  "file": "buttons.md",
48
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Buttons\nMaterial Design 3 common buttons, icon buttons, and FABs — from the HTML.\n\nComponent ID: `buttons`\n\n[Component documentation](https://www.expressivecss.com/buttons.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/buttons.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/buttons/overview)\n\nExample: Use radios for saved form values; buttons run commands.\n\n#### Material mapping\n\nRelationship: component. Google no longer recommends 24dp padding for small buttons; use the current 16dp inset. This is spacing guidance, not removal of a button style.\n\nUpstream: overview-specs-guidelines-accessibility-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/buttons/overview).\n\nRendered prose reviewed; collapsed token tables, image-only measurements, runtime and full parity unverified. See the design priority component review for requirement-level findings.\n\n[Specification link](https://m3.material.io/components/buttons/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/buttons/guidelines).\n\nSupport (2026-09-07, `llm.md#buttons`): Filled, tonal, outlined, elevated, and text; xsmall through xlarge.\n\nWeb adaptation: Native buttons run commands; a.button links navigate.\n\nKnown boundary: Standalone common buttons do not document a square/toggle shape-morph contract; do not borrow icon-button or connected-group modifiers.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#buttons).\n\n#### Contract\n\nA `<button>` is a filled common button. An `<a class=\"button\">` is the same thing for a link — on an anchor the class is `button`; the older `.btn` spelling only ever worked on a `<button>`, which is styled as one anyway. Put a `<span class=\"material-symbols\" aria-hidden=\"true\">` icon before or after the label and wrap the label in its own `<span>` — there is no `icon-left` / `icon-right` class, the order of the two spans is the placement. Add `circle` for a round common button and name it with `aria-label`, since the icon is hidden. Icon buttons are `.icon-button`.\n\nTwo independent axes dress it: a style (`filled`, `tonal`, `outlined`, `elevated`, `text`) and a size (`xsmall` through `xlarge`). Any style combines with any size — nine classes, not twenty-five, because the size sets the geometry and the style sets the color.\n\nTokens follow the [M3 button spec](https://m3.material.io/components/buttons/specs). The default is the small size: 40dp tall, label `label-large`, fully round corners, a 20dp icon on an 8dp gap, and a symmetric 16dp inset. State layers are 8% hover and 10% focus or press. Disabled is `on-surface` at 38% on a 12% container.\n\n#### Syntax\n\n```html\n<button>Create</button>\n<button>\n <span class=\"material-symbols\" aria-hidden=\"true\">add</span><span>Create</span>\n</button>\n<button>\n <span>Send</span><span class=\"material-symbols\" aria-hidden=\"true\">send</span>\n</button>\n<button class=\"circle\" aria-label=\"Add\">\n <span class=\"material-symbols\" aria-hidden=\"true\">add</span>\n</button>\n\n<a class=\"button\" href=\"/docs\">Docs</a>\n```\n\n#### Also documented\n\nThe Buttons section of the [API reference](https://www.expressivecss.com/llm.md) also covers Filled, Tonal, Outlined, Elevated, Text, Icon buttons, Floating, Sizes, Disabled.\n\n#### Rules\n\n- `icon-only-control-is-named`: Every icon is hidden from assistive technology, so a control whose only content is one has no name left. Give it an aria-label.\n- `fab-no-small-variant`: The 40dp small FAB has been removed. Use circle extra for a 56dp FAB.\n- `extended-fab-requires-size`: The baseline extended FAB has been removed. Specify extend small, extend medium or extend large.\n- `fab-no-surface-color`: Use a primary, secondary or tertiary container color for a FAB instead of a surface color.\n"
56
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Buttons\nMaterial Design 3 common buttons, icon buttons, and FABs, from the HTML.\n\nComponent ID: `buttons`\n\n[Component documentation](https://www.expressivecss.com/buttons.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/buttons.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/buttons/overview)\n\nExample: Use radios for saved form values; buttons run commands.\n\n#### Material mapping\n\nRelationship: component. Google no longer recommends 24dp padding for small buttons; use the current 16dp inset. This is spacing guidance, not removal of a button style.\n\nUpstream: overview-specs-guidelines-accessibility-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/buttons/overview).\n\nRendered prose reviewed; collapsed token tables, image-only measurements, runtime and full parity unverified. See the design priority component review for requirement-level findings.\n\n[Specification link](https://m3.material.io/components/buttons/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/buttons/guidelines).\n\nSupport (2026-09-07, `llm.md#buttons`): Filled, tonal, outlined, elevated, and text; xsmall through xlarge.\n\nWeb adaptation: Native buttons run commands; a.button links navigate.\n\nKnown boundary: Standalone toggle buttons take their shape and colours from aria-pressed; the page switches aria-pressed, as the component ships no toggle script.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#buttons).\n\n#### Contract\n\nMaterial Design 3 common buttons, icon buttons, and FABs — from the HTML.\n\nA `<button>` is a filled common button. An `<a class=\"button\">` is the same thing for a link — on an anchor the class is `button`; the older `.btn` spelling only ever worked on a `<button>`, which is styled as one anyway. Put a `<span class=\"material-symbols\" aria-hidden=\"true\">` icon before or after the label and wrap the label in its own `<span>` — there is no `icon-left` / `icon-right` class, the order of the two spans is the placement. Add `circle` for a round common button and name it with `aria-label`, since the icon is hidden. Icon buttons are `.icon-button`.\n\nTwo independent axes dress it: a style (`filled`, `tonal`, `outlined`, `elevated`, `text`) and a size (`xsmall` through `xlarge`). Any style combines with any size — nine classes, not twenty-five, because the size sets the geometry and the style sets the color.\n\n#### Syntax\n\n```html\n<button>Create</button>\n<button>\n <span class=\"material-symbols\" aria-hidden=\"true\">add</span><span>Create</span>\n</button>\n<button>\n <span>Send</span><span class=\"material-symbols\" aria-hidden=\"true\">send</span>\n</button>\n<button class=\"circle\" aria-label=\"Add\">\n <span class=\"material-symbols\" aria-hidden=\"true\">add</span>\n</button>\n\n<a class=\"button\" href=\"/docs\">Docs</a>\n```\n\n#### Also documented\n\nThe Buttons section of the [API reference](https://www.expressivecss.com/llm.md) also covers Filled, Tonal, Outlined, Elevated, Text, Icon buttons, Floating, Sizes, Shape and toggles, Disabled.\n\n#### Rules\n\n- `icon-only-control-is-named`: Every icon is hidden from assistive technology, so a control whose only content is one has no name left. Give it an aria-label.\n- `fab-no-small-variant`: The 40dp small FAB has been removed. Use circle extra for a 56dp FAB.\n- `extended-fab-requires-size`: The baseline extended FAB has been removed. Specify extend small, extend medium or extend large.\n- `fab-no-surface-color`: Use a primary, secondary or tertiary container color for a FAB instead of a surface color.\n"
57
+ },
58
+ {
59
+ "file": "calendar.md",
60
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Calendar\nA month of days with their events, on a table the page renders.\n\nComponent ID: `calendar`\n\n[Component documentation](https://www.expressivecss.com/calendar.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/calendar.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A team schedule for October is a calendar. Choosing a delivery date in a form is the date picker.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-15); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-22, `llm.md#calendar`): A seven-column month grid with weekday headers, today, days outside the month and event chips that ellipsize; week and day hourly grids with events placed by start and duration; a year of small months in as many columns as fit.\n\nWeb adaptation: A native table with a caption; the page renders the dates, navigation and events.\n\nKnown boundary: Equal columns, today, outside days, ellipsized event chips, column headers, hourly event placement, the year tiling and the phone width are checked in a browser; screen reader output remains unverified.\n\nKnown boundary: Events that overlap in time overlap on screen in the week and day views; the page splits them.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#calendar).\n\n#### Contract\n\nA month, week or day of events, or a year of months, on tables the page renders.\n\nA `table.calendar` has a `<caption>` naming the month, a header row of weekday `<th scope=\"col\">` cells, then a row per week. Each day `<td>` starts with a `<time datetime>` and may hold a `.calendar-events` list of links or text. Mark days from other months with `outside` and today with `aria-current=\"date\"`. The page owns the dates, the navigation between months and the events; CSS draws seven equal columns, the today mark and event chips that ellipsize. On a phone the days get shorter and chips tighter. CSS only; there is no JavaScript. Use the date picker to choose a date for a field.\n\n#### Syntax\n\n```html\n<table class=\"calendar\">\n <caption>October 2026</caption>\n <thead>\n <tr><th scope=\"col\"><abbr title=\"Sunday\">Sun</abbr></th>…</tr>\n </thead>\n <tbody>\n <tr>\n <td class=\"outside\"><time datetime=\"2026-09-27\">27</time></td>\n <td aria-current=\"date\">\n <time datetime=\"2026-10-15\">15</time>\n <ul class=\"calendar-events\"><li><a href=\"/events/standup\">Standup</a></li></ul>\n </td>\n </tr>\n </tbody>\n</table>\n```\n\n#### Rules\n\n- `calendar-is-a-table`: Write .calendar as a <table> with a <caption> and a header row of weekdays. A grid of divs loses the day-to-weekday relationship.\n- `calendar-has-a-caption`: Give the calendar a <caption> naming the month, such as \"October 2026\". Without it nothing says which month the days belong to.\n"
49
61
  },
50
62
  {
51
63
  "file": "cards.md",
52
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Cards\nMaterial Design 3 cards, from the HTML.\n\nComponent ID: `cards`\n\n[Component documentation](https://www.expressivecss.com/cards.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/cards.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/cards/overview)\n\nExample: A hundred compact activity records need a list. Giving every row a card adds separation without a separate content or action group.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/cards/overview).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/cards/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/cards/guidelines).\n\nSupport (2026-09-13, `llm.md#cards`): Elevated, filled, outlined, horizontal, and directly actionable cards.\n\nWeb adaptation: Native article anatomy; primary-action links preserve navigation semantics.\n\nKnown boundary: Existing card lifecycle and geometry evidence does not cover every selected variant, contrast and reflow state.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#cards).\n\n#### Contract\n\nAn `<article>` is an elevated card. Any heading is the headline, `<p class=\"subhead\">` is the optional subhead, a `<p>` is supporting copy, direct `<img>`, `<picture>`, or `<figure>` is media, and direct `<div class=\"actions\">` is the action row. Include only the slots the content needs. There is no `card-content`, `card-title`, `card-action`, `card` or `card-panel` class—the element is the component, and the action row is not a `<nav>`. Tokens follow the [M3 card spec](https://m3.material.io/components/cards/specs): all variants use 12dp corners; elevated rests at level 1, filled and outlined at level 0. A directly actionable card wraps its primary content in one direct `a.primary-action[href]` and has no second link or control. Horizontal cards preserve that source order, use content height with a 240px minimum unless a size helper fixes the expanded height, and stack below 600px with fixed heights reset to content. During reordering, `.dragged` or `.picked-up` preserves the 16% state layer and dragged elevation while the primary action remains hovered or pressed.\n\nA Card reveal is a disclosure with exactly one identified direct `<aside>` and an enabled, accessibly named `button.card-reveal-trigger[type=\"button\"]` outside that panel. Its `aria-controls` must resolve to the panel, and its closest `<article>` owns it—even when a complete nested card sits inside an outer reveal panel. After accepting the contract, Cards writes `aria-expanded`, marks the closed panel `inert`, closes it with Escape, and returns focus from the panel to the trigger. Rejected, disabled-only, unidentified, and multi-panel disclosures remain visible and unmanaged.\n\n#### Syntax\n\n```html\n<article>\n <header>\n <h3>Weekend in the mountains</h3>\n <p class=\"subhead\">Three-day itinerary</p>\n </header>\n <p>Explore trails and overlooks.</p>\n <img src=\"images/mountains.jpg\" alt=\"Mountain valley beneath a cloudy sky\">\n <div class=\"actions\">\n <button type=\"button\" class=\"text\">Share</button>\n <button type=\"button\" class=\"tonal\">View trip</button>\n </div>\n</article>\n```\n\n#### Also documented\n\nThe Cards section of the [API reference](https://www.expressivecss.com/llm.md) also covers Variants, Primary action and states, Collections, Media, Horizontal, Reveal, Expanding card, Sizes.\n\n#### Rules\n\n- `card-action-row-not-nav`: Cards do not contain navigation or tabs. Put navigation outside the card and use <div class=\"actions\"> for its action buttons.\n- `card-primary-action-is-control`: A directly actionable card uses one native link as its primary action. Use the action row for commands; common button styles do not turn a button into the card container.\n- `card-disabled-link-has-no-href`: A disabled card destination must not retain href. aria-disabled does not stop a link from navigating when activated from the keyboard.\n- `card-disabled-link-not-tabbable`: A disabled card destination is not operable, so remove it from the tab order with tabindex=\"-1\".\n- `card-primary-action-is-only-action`: A directly actionable card contains no second action. Use one primary-action link, or make the card non-actionable and put its controls in the action row.\n- `card-primary-action-is-single`: A directly actionable card has exactly one direct primary-action link. Remove duplicate primary actions or use a non-actionable card with an action row.\n- `card-reveal-has-trigger`: A card with a direct reveal panel requires a native card-reveal-trigger button whose closest article is that card and which sits outside the reveal panel.\n- `card-reveal-has-one-panel`: A card reveal has exactly one direct aside panel. With multiple direct panels, Cards rejects disclosure setup and every panel remains visible and unmanaged.\n- `card-reveal-trigger-is-button`: The reveal trigger is a native button so Enter, Space, disabled state, and focus work without synthetic keyboard handling.\n- `card-reveal-trigger-type-button`: A card reveal button may appear inside a form, so set type=\"button\" to prevent accidental submission.\n- `card-reveal-trigger-has-label`: The reveal button needs a non-empty accessible name describing the details it toggles; icon-only triggers should use aria-label.\n- `card-reveal-trigger-controls-panel`: Name the reveal panel from aria-controls so assistive technology can relate the disclosure button to its content.\n- `card-reveal-panel-has-id`: The reveal panel needs the id referenced by its trigger's aria-controls.\n- `card-reveal-expanded-is-not-authored`: Cards writes aria-expanded on the disclosure button. Do not author a stale initial value, and do not put aria-expanded on the panel.\n- `expanding-card-trigger-is-button`: The media-sized trigger opens a dialog, so use a button for native keyboard and disabled behavior.\n- `expanding-card-close-is-button`: The back affordance closes the expanded surface, so use a button for native keyboard behavior.\n"
64
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Cards\nMaterial Design 3 cards, from the HTML.\n\nComponent ID: `cards`\n\n[Component documentation](https://www.expressivecss.com/cards.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/cards.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/cards/overview)\n\nExample: A hundred compact activity records need a list. Giving every row a card adds separation without a separate content or action group.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/cards/overview).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/cards/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/cards/guidelines).\n\nSupport (2026-09-13, `llm.md#cards`): Elevated, filled, outlined, horizontal, and directly actionable cards.\n\nWeb adaptation: Native article anatomy; primary-action links preserve navigation semantics.\n\nKnown boundary: Existing card lifecycle and geometry evidence does not cover every selected variant, contrast and reflow state.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#cards).\n\n#### Contract\n\nAn `<article>` is an elevated card. Any heading is the headline, `<p class=\"subhead\">` is the optional subhead, a `<p>` is supporting copy, direct `<img>`, `<picture>`, or `<figure>` is media, and direct `<div class=\"actions\">` is the action row. Include only the slots the content needs. There is no `card-content`, `card-title`, `card-action`, `card` or `card-panel` class—the element is the component, and the action row is not a `<nav>`. Tokens follow the [M3 card spec](https://m3.material.io/components/cards/specs): all variants use 12dp corners; elevated rests at level 1, filled and outlined at level 0. A directly actionable card wraps its primary content in one direct `a.primary-action[href]` and has no second link or control. Horizontal cards preserve that source order, use content height with a 240px minimum unless a size helper fixes the expanded height, and stack below 600px, or in a `.query-container` narrower than 600px, with fixed heights reset to content. During reordering, `.dragged` or `.picked-up` preserves the 16% state layer and dragged elevation while the primary action remains hovered or pressed.\n\nA Card reveal is a disclosure with exactly one identified direct `<aside>` and an enabled, accessibly named `button.card-reveal-trigger[type=\"button\"]` outside that panel. Its `aria-controls` must resolve to the panel, and its closest `<article>` owns it—even when a complete nested card sits inside an outer reveal panel. After accepting the contract, Cards writes `aria-expanded`, marks the closed panel `inert`, closes it with Escape, and returns focus from the panel to the trigger. Rejected, disabled-only, unidentified, and multi-panel disclosures remain visible and unmanaged.\n\n#### Syntax\n\n```html\n<article>\n <header>\n <h3>Weekend in the mountains</h3>\n <p class=\"subhead\">Three-day itinerary</p>\n </header>\n <p>Explore trails and overlooks.</p>\n <img src=\"images/mountains.jpg\" alt=\"Mountain valley beneath a cloudy sky\">\n <div class=\"actions\">\n <button type=\"button\" class=\"text\">Share</button>\n <button type=\"button\" class=\"tonal\">View trip</button>\n </div>\n</article>\n```\n\n#### Also documented\n\nThe Cards section of the [API reference](https://www.expressivecss.com/llm.md) also covers Variants, Primary action and states, Collections, Media, Horizontal, Reveal, Expanding card, Sizes.\n\n#### Rules\n\n- `card-action-row-not-nav`: Cards do not contain navigation or tabs. Put navigation outside the card and use <div class=\"actions\"> for its action buttons.\n- `card-primary-action-is-control`: A directly actionable card uses one native link as its primary action. Use the action row for commands; common button styles do not turn a button into the card container.\n- `card-disabled-link-has-no-href`: A disabled card destination must not retain href. aria-disabled does not stop a link from navigating when activated from the keyboard.\n- `card-disabled-link-not-tabbable`: A disabled card destination is not operable, so remove it from the tab order with tabindex=\"-1\".\n- `card-primary-action-is-only-action`: A directly actionable card contains no second action. Use one primary-action link, or make the card non-actionable and put its controls in the action row.\n- `card-primary-action-is-single`: A directly actionable card has exactly one direct primary-action link. Remove duplicate primary actions or use a non-actionable card with an action row.\n- `card-reveal-has-trigger`: A card with a direct reveal panel requires a native card-reveal-trigger button whose closest article is that card and which sits outside the reveal panel.\n- `card-reveal-has-one-panel`: A card reveal has exactly one direct aside panel. With multiple direct panels, Cards rejects disclosure setup and every panel remains visible and unmanaged.\n- `card-reveal-trigger-is-button`: The reveal trigger is a native button so Enter, Space, disabled state, and focus work without synthetic keyboard handling.\n- `card-reveal-trigger-type-button`: A card reveal button may appear inside a form, so set type=\"button\" to prevent accidental submission.\n- `card-reveal-trigger-has-label`: The reveal button needs a non-empty accessible name describing the details it toggles; icon-only triggers should use aria-label.\n- `card-reveal-trigger-controls-panel`: Name the reveal panel from aria-controls so assistive technology can relate the disclosure button to its content.\n- `card-reveal-panel-has-id`: The reveal panel needs the id referenced by its trigger's aria-controls.\n- `card-reveal-expanded-is-not-authored`: Cards writes aria-expanded on the disclosure button. Do not author a stale initial value, and do not put aria-expanded on the panel.\n- `expanding-card-trigger-is-button`: The media-sized trigger opens a dialog, so use a button for native keyboard and disabled behavior.\n- `expanding-card-close-is-button`: The back affordance closes the expanded surface, so use a button for native keyboard behavior.\n"
53
65
  },
54
66
  {
55
67
  "file": "carousel.md",
56
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Carousel\nMaterial 3 adaptive carousels for visual collections.\n\nComponent ID: `carousel`\n\n[Component documentation](https://www.expressivecss.com/carousel.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/carousel.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/carousel/overview)\n\nExample: A transaction history must support scanning rows and values. Use a list instead of hiding records across carousel slides.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/carousel/overview).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/carousel/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/carousel/guidelines).\n\nSupport (2026-09-17, `llm.md#carousel`): Adaptive visual collections with documented layout variants.\n\nWeb adaptation: Runtime owns sizing and navigation; reduced motion removes parallax and morphing.\n\nKnown boundary: Rendered navigation and application-owned pause controls have browser coverage; screen readers, native zoom and physical-device review remain pending.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#carousel).\n\n#### Contract\n\nA Material 3 adaptive carousel for visual collections.\n\nThe default is multi-browse: one large, one medium, and one small item adapt as the active item changes. `AutoInit()` starts every `.carousel` except those marked `no-autoinit`. Give the container an accessible name and every direct item the `carousel-item` class.\n\n#### Syntax\n\n```html\n<div class=\"carousel\" aria-label=\"Featured landscapes\">\n <a class=\"carousel-item\" href=\"mountain-lake.html\">\n <img src=\"images/mountain-lake.jpg\" alt=\"Mountain lake\">\n <span class=\"carousel-item-content\">Mountain lake</span>\n </a>\n <a class=\"carousel-item\" href=\"forest-path.html\">\n <img src=\"images/forest-path.jpg\" alt=\"Forest path\">\n <span class=\"carousel-item-content\">Forest path</span>\n </a>\n <a class=\"carousel-item\" href=\"rocky-coastline.html\">\n <img src=\"images/rocky-coastline.jpg\" alt=\"Rocky coastline\">\n <span class=\"carousel-item-content\">Rocky coastline</span>\n </a>\n</div>\n<div class=\"mt-1\"><a class=\"button text\" href=\"all-landscapes.html\">Show all</a></div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `duration` | Number | `200` | Milliseconds allowed for a programmatic scroll to land, and the part of each auto-advance rest that is not `interval`. |\n| `fullWidth` | Boolean | `false` | Full-width compatibility layout used by swipeable tabs. Prefer an M3 layout class for new carousels. |\n| `indicators` | Boolean | `false` | Legacy paging dots. M3 recommends a nearby Show all path instead of overlay controls. |\n| `noWrap` | Boolean | `false` | Stop auto-advance at the last item instead of looping back to the first. Arrow keys and `set()` stop at the ends either way — a scroll track has ends. |\n| `interval` | Number | `0` | Milliseconds to rest between automatic advances, on top of `duration`; a full cycle takes `duration + interval`. `0` leaves auto-advance off. Each rest is armed by the move before it rather than on a fixed phase. |\n| `height` | Number | `null` | Fixed track height in pixels. `null` sizes the carousel from its content. |\n| `onCycleTo` | Function | `null` | Called when the active item changes. |\n| `i18n` | Object | `{ carousel: 'Carousel', item: 'Item', of: 'of', indicators: 'Slides', slide: 'Slide' }` | Generated accessible label strings. `indicators` names the indicator row and `slide` prefixes each dot, giving \"Slide 1\". Partial objects are merged with the defaults. |\n\n#### Methods\n\n`next()` and `prev()` move one item or an optional item count. `set(index, callback)` moves to a zero-based item index. `pause()` and `start()` stop and resume auto-advance, and do nothing without an `interval`. `destroy()` removes generated labels, size roles, indicators, listeners, the scroll-track wrapper, and the auto-advance timer.\n\nAn `interval` makes the carousel advance on its own, so the pause contract is mandatory: it always pauses on hover, on focus within, and while the tab is hidden, and `prefers-reduced-motion: reduce` suppresses auto-advance entirely. No option disables any of that. An explicit `noWrap: true` stops auto-advance after one pass instead of looping. Arrow keys and `set()` stop at the ends either way — a scroll track has ends, and auto-advance is the one caller that can loop back past them. A `height` gives the indicators their own row below the track (`.fixed-height`) instead of laying them over the media; markup can do the same by carrying `.fixed-height` and setting `--carousel-height`.\n\n#### Also documented\n\nThe Carousel section of the [API reference](https://www.expressivecss.com/llm.md) also covers Layouts, Tokens, Initialization, Properties.\n\n#### Rules\n\n- `hidden-subtree-holds-nothing-focusable`: Hidden from assistive technology but still in the tab order: focus lands on a control the user has no way to perceive. Take away the tab stop (tabindex=\"-1\"), or mark the subtree inert when it is genuinely not displayed - inert also removes it from hit-testing, which is wrong for anything still visible.\n- `carousel-not-a-composite-widget`: A carousel takes no composite role; it is a group of slides with independent controls.\n- `carousel-indicators-are-not-tabs`: Carousel indicators are independent controls, not tabs.\n"
68
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Carousel\nMaterial 3 adaptive carousels for visual collections.\n\nComponent ID: `carousel`\n\n[Component documentation](https://www.expressivecss.com/carousel.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/carousel.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/carousel/overview)\n\nExample: A transaction history must support scanning rows and values. Use a list instead of hiding records across carousel slides.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/carousel/overview).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/carousel/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/carousel/guidelines).\n\nSupport (2026-09-17, `llm.md#carousel`): Adaptive visual collections with documented layout variants.\n\nWeb adaptation: Runtime owns sizing and navigation; reduced motion removes parallax and morphing.\n\nKnown boundary: Rendered navigation and application-owned pause controls have browser coverage; screen readers, native zoom and physical-device review remain pending.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#carousel).\n\n#### Contract\n\nA Material 3 adaptive carousel for visual collections.\n\nThe default is multi-browse: one large, one medium, and one small item adapt as the active item changes. `AutoInit()` starts every `.carousel` except those marked `no-autoinit`. Give the container an accessible name and every direct item the `carousel-item` class.\n\n#### Syntax\n\n```html\n<div class=\"carousel\" aria-label=\"Featured landscapes\">\n <a class=\"carousel-item\" href=\"mountain-lake.html\">\n <img src=\"images/mountain-lake.jpg\" alt=\"Mountain lake\">\n <span class=\"carousel-item-content\">Mountain lake</span>\n </a>\n <a class=\"carousel-item\" href=\"forest-path.html\">\n <img src=\"images/forest-path.jpg\" alt=\"Forest path\">\n <span class=\"carousel-item-content\">Forest path</span>\n </a>\n <a class=\"carousel-item\" href=\"rocky-coastline.html\">\n <img src=\"images/rocky-coastline.jpg\" alt=\"Rocky coastline\">\n <span class=\"carousel-item-content\">Rocky coastline</span>\n </a>\n</div>\n<div class=\"mt-1\"><a class=\"button text\" href=\"all-landscapes.html\">Show all</a></div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `duration` | Number | `200` | Milliseconds allowed for a programmatic scroll to land, and the part of each auto-advance rest that is not `interval`. |\n| `indicators` | Boolean | `false` | Legacy paging dots. M3 recommends a nearby Show all path instead of overlay controls. |\n| `noWrap` | Boolean | `false` | Stop auto-advance at the last item instead of looping back to the first. Arrow keys and `set()` stop at the ends either way — a scroll track has ends. |\n| `interval` | Number | `0` | Milliseconds to rest between automatic advances, on top of `duration`; a full cycle takes `duration + interval`. `0` leaves auto-advance off. Each rest is armed by the move before it rather than on a fixed phase. |\n| `height` | Number | `null` | Fixed track height in pixels. `null` sizes the carousel from its content. |\n| `onCycleTo` | Function | `null` | Called when the active item changes. |\n| `i18n` | Object | `{ carousel: 'Carousel', item: 'Item', of: 'of', indicators: 'Slides', slide: 'Slide' }` | Generated accessible label strings. `indicators` names the indicator row and `slide` prefixes each dot, giving \"Slide 1\". Partial objects are merged with the defaults. |\n\n#### Methods\n\n`next()` and `prev()` move one item or an optional item count. `set(index, callback)` moves to a zero-based item index. `pause()` and `start()` stop and resume auto-advance, and do nothing without an `interval`. `destroy()` removes generated labels, size roles, indicators, listeners, the scroll-track wrapper, and the auto-advance timer.\n\nAn `interval` makes the carousel advance on its own, so the pause contract is mandatory: it always pauses on hover, on focus within, and while the tab is hidden, and `prefers-reduced-motion: reduce` suppresses auto-advance entirely. No option disables any of that. An explicit `noWrap: true` stops auto-advance after one pass instead of looping. Arrow keys and `set()` stop at the ends either way — a scroll track has ends, and auto-advance is the one caller that can loop back past them. A `height` gives the indicators their own row below the track (`.fixed-height`) instead of laying them over the media; markup can do the same by carrying `.fixed-height` and setting `--carousel-height`.\n\n#### Also documented\n\nThe Carousel section of the [API reference](https://www.expressivecss.com/llm.md) also covers Layouts, Tokens, Initialization, Properties.\n\n#### Rules\n\n- `hidden-subtree-holds-nothing-focusable`: Hidden from assistive technology but still in the tab order: focus lands on a control the user has no way to perceive. Take away the tab stop (tabindex=\"-1\"), or mark the subtree inert when it is genuinely not displayed - inert also removes it from hit-testing, which is wrong for anything still visible.\n- `carousel-not-a-composite-widget`: A carousel takes no composite role; it is a group of slides with independent controls.\n- `carousel-indicators-are-not-tabs`: Carousel indicators are independent controls, not tabs.\n"
69
+ },
70
+ {
71
+ "file": "cascader.md",
72
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Cascader\nPick a path through nested options shown in columns, and submit the whole path.\n\nComponent ID: `cascader`\n\n[Component documentation](https://www.expressivecss.com/cascader.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/cascader.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A shipping form asks for country, region and city in one field: a .cascader with three levels submits place=us, place=ca and place=sf; a single country choice stays a select.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-25); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-25, `llm.md#cascader`): Nested lists with data-value read into listbox columns in a popover anchored to a read-only combobox input; click and keyboard navigation with arrows, Home, End, Enter, Space, Escape and Tab, swapped under rtl; leaf-only commits shown as joined labels with input and change events; one form entry per level through a form-associated control; data-selected starting path, reset, required with i18n.required, aria-invalid and disabled state.\n\nWeb adaptation: The author's nested lists stay hidden as the source. A manual popover opened with the input as its source uses CSS anchor positioning, and a form-associated expressive-cascader-control wraps the input and submits the path, so the browser handles reset, validity and disabled state.\n\nKnown boundary: Clicks and keys through three columns, rtl, roles, names and states, form data, reset, required, disabled state and destroy() are checked in Chromium, Firefox and WebKit, and parsing in jsdom; spoken output remains unverified. There is no search, type-ahead, multiple selection or lazy loading, and only a leaf commits.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#cascader).\n\n#### Contract\n\nPick a path through nested options shown in columns, where each choice opens the next column, and submit the whole path.\n\nWrite a `.field.cascader` holding a text `<input>` with a `name`, its `<label for>`, and a `ul.cascader-options` of nested lists. Each `<li>` holds a `data-value`, and its label is its own text outside any nested list. An `<li>` with a nested list of options is a branch; any other is a leaf. `AutoInit()` starts every `.cascader`. The lists stay in the page, hidden, and the script builds the columns from them; leave roles off them.\n\nThe input becomes read-only, with `role=\"combobox\"` and `aria-haspopup=\"dialog\"`. A click, or Enter, Space, ArrowDown or Alt+ArrowDown, opens a popover below the field with `role=\"dialog\"`. Each column is a listbox named by the field's label or by the option that opened it, and the options on the open path are `aria-selected=\"true\"`. A branch ends in a chevron.\n\n#### Syntax\n\n```html\n<div class=\"field cascader\">\n <input id=\"place\" name=\"place\" type=\"text\" required>\n <label for=\"place\">Place</label>\n <ul class=\"cascader-options\">\n <li data-value=\"us\">United States\n <ul>\n <li data-value=\"ca\">California\n <ul>\n <li data-value=\"sf\">San Francisco</li>\n <li data-value=\"la\">Los Angeles</li>\n </ul>\n </li>\n <li data-value=\"wa\">Washington</li>\n </ul>\n </li>\n <li data-value=\"cn\">Canada</li>\n </ul>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `i18n` | Object | `{ required: 'Choose an option.' }` | `required` is the validity message while a required field has no path. Markup sets it with `data-cascader-options='{\"i18n\":{\"required\":\"Elige un lugar.\"}}'` on the `.cascader`. |\n\n#### Methods\n\n- `.open()`: Opens the popover at the committed path, with focus on its leaf or on the first option. A disabled field does not open.\n- `.close()`: Closes the popover and keeps the committed path.\n- `.destroy()`: Closes and removes the popover, unwraps the input, and gives back its name, `required` and attributes. The input keeps its text.\n\n#### Also documented\n\nThe Cascader section of the [API reference](https://www.expressivecss.com/llm.md) also covers Properties.\n\n#### Rules\n\n- `cascader-lists-take-no-roles`: Leave roles off .cascader-options and its items. The lists are the source the script reads; it builds the listbox columns, with their roles and states, itself.\n- `cascader-option-has-value`: Give every option in .cascader-options a data-value. The form submits one value per level of the chosen path, and an option without one submits an empty string.\n- `cascader-needs-a-text-field`: A .cascader shows the chosen path in an <input> of type text, which the script makes read-only. Other input types reject the path's labels.\n- `cascader-input-is-named`: Name the cascader's input with a <label for> in the field, or aria-label. The popover and its first column take their name from the same label.\n"
57
73
  },
58
74
  {
59
75
  "file": "checkboxes.md",
60
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Checkboxes\nMaterial Design 3 checkboxes, from the HTML.\n\nComponent ID: `checkboxes`\n\n[Component documentation](https://www.expressivecss.com/checkboxes.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/checkboxes.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/checkbox/overview)\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/checkbox/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/checkbox/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/checkbox/guidelines).\n\nSupport (2026-09-13, `llm.md#checkboxes`): Unchecked, checked, indeterminate, and disabled.\n\nWeb adaptation: Native checkbox; indeterminate is set through the DOM property.\n\nKnown boundary: Native state coverage does not verify parent aggregation or every target and forced-colors state.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#checkboxes).\n\n#### Contract\n\nA `<label>` wrapping `<input type=\"checkbox\">` is the control. The label text is a sibling of the input — no extra class, no required `<span>`. They are CSS only. There is no JavaScript component and nothing to AutoInit.\n\nTokens follow the [M3 checkbox spec](https://m3.material.io/components/checkbox/specs). The container is 18dp with 2dp corners. Unselected is a 2dp `on-surface-variant` outline. Selected is a `primary` fill with an `on-primary` check. Indeterminate is the same fill with a dash. The state layer is 40dp at 8% hover and 10% focus. The touch target is 48dp. The label is `body-large` / `on-surface`. Disabled is 38%.\n\nPut the input first. Add `checked` or `disabled` on the input. Indeterminate is not an HTML attribute — set `element.indeterminate = true` in script. `filled-in` is a no-op: the selected state is always the filled box.\n\n#### Syntax\n\n```html\n<label>\n <input type=\"checkbox\">\n Red\n</label>\n<label>\n <input type=\"checkbox\" checked>\n Yellow\n</label>\n<label>\n <input type=\"checkbox\" disabled>\n Brown\n</label>\n```\n\n#### Also documented\n\nThe Checkboxes section of the [API reference](https://www.expressivecss.com/llm.md) also covers In a row, Error.\n\n#### Rules\n\n- `checkbox-labelled`: A checkbox must be inside its <label> or carry an id a label points at.\n"
76
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Checkboxes\nMaterial Design 3 checkboxes, from the HTML.\n\nComponent ID: `checkboxes`\n\n[Component documentation](https://www.expressivecss.com/checkboxes.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/checkboxes.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/checkbox/overview)\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/checkbox/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/checkbox/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/checkbox/guidelines).\n\nSupport (2026-09-13, `llm.md#checkboxes`): Unchecked, checked, indeterminate, and disabled.\n\nWeb adaptation: Native checkbox; indeterminate is set through the DOM property.\n\nKnown boundary: Native state coverage does not verify parent aggregation or every target and forced-colors state.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#checkboxes).\n\n#### Contract\n\nA `<label>` wrapping `<input type=\"checkbox\">` is the control. The label text is a sibling of the input — no extra class, no required `<span>`. They are CSS only. There is no JavaScript component and nothing to AutoInit.\n\nTokens follow the [M3 checkbox spec](https://m3.material.io/components/checkbox/specs). The container is 18dp with 2dp corners. Unselected is a 2dp `on-surface-variant` outline. Selected is a `primary` fill with an `on-primary` check. Indeterminate is the same fill with a dash. The state layer is 40dp at 8% hover and 10% focus. The touch target is 48dp. The label is `body-large` / `on-surface`. Disabled is 38%.\n\nPut the input first. Add `checked` or `disabled` on the input. Indeterminate is not an HTML attribute — set `element.indeterminate = true` in script. `filled-in` is a no-op: the selected state is always the filled box.\n\n#### Syntax\n\n```html\n<label>\n <input type=\"checkbox\">\n Red\n</label>\n<label>\n <input type=\"checkbox\" checked>\n Yellow\n</label>\n<label>\n <input type=\"checkbox\" disabled>\n Brown\n</label>\n```\n\n#### Also documented\n\nThe Checkboxes section of the [API reference](https://www.expressivecss.com/llm.md) also covers In a row, Error.\n\n#### Rules\n\n- `checkbox-labelled`: A checkbox must be inside its <label> or carry an id a label points at.\n"
61
77
  },
62
78
  {
63
79
  "file": "chips.md",
64
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Chips\nSmall blocks for contacts, tags, and filters.\n\nComponent ID: `chips`\n\n[Component documentation](https://www.expressivecss.com/chips.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/chips.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/chips/overview)\n\nExample: A passive unread count is a badge. Turning it into a filter chip falsely suggests a selectable state unless filtering is implemented.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/chips/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/chips/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/chips/guidelines).\n\nSupport (2026-09-13, `llm.md#chips`): Assist, suggestion, filter, input, and display chips.\n\nWeb adaptation: Element choice defines behavior; display chips are a framework extension.\n\nKnown boundary: Rendered deletion, focus recovery and wrapped action/delete controls are checked. Full effective-target sizing, spoken output and physical-device behavior remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#chips).\n\n#### Contract\n\nA chip is a `.chip`, and **the element says which kind it is** — the four Material 3 chip types, plus a non-interactive display chip, across three root elements. Add `outlined` for a bordered style. Static chips are CSS. The JavaScript plugin lives on a `.chips` wrapper.\n\nA long label wraps and the chip grows from its 32dp minimum, with 4dp above and below the lines. It never grows wider than its parent.\n\n#### Syntax\n\n```html\n<span class=\"chip outlined\">Information</span>\n\n<span class=\"chip\">\n <img src=\"photo.jpg\" alt=\"\"> Jane Doe\n</span>\n\n<button type=\"button\" class=\"chip\">\n <span class=\"material-symbols\" aria-hidden=\"true\">event</span>\n Add to calendar\n</button>\n\n<input type=\"checkbox\" class=\"chip-input\" id=\"filter-flights\">\n<label class=\"chip\" for=\"filter-flights\">\n <span class=\"material-symbols\" aria-hidden=\"true\">check</span>\n Flights\n</label>\n\n<span class=\"chip\">\n Tag\n <button type=\"button\" class=\"close\" aria-label=\"Remove Tag\">\n <span class=\"material-symbols\" aria-hidden=\"true\">close</span>\n </button>\n</span>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `data` | Array | `[]` | Initial chips. Each item is a chip data object. |\n| `placeholder` | String | `''` | Placeholder when there are no chips. Requires `allowUserInput`. |\n| `secondaryPlaceholder` | String | `''` | Placeholder after at least one chip exists. |\n| `closeIconClass` | String | `'material-symbols'` | Class on the icon inside the delete button. |\n| `allowUserInput` | Boolean | `false` | If true, render a text field and a delete button per chip, so the user can add and remove chips. |\n| `i18n` | Object | `{ remove: 'Remove' }` | Strings the component generates. `remove` prefixes the delete button's accessible name, giving \"Remove Apple\". |\n| `autocompleteOptions` | Object | `{}` | Options passed to Autocomplete on the input. A non-empty object enables autocomplete. |\n| `autocompleteOnly` | Boolean | `false` | If true, Enter will not add a value that is not in the autocomplete list. |\n| `limit` | Number | `Infinity` | Maximum number of chips. |\n| `onChipAdd` | Function | `null` | Called after a chip is added. Receives the container and the chip element. |\n| `onChipSelect` | Function | `null` | Called when a chip is selected. Receives the container and the chip element. |\n| `onChipDelete` | Function | `null` | Called after a chip is deleted. Receives the container and the chip element. |\n\n#### Methods\n\n- `.addChip()`: Add a chip, including when `allowUserInput` is false. Numeric id `0` is valid. Ignored if `id` is missing, already present, or the limit is reached.\n- `.deleteChip()`: Delete the chip at this index.\n- `.selectChip()`: Focus the chip at this index.\n- `.getData()`: The current chips as an array of chip data objects.\n- `.destroy()`: Destroy the plugin instance, remove rendered chips, and tear down its event handlers. This also destroys the owned Autocomplete and its Menu.\n\n#### Also documented\n\nThe Chips section of the [API reference](https://www.expressivecss.com/llm.md) also covers Contacts, Tags, Javascript Plugin, Initialization, Properties.\n\n#### Rules\n\n- `chip-root-element`: A chip is <span> (display), <button> (assist/suggestion/input), or <label> (filter). Never <div>.\n- `chip-not-focusable-span`: A display chip is not a control and must not be in the tab order. Put the action on a nested <button>.\n- `chip-close-is-button`: The chip delete affordance is a control: use <button type=\"button\" class=\"close\">, not <i> or <span>.\n- `chip-close-type-button`: A <button> inside a form defaults to type=submit. Chip delete buttons must set type=\"button\".\n- `chip-close-has-label`: The delete button contains only an icon, so it needs an accessible name naming the chip it removes.\n- `chip-no-i-element`: <i> means idiomatic text, not icon. Use <span class=\"material-symbols\" aria-hidden=\"true\">.\n- `chip-icon-hidden`: A ligature icon is read aloud verbatim. Decorative icons inside a labelled control must be aria-hidden.\n- `filter-chip-label-for`: A filter chip is <input type=\"checkbox\"> + <label class=\"chip\">; the label must point at its input.\n"
80
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Chips\nSmall blocks for contacts, tags, and filters.\n\nComponent ID: `chips`\n\n[Component documentation](https://www.expressivecss.com/chips.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/chips.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/chips/overview)\n\nExample: A passive unread count is a badge. Turning it into a filter chip falsely suggests a selectable state unless filtering is implemented.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/chips/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/chips/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/chips/guidelines).\n\nSupport (2026-09-13, `llm.md#chips`): Assist, suggestion, filter, input, and display chips.\n\nWeb adaptation: Element choice defines behavior; display chips are a framework extension.\n\nKnown boundary: Rendered deletion, focus recovery and wrapped action/delete controls are checked. Full effective-target sizing, spoken output and physical-device behavior remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#chips).\n\n#### Contract\n\nA chip is a `.chip`, and **the element says which kind it is** — the four Material 3 chip types, plus a non-interactive display chip, across three root elements. Add `outlined` for a bordered style. Static chips are CSS. The JavaScript plugin lives on a `.chips` wrapper.\n\n`--md-comp-chip-padding`, `--md-comp-chip-label-size`, and `--md-comp-chip-icon-size` control horizontal padding, label font size, and direct child Material Symbol font size. Each takes priority over its old counterpart, respectively `--padding`, `--font-size`, and `--font-size-icon`. Old overrides on a chip remain supported as fallbacks, including fixed px values. The old names are deprecated with no removal date. These properties leave block padding, avatars, close buttons, the nested close glyph, and editable input sizes unchanged.\n\nThe label defaults to `--md-sys-typescale-label-large-font-size`. The chip minimum height, direct child icon font size, avatar and close-button boxes, and editable input font size, height and width limits use rem. At a 16px root the default label is 14px, icon is 18px and minimum height is 32px; at a 32px root they are 28px, 36px and 64px. The avatar and close-button boxes grow from 24px to 48px. Padding, gaps and borders stay in px. The nested close glyph keeps the shared button icon size. A long label wraps and the chip grows from its minimum height, with 4px above and below the lines. It never grows wider than its parent.\n\n#### Syntax\n\n```html\n<span class=\"chip outlined\">Information</span>\n\n<span class=\"chip\">\n <img src=\"photo.jpg\" alt=\"\"> Jane Doe\n</span>\n\n<button type=\"button\" class=\"chip\">\n <span class=\"material-symbols\" aria-hidden=\"true\">event</span>\n Add to calendar\n</button>\n\n<input type=\"checkbox\" class=\"chip-input\" id=\"filter-flights\">\n<label class=\"chip\" for=\"filter-flights\">\n <span class=\"material-symbols\" aria-hidden=\"true\">check</span>\n Flights\n</label>\n\n<span class=\"chip\">\n Tag\n <button type=\"button\" class=\"close\" aria-label=\"Remove Tag\">\n <span class=\"material-symbols\" aria-hidden=\"true\">close</span>\n </button>\n</span>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `data` | Array | `[]` | Initial chips. Each item is a chip data object. |\n| `placeholder` | String | `''` | Placeholder when there are no chips. Requires `allowUserInput`. |\n| `secondaryPlaceholder` | String | `''` | Placeholder after at least one chip exists. |\n| `closeIconClass` | String | `'material-symbols'` | Class on the icon inside the delete button. |\n| `allowUserInput` | Boolean | `false` | If true, render a text field and a delete button per chip, so the user can add and remove chips. |\n| `i18n` | Object | `{ remove: 'Remove', required: 'Add at least one item.' }` | Strings the component generates. `remove` prefixes the delete button's accessible name, giving \"Remove Apple\". `required` is what a required field says while it has no chips. |\n| `autocompleteOptions` | Object | `{}` | Options passed to Autocomplete on the input. A non-empty object enables autocomplete. |\n| `autocompleteOnly` | Boolean | `false` | If true, Enter will not add a value that is not in the autocomplete list. An `Add` row from `allowCreate` still adds its text. |\n| `limit` | Number | `Infinity` | Maximum number of chips. |\n| `onChipAdd` | Function | `null` | Called after a chip is added. Receives the container and the chip element. |\n| `onChipSelect` | Function | `null` | Called when a chip is selected. Receives the container and the chip element. |\n| `onChipDelete` | Function | `null` | Called after a chip is deleted. Receives the container and the chip element. |\n\n#### Methods\n\n- `.addChip()`: Add a chip, including when `allowUserInput` is false. Numeric id `0` is valid. Ignored if `id` is missing, already present, or the limit is reached.\n- `.deleteChip()`: Delete the chip at this index.\n- `.selectChip()`: Focus the chip at this index.\n- `.getData()`: The current chips as an array of chip data objects.\n- `.destroy()`: Destroy the plugin instance, remove rendered chips, and tear down its event handlers. This also destroys the owned Autocomplete and its Menu.\n\n#### Also documented\n\nThe Chips section of the [API reference](https://www.expressivecss.com/llm.md) also covers Contacts, Tags, JavaScript plugin, Initialization, Properties.\n\n#### Rules\n\n- `chip-root-element`: A chip is <span> (display), <button> (assist/suggestion/input), or <label> (filter). Never <div>.\n- `chip-not-focusable-span`: A display chip is not a control and must not be in the tab order. Put the action on a nested <button>.\n- `chip-close-is-button`: The chip delete affordance is a control: use <button type=\"button\" class=\"close\">, not <i> or <span>.\n- `chip-close-type-button`: A <button> inside a form defaults to type=submit. Chip delete buttons must set type=\"button\".\n- `chip-close-has-label`: The delete button contains only an icon, so it needs an accessible name naming the chip it removes.\n- `chip-no-i-element`: <i> means idiomatic text, not icon. Use <span class=\"material-symbols\" aria-hidden=\"true\">.\n- `chip-icon-hidden`: A ligature icon is read aloud verbatim. Decorative icons inside a labelled control must be aria-hidden.\n- `filter-chip-label-for`: A filter chip is <input type=\"checkbox\"> + <label class=\"chip\">; the label must point at its input.\n"
81
+ },
82
+ {
83
+ "file": "color-picker.md",
84
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Color picker\nChoose any color with a saturation and brightness area, a hue slider and a hex field, over a native color input.\n\nComponent ID: `color-picker`\n\n[Component documentation](https://www.expressivecss.com/color-picker.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/color-picker.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A theme builder where any brand color is allowed is a color picker. Eight calendar label colors are color swatches.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-18); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-18, `llm.md#color-picker`): A saturation and brightness area as a keyboard slider, a hue range on the framework slider, a hex field and the native color input as form value and preview, with input, change and onChange events.\n\nWeb adaptation: Progressive enhancement of <input type=\"color\">: without JavaScript the native picker remains, and the browser picker still opens from the input after enhancement.\n\nKnown boundary: Arrow keys, Shift steps, pointer press and drag with clamping, the unmirrored area in RTL, the hue range, the hex field with Enter and invalid values, the value property, native input changes, the form value and destroy are checked in Chromium, Firefox and WebKit; spoken output, forced colors and touch remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#color-picker).\n\n#### Contract\n\nWrite a `.color-picker` with `role=\"group\"` and a name around an `<input type=\"color\">`. Without JavaScript that is the native swatch and the browser's picker. `ColorPicker`, which AutoInit starts, adds three controls around the input: a saturation and brightness area, a hue slider and a hex field. The native input stays the form value and the preview, and the browser's picker still opens from it. Every change writes the input and fires its `input` event, and a committed change (a released drag, a key press, a released hue, an applied hex value) also fires `change`, so existing listeners keep working.\n\nThe area is a `role=\"slider\"` with `aria-valuetext` such as \"Saturation 60%, brightness 80%\". Left and Right change saturation, Up and Down brightness, Shift takes steps of 10%, Page Up and Page Down change brightness by 10%, and Home and End go to no and full saturation. The area and the hue track are the color itself, so they do not mirror in right-to-left pages and keep their colors under forced colors. The hue slider is the framework's range input. In the hex field, Enter applies the value instead of submitting the form, and an invalid value goes back to the current color. The browser's color input only holds `#rrggbb`, so there is no alpha.\n\n#### Syntax\n\n```html\n<div class=\"color-picker\" role=\"group\" aria-label=\"Accent color\">\n <input type=\"color\" name=\"accent\" value=\"#6750a4\" aria-label=\"Accent color\">\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `onChange` | Function | `null` | Called with the new `#rrggbb` value when a change is committed. For live updates, listen for `input` on the native input. |\n\n#### Methods\n\n- `.destroy()`: Removes the area, the hue slider and the hex field. The native color input stays with its current value.\n\n#### Also documented\n\nThe Color picker section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens, Properties.\n\n#### Rules\n\n- `color-picker-wraps-a-color-input`: A .color-picker holds an <input type=\"color\"> as a direct child. It is the form value, the preview and the picker without JavaScript.\n- `color-picker-is-a-named-group`: Give the .color-picker role=\"group\" and a name, such as aria-label=\"Accent\", so its sliders and hex field read as one control.\n"
85
+ },
86
+ {
87
+ "file": "color-swatches.md",
88
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Color swatches\nPick one color from a preset palette, on native radio buttons.\n\nComponent ID: `color-swatches`\n\n[Component documentation](https://www.expressivecss.com/color-swatches.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/color-swatches.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Eight label colors for a calendar are color swatches. Any brand color a designer types in is the native color input.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-18); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-18, `llm.md#color-swatches`): A fieldset of native radios drawn as round chips of their colors, a ring around the checked one, the shared focus ring, a 48px target and an optional native color input for a custom value.\n\nWeb adaptation: Native radios keep one Tab stop, arrow keys and form submission; each swatch is named by visually hidden text in its label.\n\nKnown boundary: Swatch color, size and shape, the checked ring, the group name, arrow keys, the form value, the focus ring after arrow keys and the swatch color under forced colors are checked in Chromium; other engines, spoken output and enlarged text remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#color-swatches).\n\n#### Contract\n\nA `<fieldset class=\"color-swatches\">` holds a `<legend>` and one radio per color, each in a `<label>` whose visually hidden text (\"Purple\") comes before the radio, with `--md-comp-color-swatch-color` set on the radio. The radio is drawn as a 40px round chip of that color with a 48px target, and the checked one gets a ring, so the choice is marked by shape as well as color. Tab, the arrow keys and form submission stay native. Add an `<input type=\"color\">` in a label of its own at the end for a custom value; it is already drawn as a matching round swatch. Under forced colors the swatches keep their colors and the checked one gets a `Highlight` outline. CSS only; there is no JavaScript.\n\n#### Syntax\n\n```html\n<fieldset class=\"color-swatches\">\n <legend>Label color</legend>\n <label><span class=\"visually-hidden\">Purple</span><input type=\"radio\" name=\"label-color\" value=\"#6750a4\" style=\"--md-comp-color-swatch-color: #6750a4\" checked></label>\n <label><span class=\"visually-hidden\">Green</span><input type=\"radio\" name=\"label-color\" value=\"#386a20\" style=\"--md-comp-color-swatch-color: #386a20\"></label>\n <label><span class=\"visually-hidden\">Red</span><input type=\"radio\" name=\"label-color\" value=\"#b3261e\" style=\"--md-comp-color-swatch-color: #b3261e\"></label>\n <label><span class=\"visually-hidden\">Custom color</span><input type=\"color\" name=\"label-color-custom\" value=\"#ff8800\"></label>\n</fieldset>\n```\n\n#### Also documented\n\nThe Color swatches section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `color-swatches-is-a-fieldset`: A .color-swatches is a <fieldset> with a <legend>, so the radios read as one named group.\n- `color-swatch-is-named`: A swatch shows no text, so name each radio with visually hidden text in its label, such as \"Purple\".\n"
65
89
  },
66
90
  {
67
91
  "file": "column-chart.md",
68
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Column chart\nValues compared across categories or periods, drawn as columns from a data table, grouped or stacked.\n\nComponent ID: `column-chart`\n\n[Component documentation](https://www.expressivecss.com/column-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/column-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Signups per plan for each month of a half year is a grouped column chart. The same plans over three years of weekly data is a line chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-07); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-07, `llm.md#column-chart`): Columns drawn from zero from a figure's data table, grouped by row or stacked, with fade, track and sparkline modifiers, negative values, gaps, a fixed y range, x labels, a legend, and a pointer and keyboard tooltip with a highlight band.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the columns are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, grouped and stacked geometry, gaps, negative values, the y range, RTL, the tooltip, the band, keyboard movement and destroy are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. There is no y axis labelling and no range (low to high) column; horizontal bars are the bar chart.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#column-chart).\n\n#### Contract\n\nA `<figure class=\"column-chart\">` takes the same `<figcaption>` and `<table>` as a line chart. The first column labels each group; each further column is a series named by its header cell. `AutoInit()` draws each row as a group of columns growing from zero, one per series, with x labels, a legend when there is more than one series, and a highlight band and tooltip that follow the pointer. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"column-chart\">\n <figcaption>Subscriptions by plan</figcaption>\n <table>\n <thead>\n <tr><th>Month</th><th>Basic</th><th>Premium</th></tr>\n </thead>\n <tbody>\n <tr><th>Jan</th><td>5,500</td><td>3,500</td></tr>\n <tr><th>Feb</th><td>6,400</td><td>3,200</td></tr>\n <tr><th>Mar</th><td>7,600</td><td>3,400</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Column chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `column-chart-is-a-figure-with-a-table`: Write .column-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `column-chart-has-a-caption`: Name a .column-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
92
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Column chart\nValues compared across categories or periods, drawn as columns from a data table, grouped or stacked.\n\nComponent ID: `column-chart`\n\n[Component documentation](https://www.expressivecss.com/column-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/column-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Signups per plan for each month of a half year is a grouped column chart. The same plans over three years of weekly data is a line chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-07); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-07, `llm.md#column-chart`): Columns drawn from zero from a figure's data table, grouped by row or stacked, with fade, track and sparkline modifiers, negative values, gaps, a fixed y range, x labels, round y axis labels with .axis, a legend, and a pointer and keyboard tooltip with a highlight band.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the columns are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, grouped and stacked geometry, range columns, gaps, negative values, the y range, RTL, the tooltip, the band, keyboard movement and destroy are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. There is no y axis labelling and no range (low to high) column; horizontal bars are the bar chart.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#column-chart).\n\n#### Contract\n\nA `<figure class=\"column-chart\">` takes the same `<figcaption>` and `<table>` as a line chart. The first column labels each group; each further column is a series named by its header cell. `AutoInit()` draws each row as a group of columns growing from zero, one per series, with x labels, a legend when there is more than one series, and a highlight band and tooltip that follow the pointer. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"column-chart\">\n <figcaption>Subscriptions by plan</figcaption>\n <table>\n <thead>\n <tr><th>Month</th><th>Basic</th><th>Premium</th></tr>\n </thead>\n <tbody>\n <tr><th>Jan</th><td>5,500</td><td>3,500</td></tr>\n <tr><th>Feb</th><td>6,400</td><td>3,200</td></tr>\n <tr><th>Mar</th><td>7,600</td><td>3,400</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Column chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `column-chart-is-a-figure-with-a-table`: Write .column-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `column-chart-has-a-caption`: Name a .column-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
69
93
  },
70
94
  {
71
95
  "file": "command-palette.md",
72
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Command palette\nA searchable list of commands in a dialog, opened from anywhere with a shortcut.\n\nComponent ID: `command-palette`\n\n[Component documentation](https://www.expressivecss.com/command-palette.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/command-palette.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Finding an order by customer name is search over content. A command palette is for running actions and opening places in the app.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#command-palette`): A modal dialog with a filtering combobox and listbox, group headings, keywords, an empty message, a Ctrl or Command shortcut and native commandfor opening.\n\nWeb adaptation: A native modal <dialog>; commands are links and buttons, and the component adds the combobox and listbox roles and the active option.\n\nKnown boundary: The shortcut, filtering, arrow and Enter handling, closing, focus return, the native opener and destroy are checked in a browser; screen reader announcements and shortcut conflicts with assistive technology remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#command-palette).\n\n#### Contract\n\nA `<dialog class=\"command-palette\">` holds a search `<input>`, a `<ul>` of commands and an optional `.command-palette-empty` message. Each command is an `<li>` holding a link or button: an optional leading icon, the label and an optional trailing `<kbd>` shortcut. An `<li class=\"label\">` heads a group. Running a command clicks its link or button, so commands are ordinary links and click handlers. Commands added to the list later are picked up. A command the page hides with `hidden` stays hidden, and one whose button is `disabled` is shown dimmed, marked `aria-disabled` and skipped by the arrow keys. `AutoInit()` starts every `dialog.command-palette`.\n\nCtrl+K, or Command+K on Apple platforms, opens and closes it from anywhere on the page unless the page has already handled the key, as an editor's own Ctrl+K does; a `commandfor` button with `command=\"show-modal\"` opens it too. Typing filters the commands by their text and any `data-keywords`, ignoring case and accents, and hides a group heading whose commands are all filtered out. Focus stays in the input: the arrow keys move the active command and Enter runs it. The dialog closes on Escape and returns focus. Each opening starts with an empty search.\n\nThe component makes the input a combobox and the list a listbox of options, and reports the active option with `aria-activedescendant`. Name the dialog and the input with `aria-label`. Give the empty message `role=\"status\"` so a search with no results is announced.\n\n#### Syntax\n\n```html\n<button type=\"button\" commandfor=\"commands\" command=\"show-modal\">\n Commands <kbd><kbd>Ctrl</kbd>+<kbd>K</kbd></kbd>\n</button>\n<dialog id=\"commands\" class=\"command-palette\" aria-label=\"Commands\">\n <input type=\"search\" placeholder=\"Type a command\" aria-label=\"Search commands\">\n <ul>\n <li class=\"label\">Files</li>\n <li>\n <button type=\"button\" data-keywords=\"create add\">\n <span class=\"material-symbols\" aria-hidden=\"true\">note_add</span>\n New file\n <kbd>Ctrl N</kbd>\n </button>\n </li>\n <li><a href=\"/settings\">\n <span class=\"material-symbols\" aria-hidden=\"true\">settings</span>\n Settings\n </a></li>\n </ul>\n <p class=\"command-palette-empty\" role=\"status\">No matching commands</p>\n</dialog>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `shortcut` | String | `'k'` | Letter that toggles the palette with Command on Apple platforms or Ctrl elsewhere. A key event the page already handled is left alone. `null` turns it off. `data-shortcut` on the dialog sets it, and an empty `data-shortcut` turns it off. |\n| `closeOnRun` | Boolean | `true` | If true, the palette closes after a command runs. |\n\n#### Methods\n\n- `.open()`: Opens the palette as a modal dialog with the search input focused.\n- `.close()`: Closes the palette.\n- `.destroy()`: Removes the shortcut, listeners, roles and ids the component added.\n\n#### Also documented\n\nThe Command palette section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `command-palette-is-a-dialog`: Write the palette as <dialog class=\"command-palette\">. The dialog makes it modal, closes it on Escape and returns focus.\n- `command-palette-is-named`: Name the palette with aria-label so it is announced when it opens.\n- `command-palette-input-is-named`: Name the search input with aria-label. A placeholder disappears as the user types and is not a reliable name.\n"
96
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Command palette\nA searchable list of commands in a dialog, opened from anywhere with a shortcut.\n\nComponent ID: `command-palette`\n\n[Component documentation](https://www.expressivecss.com/command-palette.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/command-palette.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Finding an order by customer name is search over content. A command palette is for running actions and opening places in the app.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#command-palette`): A modal dialog with a filtering combobox and listbox, group headings, keywords, an empty message, a Ctrl or Command shortcut and native commandfor opening.\n\nWeb adaptation: A native modal <dialog>; commands are links and buttons, and the component adds the combobox and listbox roles and the active option.\n\nKnown boundary: The shortcut, filtering, arrow and Enter handling, closing, focus return, the native opener and destroy are checked in a browser; screen reader announcements and shortcut conflicts with assistive technology remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#command-palette).\n\n#### Contract\n\nA `<dialog class=\"command-palette\">` holds a search `<input>`, a `<ul>` of commands and an optional `.command-palette-empty` message. Each command is an `<li>` holding a link or button: an optional leading icon, the label and an optional trailing `<kbd>` shortcut. An `<li class=\"label\">` heads a group. Running a command clicks its link or button, so commands are ordinary links and click handlers. Commands added to the list later are picked up. A command the page hides with `hidden` stays hidden, and one whose button is `disabled` is shown dimmed, marked `aria-disabled` and skipped by the arrow keys. `AutoInit()` starts every `dialog.command-palette`.\n\nCtrl+K, or Command+K on Apple platforms, opens and closes it from anywhere on the page unless the page has already handled the key, as an editor's own Ctrl+K does; a `commandfor` button with `command=\"show-modal\"` opens it too. Typing filters the commands by their text and any `data-keywords`, ignoring case and accents, and hides a group heading whose commands are all filtered out. Focus stays in the input: the arrow keys move the active command and Enter runs it. The dialog closes on Escape and returns focus. Each opening starts with an empty search.\n\nThe component makes the input a combobox and the list a listbox of options, and reports the active option with `aria-activedescendant`. Name the dialog and the input with `aria-label`. Give the empty message `role=\"status\"` so a search with no results is announced.\n\n#### Syntax\n\n```html\n<button type=\"button\" commandfor=\"commands\" command=\"show-modal\">\n Commands <kbd><kbd>Ctrl</kbd>+<kbd>K</kbd></kbd>\n</button>\n<dialog id=\"commands\" class=\"command-palette\" aria-label=\"Commands\">\n <input type=\"search\" placeholder=\"Type a command\" aria-label=\"Search commands\">\n <ul>\n <li class=\"label\">Files</li>\n <li>\n <button type=\"button\" data-keywords=\"create add\">\n <span class=\"material-symbols\" aria-hidden=\"true\">note_add</span>\n New file\n <kbd>Ctrl N</kbd>\n </button>\n </li>\n <li><a href=\"/settings\">\n <span class=\"material-symbols\" aria-hidden=\"true\">settings</span>\n Settings\n </a></li>\n </ul>\n <p class=\"command-palette-empty\" role=\"status\">No matching commands</p>\n</dialog>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `shortcut` | String | `'k'` | Letter that toggles the palette with Command on Apple platforms or Ctrl elsewhere. A key event the page already handled is left alone. `null` turns it off. `data-shortcut` on the dialog sets it, and an empty `data-shortcut` turns it off. |\n| `closeOnRun` | Boolean | `true` | If true, the palette closes after a command runs. |\n\n#### Methods\n\n- `.open()`: Opens the palette as a modal dialog with the search input focused.\n- `.close()`: Closes the palette.\n- `.destroy()`: Removes the shortcut, listeners, roles and ids the component added.\n\n#### Also documented\n\nThe Command palette section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `command-palette-is-a-dialog`: Write the palette as <dialog class=\"command-palette\">. The dialog makes it modal, closes it on Escape and returns focus.\n- `command-palette-is-named`: Name the palette with aria-label so it is announced when it opens.\n- `command-palette-input-is-named`: Name the search input with aria-label. A placeholder disappears as the user types and is not a reliable name.\n"
73
97
  },
74
98
  {
75
99
  "file": "confetti.md",
76
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Confetti\nBursts of theme-colored confetti for a moment worth celebrating.\n\nComponent ID: `confetti`\n\n[Component documentation](https://www.expressivecss.com/confetti.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/confetti.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A checkout's \"Order placed\" screen can burst confetti once beside its confirmation text. Saving a draft shows a snackbar and no confetti.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-09); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-09, `llm.md#confetti`): Bursts from a button's center over the page, inside an authored canvas, or from script, with count, angle, spread, speed, decay, gravity, drift, lifetime, origin, colors, square, circle and star shapes, and size options, theme colors by default and a promise that resolves when the burst ends.\n\nWeb adaptation: A canvas drawn with requestAnimationFrame. The page canvas is an aria-hidden manual popover with pointer events off, so it draws above modal dialogs without blocking clicks. Reduced motion draws nothing.\n\nKnown boundary: The burst origin on a button and on a canvas, theme colors, manualStart, the page canvas in the top layer above a modal dialog with pointer events off and aria-hidden, its removal when the burst ends, destroy and the reduced-motion skip are checked in a browser; frame rate on low-end devices and forced colors remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#confetti).\n\n#### Contract\n\nBursts of theme-colored confetti for a moment worth celebrating, such as a finished order or a completed goal.\n\nGive a button the `confetti` class and each click bursts from its center over the whole page. A `<canvas class=\"confetti\" aria-hidden=\"true\">` covers its nearest positioned ancestor and bursts inside it once when initialized, unless `manualStart` is set, and again on each `fire()`. `Expressive.Confetti.fire(options)` bursts over the page with no markup. `AutoInit()` starts every `.confetti`.\n\n#### Syntax\n\n```html\n<button type=\"button\" class=\"filled confetti\">Celebrate</button>\n\n<div style=\"position: relative; height: 320px\">\n <canvas class=\"confetti\" aria-hidden=\"true\"></canvas>\n</div>\n\n<script>\nExpressive.Confetti.fire({ particleCount: 100, spread: 70, origin: { x: 0.5, y: 0.6 } });\n</script>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `particleCount` | Number | `50` | Pieces per burst. |\n| `angle` | Number | `90` | Launch direction in degrees; 90 is straight up and 0 is right. |\n| `spread` | Number | `45` | Degrees the pieces fan out from the angle. |\n| `startVelocity` | Number | `45` | Launch speed in pixels per frame. A small canvas needs a lower speed to keep the pieces inside it. |\n| `decay` | Number | `0.9` | Share of its speed a piece keeps each frame. |\n| `gravity` | Number | `1` | Pull toward the bottom; 0 floats. |\n| `drift` | Number | `0` | Sideways push per frame; negative drifts left. |\n| `ticks` | Number | `200` | Frames a piece lives while it fades, counted at 60 per second. |\n| `origin` | Object | `{ x: 0.5, y: 0.5 }` | Launch point as fractions of the canvas from 0 to 1; a missing side is 0.5. A button launches from its own center instead. |\n| `colors` | Array | `[]` | CSS colors. Empty uses the theme colors. |\n| `shapes` | Array | `['square', 'circle']` | Any of `'square'`, `'circle'` and `'star'`. |\n| `scalar` | Number | `1` | Piece size multiplier. |\n| `manualStart` | Boolean | `false` | On a canvas, wait for `fire()` instead of bursting at init. |\n\n#### Methods\n\n- `Confetti.fire(options)`: Bursts over the whole page. Returns a promise that resolves when the last piece is gone.\n- `.fire(options)`: Bursts on this canvas, or from this button's center over the page, with these options merged over the instance's. Returns the same kind of promise.\n- `.destroy()`: Removes a button's click handler, or stops and clears a canvas.\n\n#### Rules\n\n- `confetti-canvas-is-hidden`: Confetti is decoration. Mark the canvas aria-hidden=\"true\" and say what happened in text, for example in a snackbar.\n"
100
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Confetti\nBursts of theme-colored confetti for a moment worth celebrating.\n\nComponent ID: `confetti`\n\n[Component documentation](https://www.expressivecss.com/confetti.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/confetti.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A checkout's \"Order placed\" screen can burst confetti once beside its confirmation text. Saving a draft shows a snackbar and no confetti.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-09); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-09, `llm.md#confetti`): Bursts from a button's center over the page, inside an authored canvas, or from script, with count, angle, spread, speed, decay, gravity, drift, lifetime, origin, colors, square, circle and star shapes, and size options, theme colors by default and a promise that resolves when the burst ends.\n\nWeb adaptation: A canvas drawn with requestAnimationFrame. The page canvas is an aria-hidden manual popover with pointer events off, so it draws above modal dialogs without blocking clicks. Reduced motion draws nothing.\n\nKnown boundary: The burst origin on a button and on a canvas, theme colors, manualStart, the page canvas in the top layer above a modal dialog with pointer events off and aria-hidden, its removal when the burst ends, destroy and the reduced-motion skip are checked in a browser; frame rate on low-end devices and forced colors remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#confetti).\n\n#### Contract\n\nBursts of theme-colored confetti for a moment worth celebrating, such as a finished order or a completed goal.\n\nGive a button the `confetti` class and each click bursts from its center over the whole page. A `<canvas class=\"confetti\" aria-hidden=\"true\">` covers its nearest positioned ancestor and bursts inside it once when initialized, unless `manualStart` is set, and again on each `fire()`. `Expressive.Confetti.fire(options)` bursts over the page with no markup. `AutoInit()` starts every `.confetti`.\n\n#### Syntax\n\n```html\n<button type=\"button\" class=\"filled confetti\">Celebrate</button>\n\n<div style=\"position: relative; height: 320px\">\n <canvas class=\"confetti\" aria-hidden=\"true\"></canvas>\n</div>\n\n<script>\nExpressive.Confetti.fire({ particleCount: 100, spread: 70, origin: { x: 0.5, y: 0.6 } });\n</script>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `particleCount` | Number | `50` | Pieces per burst. |\n| `angle` | Number | `90` | Launch direction in degrees; 90 is straight up and 0 is right. |\n| `spread` | Number | `45` | Degrees the pieces fan out from the angle. |\n| `startVelocity` | Number | `45` | Launch speed in pixels per frame. A small canvas needs a lower speed to keep the pieces inside it. |\n| `decay` | Number | `0.9` | Share of its speed a piece keeps each frame. |\n| `gravity` | Number | `1` | Pull toward the bottom; 0 floats. |\n| `drift` | Number | `0` | Sideways push per frame; negative drifts left. |\n| `ticks` | Number | `200` | Frames a piece lives while it fades, counted at 60 per second. |\n| `origin` | Object | `{ x: 0.5, y: 0.5 }` | Launch point as fractions of the canvas from 0 to 1; a missing side is 0.5. A button launches from its own center instead. |\n| `colors` | Array | `[]` | CSS colors. Empty uses the theme colors. |\n| `shapes` | Array | `['square', 'circle']` | Any of `'square'`, `'circle'` and `'star'`. |\n| `scalar` | Number | `1` | Piece size multiplier. |\n| `manualStart` | Boolean | `false` | On a canvas, wait for `fire()` instead of bursting at init. |\n\n#### Methods\n\n- `Confetti.fire(options)`: Bursts over the whole page. Returns a promise that resolves when the last piece is gone.\n- `.fire(options)`: Bursts on this canvas, or from this button's center over the page, with these options merged over the instance's. Returns the same kind of promise.\n- `.destroy()`: Removes a button's click handler, or stops and clears a canvas.\n\n#### Rules\n\n- `confetti-canvas-is-hidden`: Confetti is decoration. Mark the canvas aria-hidden=\"true\" and say what happened in text, for example in a snackbar.\n"
77
101
  },
78
102
  {
79
103
  "file": "countdown.md",
80
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Countdown\nA number from 0 to 999 whose digits roll to each new value.\n\nComponent ID: `countdown`\n\n[Component documentation](https://www.expressivecss.com/countdown.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/countdown.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Monthly revenue of $48,200 is a stat tile. A countdown only shows whole numbers up to 999 and has no label.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-08); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-08, `llm.md#countdown`): Digits from 0 to 999 that roll up or down when --md-comp-countdown-value changes, with zero padding to two or three digits and a reduced-motion fallback.\n\nWeb adaptation: The element's text is the accessible value; the digit strips are generated content with empty alternative text. A page script sets the property and the text together.\n\nKnown boundary: Width, digit positions, the baseline, the roll transition, reduced motion and the accessibility tree text are checked in a browser; spoken output inside role=\"timer\" and aria-live regions remains unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#countdown).\n\n#### Contract\n\nA `.countdown` is a `<span>` with `--md-comp-countdown-value` set to a whole number and the same number as its text. When the value changes, each digit rolls up or down to the new one. The rolling digits are generated content hidden from assistive technology, so the text is what a screen reader reads; change the property and the text together. Values below 0 show 0 and values above 999 show 999. The box is as wide as the number; `--md-comp-countdown-digits` pads it with leading zeros to 2 or 3 digits. Size and color come from the surrounding text. With reduced motion the digits change without rolling. CSS only; the page's script sets the value.\n\n#### Syntax\n\n```html\n<span id=\"countdown\" class=\"countdown\" style=\"--md-comp-countdown-value: 60\">60</span>\n\n<script>\n const countdown = document.getElementById('countdown');\n let value = 60;\n setInterval(() => {\n value = value === 0 ? 60 : value - 1;\n countdown.style.setProperty('--md-comp-countdown-value', value);\n countdown.textContent = value;\n }, 1000);\n</script>\n```\n\n#### Also documented\n\nThe Countdown section of the [API reference](https://www.expressivecss.com/llm.md) also covers Clock, Tokens.\n\n#### Rules\n\n- `countdown-has-text`: Write the number as the countdown's text, matching --md-comp-countdown-value. The rolling digits are hidden from screen readers, so an empty countdown reads as nothing.\n"
104
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Countdown\nA number from 0 to 999 whose digits roll to each new value.\n\nComponent ID: `countdown`\n\n[Component documentation](https://www.expressivecss.com/countdown.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/countdown.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Monthly revenue of $48,200 is a stat tile. A countdown only shows whole numbers up to 999 and has no label.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-08); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-08, `llm.md#countdown`): Digits from 0 to 999 that roll up or down when --md-comp-countdown-value changes, with zero padding to two or three digits and a reduced-motion fallback.\n\nWeb adaptation: The element's text is the accessible value; the digit strips are generated content with empty alternative text. A page script sets the property and the text together.\n\nKnown boundary: Width, digit positions, the baseline, the roll transition, reduced motion and the accessibility tree text are checked in a browser; spoken output inside role=\"timer\" and aria-live regions remains unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#countdown).\n\n#### Contract\n\nA `.countdown` is a `<span>` with `--md-comp-countdown-value` set to a whole number and the same number as its text. When the value changes, each digit rolls up or down to the new one. The rolling digits are generated content hidden from assistive technology, so the text is what a screen reader reads; change the property and the text together. Values below 0 show 0 and values above 999 show 999. The box is as wide as the number; `--md-comp-countdown-digits` pads it with leading zeros to 2 or 3 digits. Size and color come from the surrounding text. With reduced motion the digits change without rolling. CSS only; the page's script sets the value.\n\n#### Syntax\n\n```html\n<span id=\"countdown\" class=\"countdown\" style=\"--md-comp-countdown-value: 60\">60</span>\n\n<script>\n const countdown = document.getElementById('countdown');\n let value = 60;\n setInterval(() => {\n value = value === 0 ? 60 : value - 1;\n countdown.style.setProperty('--md-comp-countdown-value', value);\n countdown.textContent = value;\n }, 1000);\n</script>\n```\n\n#### Also documented\n\nThe Countdown section of the [API reference](https://www.expressivecss.com/llm.md) also covers Clock, Tokens.\n\n#### Rules\n\n- `countdown-has-text`: Write the number as the countdown's text, matching --md-comp-countdown-value. The rolling digits are hidden from screen readers, so an empty countdown reads as nothing.\n"
105
+ },
106
+ {
107
+ "file": "currency-input.md",
108
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Currency input\nShows a typed amount in the page's locale and currency once the field loses focus, and submits the plain number.\n\nComponent ID: `currency-input`\n\n[Component documentation](https://www.expressivecss.com/currency-input.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/currency-input.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A price field in a German shop takes data-currency=\"EUR\" with inputmode=\"decimal\" under lang=\"de\" and shows 1.234,50 € after blur; a quantity field stays a number field.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-25); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-25, `llm.md#currency-input`): A data-currency ISO 4217 code on a text input; the amount shown with Intl.NumberFormat in the nearest lang and that currency on blur, the plain number in the locale's decimal mark on focus, typed and pasted amounts read in either decimal mark, a validity message for text with no digit that i18n can replace, saved and reset values formatted, a value property, and the plain number in the form data.\n\nWeb adaptation: The native value holds the formatted text, and a capture-phase formdata listener on the document swaps the field's entry for the plain number, so submits and new FormData(form) read a number while the field keeps its name. setCustomValidity reports text with no amount, so constraint validation and .validate show the message.\n\nKnown boundary: Typing and blur, focus editing, listener order, pasted amounts submitted with Enter, the validity message, saved and reset values and destroy() are checked in a browser, and the parsing rules in jsdom; mobile decimal keypads and spoken output remain unverified. Formatting happens on blur only, and a form that repeats a currency field's name gets one entry for them.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#currency-input).\n\n#### Contract\n\nAdd `data-currency` with an ISO 4217 code, such as `EUR`, to an `<input>` of type `text`. `AutoInit()` starts every one. The locale comes from the nearest `lang`, or the browser's language without one. A code `Intl.NumberFormat` rejects leaves the field a plain text input.\n\nWhen the field loses focus, it shows the amount rounded to the currency's fraction digits: under `lang=\"de\"` with `EUR`, typing `1234.5` shows `1.234,50 €`. A page's `change` and `blur` listeners read the formatted text. When the field gains focus, it shows the plain number in the locale's decimal mark, without grouping or sign, such as `1234,5`, with the caret at the end.\n\nTyped and pasted text keeps its digits, `.`, `,` and a leading `-` or `−`, and drops anything else, such as spaces, signs and letters. The last `.` or `,` is the decimal mark when it is followed by no more digits than the currency shows; every other separator groups. So `1234.5`, `1234,5`, `1.234,5` and `1,234.50` all read 1234.5, `1.234` reads 1234 for euros, and with yen, which has no fraction digits, every separator groups. Full-width digits from an IME and the locale's own digits read as digits. Text with no digit stays as typed, and the field reports itself invalid with `Enter an amount.` until it holds an amount or is empty, so `.validate` shows the message.\n\n#### Syntax\n\n```html\n<div class=\"field\">\n <input id=\"price\" name=\"price\" type=\"text\" inputmode=\"decimal\" autocomplete=\"off\" placeholder=\" \" data-currency=\"EUR\">\n <label for=\"price\">Price in euros</label>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `i18n` | Object | `{ invalid: 'Enter an amount.' }` | `invalid` is the validity message for text that holds no amount. Markup sets it with `data-currency-input-options='{\"i18n\":{\"invalid\":\"Betrag eingeben.\"}}'`. |\n\n#### Methods\n\n- `.destroy()`: Removes the listeners, clears the validity message and leaves the text as it is.\n\n#### Also documented\n\nThe Currency input section of the [API reference](https://www.expressivecss.com/llm.md) also covers Properties.\n\n#### Rules\n\n- `currency-input-needs-a-text-field`: Put data-currency, with a currency code such as EUR, on an <input> of type text. Other input types reject a formatted amount such as 1.234,50 €, and AutoInit skips them; a <textarea> is not formatted.\n"
81
109
  },
82
110
  {
83
111
  "file": "data-table.md",
84
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Data table\nRows of records in a scrolling table with a sticky header, sort state and row selection.\n\nComponent ID: `data-table`\n\n[Component documentation](https://www.expressivecss.com/data-table.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/data-table.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A contacts screen where people tap a name to open a profile is a list. A data table would add column headers and sorting nobody uses on a phone.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#data-table`): Scrolling wrapper with an outline, sticky header row, numeric columns, dense rows, a sort arrow drawn from aria-sort and checkbox row selection.\n\nWeb adaptation: A plain div wraps a native <table>, so the table keeps its role; the page owns sorting, select-all and the scroll region name.\n\nKnown boundary: Table semantics, the sticky header, numeric alignment, the sort arrow and selected rows are checked in a browser; sort announcements, enlarged text and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#data-table).\n\n#### Contract\n\nA `.data-table` is a `<div>` around one native `<table>`. The wrapper scrolls and draws the outline, so the table keeps its table semantics. The header row sticks to the top of the wrapper once the wrapper scrolls vertically, so give the wrapper a `max-block-size` for long tables. Add `numeric` to the header and body cells of a number column to align them on the end edge with tabular figures, and `dense` to the wrapper for shorter rows. CSS only; there is no JavaScript.\n\nSorting is page state. Put a `<button>` in each sortable `<th>` and set `aria-sort=\"ascending\"` or `\"descending\"` on the header of the sorted column. The framework draws the arrow; the page reorders the rows and moves `aria-sort`.\n\n#### Syntax\n\n```html\n<div class=\"data-table\">\n <table>\n <caption>Orders</caption>\n <thead>\n <tr>\n <th scope=\"col\" aria-sort=\"ascending\"><button type=\"button\">Customer</button></th>\n <th scope=\"col\">Status</th>\n <th scope=\"col\" class=\"numeric\"><button type=\"button\">Total</button></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th scope=\"row\">Alvin</th>\n <td>Shipped</td>\n <td class=\"numeric\">$87.00</td>\n </tr>\n <tr>\n <th scope=\"row\">Shannon</th>\n <td>Pending</td>\n <td class=\"numeric\">$9.99</td>\n </tr>\n </tbody>\n </table>\n</div>\n```\n\n#### Also documented\n\nThe Data table section of the [API reference](https://www.expressivecss.com/llm.md) also covers Row selection, Scrolling, Tokens.\n\n#### Rules\n\n- `data-table-wraps-the-table`: Put .data-table on a <div> around the <table>. The wrapper scrolls; a table made to scroll itself needs display:block, which drops its table role in some engines.\n- `data-table-sort-is-on-a-header`: aria-sort belongs on the column header cell (<th>), not on the button inside it or on a data cell. Assistive technology reads the sort state from the header.\n- `data-table-row-is-not-selected`: aria-selected is not valid on a row of a plain table. It belongs to role=\"grid\" and its keyboard contract. Select a row with a labelled checkbox in its first cell.\n- `data-table-scroll-region-is-a-region`: A focusable scroll wrapper needs role=\"region\" so its name is announced. aria-label is not allowed on a plain div.\n- `data-table-scroll-region-is-named`: Name a focusable scroll wrapper with aria-label, or aria-labelledby pointing at the table caption, so keyboard users know what they are scrolling.\n"
112
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Data table\nRows of records in a scrolling table with a sticky header, sort state and row selection.\n\nComponent ID: `data-table`\n\n[Component documentation](https://www.expressivecss.com/data-table.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/data-table.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A contacts screen where people tap a name to open a profile is a list. A data table would add column headers and sorting nobody uses on a phone.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-27, `llm.md#data-table`): Scrolling native tables with sticky headers, numeric/dense rows, sorting, page-scoped selection, column resizing/visibility, text filtering and local paging with record/detail groups and authored counts.\n\nWeb adaptation: A plain div wraps a native table. Shared DataTables handlers own opt-in sorting, filtering, select-all and column operations; authors name controls and the scroll region, and keep native row and header semantics.\n\nKnown boundary: Table semantics, the sticky header, numeric alignment, the sort arrow and selected rows are checked in a browser; sort announcements, enlarged text and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#data-table).\n\n#### Contract\n\nA `.data-table` is a `<div>` around one native `<table>`. The wrapper scrolls and draws the outline, so the table keeps its table semantics. The header row sticks to the top of the wrapper once the wrapper scrolls vertically, so give the wrapper a `max-block-size` for long tables. Add `numeric` to the header and body cells of a number column to align them on the end edge with tabular figures, and `dense` to the wrapper for shorter rows.\n\nPut a `<button>` in each sortable `<th>` and set `aria-sort=\"ascending\"` or `\"descending\"` on the header of the sorted column. The framework draws the arrow. Add `data-sort` to the wrapper and the framework sorts too: a header button reorders the rows by its column and moves `aria-sort`. A `numeric` header compares numbers (`$1,200.00` after `$87.00`); other columns compare text in the page's language, with 9 before 10. A cell's `data-value` stands in for its text, and a `tr.data-table-detail` moves with its row. Each press fires a cancelable `expressive:sort` from the wrapper with `{ header, column, direction }`; a server-paged table cancels it and loads the sorted page, with `aria-sort` already moved. Without `data-sort`, the page reorders the rows and moves `aria-sort` itself.\n\nAdd a labelled `input[type=\"search\"][data-table-filter]` inside the wrapper for text filtering, and put `data-filter` on each searchable single-column header. A named `output[data-table-filter-count]` with `role=\"status\"` and `aria-live=\"polite\"` receives the matching record count. Filtering keeps record details together and preserves author-hidden rows. Select-all changes only enabled records visible in the current view, while hidden records keep their selections. Each edit fires cancelable `expressive:filter` with `{ query }`. Cancel synchronously for server filtering, then replace records, write the count and call `Expressive.DataTables.sync(wrapper)`. Sync stays silent and preserves server records and counts until the next uncanceled input; otherwise it reapplies the local query.\n\n#### Syntax\n\n```html\n<div class=\"data-table\" data-sort>\n <table>\n <caption>Orders</caption>\n <thead>\n <tr>\n <th scope=\"col\" aria-sort=\"ascending\"><button type=\"button\">Customer</button></th>\n <th scope=\"col\">Status</th>\n <th scope=\"col\" class=\"numeric\"><button type=\"button\">Total</button></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th scope=\"row\">Alvin</th>\n <td>Shipped</td>\n <td class=\"numeric\">$87.00</td>\n </tr>\n <tr>\n <th scope=\"row\">Shannon</th>\n <td>Pending</td>\n <td class=\"numeric\">$9.99</td>\n </tr>\n </tbody>\n </table>\n</div>\n```\n\n#### Also documented\n\nThe Data table section of the [API reference](https://www.expressivecss.com/llm.md) also covers Row selection, Text filtering, Local paging, Server paging, Bulk actions, Row expansion, Scrolling, Sticky first column, Column resize, Column visibility, Tokens.\n\n#### Rules\n\n- `data-table-wraps-the-table`: Put .data-table on a <div> around the <table>. The wrapper scrolls; a table made to scroll itself needs display:block, which drops its table role in some engines.\n- `data-table-sort-is-on-a-header`: aria-sort belongs on the column header cell (<th>), not on the button inside it or on a data cell. Assistive technology reads the sort state from the header.\n- `data-table-row-is-not-selected`: aria-selected is not valid on a row of a plain table. It belongs to role=\"grid\" and its keyboard contract. Select a row with a labelled checkbox in its first cell.\n- `data-table-scroll-region-is-a-region`: A focusable scroll wrapper needs role=\"region\" so its name is announced. aria-label is not allowed on a plain div.\n- `data-table-scroll-region-is-named`: Name a focusable scroll wrapper with aria-label, or aria-labelledby pointing at the table caption, so keyboard users know what they are scrolling.\n- `data-table-filter-is-named`: Give the table search input a label identifying the records it filters.\n- `data-table-filter-count-is-named`: Name the result count, for example aria-label=\"Matching records\", so its number has context.\n- `data-table-filter-count-is-status`: Give the table result count role=\"status\" so changes are announced without moving focus.\n- `data-table-filter-count-is-polite`: Use aria-live=\"polite\" on the table result count so typing does not interrupt other speech.\n- `data-table-page-size-is-select`: Use a native select for an authored table page-size control. The existing FormSelect enhancement reads and updates that same select.\n- `data-table-page-size-is-single`: A table has one page size. Omit multiple from its page-size select.\n- `data-table-page-size-is-named`: Label the table page-size select, for example Rows per page. Option values do not name the control.\n- `data-table-page-control-is-button`: Use a native button with type=\"button\" for a local table page control. Ordinary pagination links keep their navigation behavior.\n- `data-table-page-control-is-named`: Name each table page button, for example Previous or Next, using visible text or an accessible label.\n- `data-table-page-control-is-in-pagination`: Put local table page buttons in a named nav.pagination inside their table wrapper.\n- `data-table-page-navigation-is-named`: Name the table's pagination navigation so it is distinct from other navigation on the page.\n- `data-table-page-direction-is-known`: Set data-table-page to previous or next. Local table paging does not generate numbered page controls.\n- `data-table-page-status-is-named`: Name the page status, for example aria-label=\"Pages\", so its numbers have context.\n- `data-table-page-status-is-status`: Give the table page status role=\"status\" so page changes are announced without moving focus.\n- `data-table-page-status-is-polite`: Use aria-live=\"polite\" on the table page status so page changes do not interrupt other speech.\n"
85
113
  },
86
114
  {
87
115
  "file": "date-picker.md",
88
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Date picker\nSelect a date, a range, or several dates from a calendar.\n\nComponent ID: `date-picker`\n\n[Component documentation](https://www.expressivecss.com/datepicker.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/datepicker.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/date-pickers/overview)\n\nExample: A single date with no calendar-specific requirements can use a labeled native date input. Do not choose this picker expecting a popup: its calendar is inline and requires the documented visibility option.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/date-pickers/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/date-pickers/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/date-pickers/guidelines).\n\nSupport (2026-09-17, `llm.md#date-picker`): Inline single-date, range, and multiple-date calendar.\n\nWeb adaptation: Default inline presentation with optional docked display plugin; no open()/close() methods or modal implementation.\n\nKnown boundary: Modal picker parity is not provided by the documented inline API.\n\nKnown boundary: Calendar keyboard navigation and range/multiple selection lifecycle have browser coverage. Spoken calendar context, native zoom and physical devices remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#date-picker).\n\n#### Contract\n\nAdd `date-picker` to a text input. `AutoInit()` starts every `.date-picker` except those marked `no-autoinit`.\n\nThe calendar is inline, not a modal. `open()` and `close()` are not provided. With the default options the calendar is hidden (`openByDefault: false`) and clicking the input does not reveal it. Pass `openByDefault: true` to show the calendar under the field.\n\n#### Syntax\n\n```html\n<div class=\"field\">\n <input type=\"text\" class=\"date-picker\" id=\"birthdate\">\n <label for=\"birthdate\">Birthdate</label>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `format` | String or Function | `'mmm dd, yyyy'` | Output written to the input, or a function that takes a `Date` and returns a string. |\n| `parse` | Function | `null` | Turn the current input string back into a `Date`. Receives `(value, format)`, where `format` is the configured string or an empty string for a function formatter. Blank inputs skip parsing. |\n| `isDateRange` | Boolean | `false` | Select a start date and an end date. |\n| `dateRangeEndEl` | String | `null` | Selector for an existing end-date input. If omitted, a second input is created. |\n| `isMultipleSelection` | Boolean | `false` | Toggle several dates. Extra inputs are created as dates are added. |\n| `defaultDate` | Date | `null` | Initial date to view. Falls back to the input’s current value, then today. |\n| `defaultEndDate` | Date | `null` | Initial end date when `isDateRange` is true. |\n| `setDefaultDate` | Boolean | `false` | If true, `defaultDate` is also the selected value. |\n| `setDefaultEndDate` | Boolean | `false` | If true, `defaultEndDate` is also the selected end value. |\n| `disableWeekends` | Boolean | `false` | Prevent selecting Saturday and Sunday. |\n| `disableDayFn` | Function | `null` | Return true to disable that day. Receives a `Date`. |\n| `firstDay` | Number | `0` | First day of the week. `0` is Sunday, `1` is Monday. |\n| `minDate` | Date | `null` | Earliest selectable date. |\n| `maxDate` | Date | `null` | Latest selectable date. |\n| `yearRange` | Number or Array | `10` | Years on either side of the viewed year, or `[minYear, maxYear]`. |\n| `yearRangeReverse` | Boolean | `false` | Sort the year list in reverse order. |\n| `isRTL` | Boolean | `false` | Render the calendar right-to-left. |\n| `showMonthAfterYear` | Boolean | `false` | Show the month after the year in the title. |\n| `showDaysInNextAndPreviousMonths` | Boolean | `false` | Render days that fall in the adjoining months. |\n| `openByDefault` | Boolean | `false` | If true, the calendar is visible. If false, it is given `display: none`. |\n| `container` | Element or String | `null` | Element or selector to render the calendar into. When set, the calendar is not hidden. |\n| `showClearBtn` | Boolean | `false` | Show a Clear button in the footer. |\n| `autoSubmit` | Boolean | `true` | If true, selecting a day writes the input immediately. If false, Ok and Cancel buttons are added. |\n| `i18n` | Object | See below | Labels and month/weekday names. Partial objects are merged with the defaults. |\n| `events` | Array | `[]` | Strings from `Date.toDateString()`. Matching days get a `has-event` class. The default stylesheet does not style that class. |\n| `onSelect` | Function | `null` | Called when a date is selected. Receives the `Date`. |\n| `onDraw` | Function | `null` | Called after the calendar HTML is redrawn. |\n| `onInputInteraction` | Function | `null` | Called when the input is clicked or confirmed with Enter. |\n| `onConfirm` | Function | `null` | Called once when Ok is activated by click, Enter or Space. The button is created when `autoSubmit` is false. |\n| `onCancel` | Function | `null` | Called once when Cancel is activated by click, Enter or Space. The button is created when `autoSubmit` is false. |\n| `displayPlugin` | String | `null` | Set to `'docked'` for a click-to-open popover. Pair with `openByDefault: true`. |\n| `displayPluginOptions` | Object | `null` | Options for the docked plugin: `margin`, `transition`, `duration`, `align`. |\n\n#### Methods\n\n- `.toString()`: String form of the selected date, using `format`. You can pass another date and format.\n- `.setDate()`: Select a date and move the calendar to it.\n- `.gotoDate()`: Change the visible month without changing the selection.\n- `.destroy()`: Destroy the plugin instance, remove the calendar, and tear down its event handlers.\n- `.open(); / .close()`: These methods are not provided. The calendar defaults to inline presentation; `displayPlugin: 'docked'` enables docking. Both require `openByDefault: true` to show the calendar. No modal implementation is available.\n\n#### Also documented\n\nThe Date Picker section of the [API reference](https://www.expressivecss.com/llm.md) also covers Initialization, Display, Date format options, Internationalization, Calendar keyboard navigation, Date range, Multiple dates, Properties.\n\n#### Rules\n\nNo component-specific `semantics.json` rules apply to this guide.\n"
116
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Date picker\nSelect a date, a range, or several dates from a calendar.\n\nComponent ID: `date-picker`\n\n[Component documentation](https://www.expressivecss.com/datepicker.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/datepicker.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/date-pickers/overview)\n\nExample: A single date with no calendar-specific requirements can use a labeled native date input. Do not choose this picker expecting a popup: its calendar is inline and requires the documented visibility option.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/date-pickers/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/date-pickers/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/date-pickers/guidelines).\n\nSupport (2026-09-17, `llm.md#date-picker`): Inline single-date, range, and multiple-date calendar, with an optional modal dialog.\n\nWeb adaptation: Default inline presentation with optional docked and modal display plugins; displayPlugin: 'modal' opens it in a dialog from the input. No open()/close() methods.\n\nKnown boundary: Modal picker parity is not provided by the documented inline API.\n\nKnown boundary: Calendar keyboard navigation and range/multiple selection lifecycle have browser coverage. Spoken calendar context, native zoom and physical devices remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#date-picker).\n\n#### Contract\n\nAdd `date-picker` to a text input. `AutoInit()` starts every `.date-picker` except those marked `no-autoinit`.\n\nThe calendar is inline, not a modal. `open()` and `close()` are not provided. With the default options the calendar is hidden (`openByDefault: false`) and clicking the input does not reveal it. Pass `openByDefault: true` to show the calendar under the field.\n\n#### Syntax\n\n```html\n<div class=\"field\">\n <input type=\"text\" class=\"date-picker\" id=\"birthdate\">\n <label for=\"birthdate\">Birthdate</label>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `format` | String or Function | `'mmm dd, yyyy'` | Output written to the input, or a function that takes a `Date` and returns a string. |\n| `parse` | Function | `null` | Turn the current input string back into a `Date`. Receives `(value, format)`, where `format` is the configured string or an empty string for a function formatter. Blank inputs skip parsing. |\n| `isDateRange` | Boolean | `false` | Select a start date and an end date. |\n| `dateRangeEndEl` | String | `null` | Selector for an existing end-date input. If omitted, a second input is created. |\n| `isMultipleSelection` | Boolean | `false` | Toggle several dates. Extra inputs are created as dates are added. |\n| `defaultDate` | Date | `null` | Initial date to view. Falls back to the input’s current value, then today. |\n| `defaultEndDate` | Date | `null` | Initial end date when `isDateRange` is true. |\n| `setDefaultDate` | Boolean | `false` | If true, `defaultDate` is also the selected value. |\n| `setDefaultEndDate` | Boolean | `false` | If true, `defaultEndDate` is also the selected end value. |\n| `disableWeekends` | Boolean | `false` | Prevent selecting Saturday and Sunday. |\n| `disableDayFn` | Function | `null` | Return true to disable that day. Receives a `Date`. |\n| `firstDay` | Number | `0`, or the language's | First day of the week. `0` is Sunday, `1` is Monday. With a `lang` on or around the input, defaults to that language's first day where the browser knows it. |\n| `minDate` | Date | `null` | Earliest selectable date. |\n| `maxDate` | Date | `null` | Latest selectable date. |\n| `yearRange` | Number or Array | `10` | Years on either side of the viewed year, or `[minYear, maxYear]`. |\n| `yearRangeReverse` | Boolean | `false` | Sort the year list in reverse order. |\n| `isRTL` | Boolean | `false` | Render the calendar right-to-left. |\n| `showMonthAfterYear` | Boolean | `false` | Show the month after the year in the title. |\n| `showDaysInNextAndPreviousMonths` | Boolean | `false` | Render days that fall in the adjoining months. |\n| `openByDefault` | Boolean | `false` | If true, the calendar is visible. If false, it is given `display: none`. |\n| `container` | Element or String | `null` | Element or selector to render the calendar into. When set, the calendar is not hidden. |\n| `showClearBtn` | Boolean | `false` | Show a Clear button in the footer. |\n| `autoSubmit` | Boolean | `true` | If true, selecting a day writes the input immediately. If false, Ok and Cancel buttons are added. |\n| `i18n` | Object | See below | Labels and month/weekday names. Partial objects are merged with the defaults. With a `lang` on or around the input, the month and weekday names default to that language's, as `Intl` writes them; the button words stay English unless passed. |\n| `events` | Array | `[]` | Strings from `Date.toDateString()`. Matching days get a `has-event` class. The default stylesheet does not style that class. |\n| `onSelect` | Function | `null` | Called when a date is selected. Receives the `Date`. |\n| `onDraw` | Function | `null` | Called after the calendar HTML is redrawn. |\n| `onInputInteraction` | Function | `null` | Called when the input is clicked or confirmed with Enter. |\n| `onConfirm` | Function | `null` | Called once when Ok is activated by click, Enter or Space. The button is created when `autoSubmit` is false. |\n| `onCancel` | Function | `null` | Called once when Cancel is activated by click, Enter or Space. The button is created when `autoSubmit` is false. |\n| `displayPlugin` | String | `null` | Set to `'docked'` for a click-to-open popover, paired with `openByDefault: true`, or `'modal'` for a dialog with Cancel and Ok. |\n| `displayPluginOptions` | Object | `null` | Options for the docked plugin: `margin`, `transition`, `duration`, `align`. The modal plugin takes none. |\n\n#### Methods\n\n- `.toString()`: String form of the selected date, using `format`. You can pass another date and format.\n- `.setDate()`: Select a date and move the calendar to it.\n- `.gotoDate()`: Change the visible month without changing the selection.\n- `.destroy()`: Destroy the plugin instance, remove the calendar, and tear down its event handlers.\n- `.open(); / .close()`: These methods are not provided. The calendar defaults to inline presentation; `displayPlugin: 'docked'` enables docking and needs `openByDefault: true` to show the calendar. `displayPlugin: 'modal'` opens it in a dialog from the input.\n\n#### Also documented\n\nThe Date Picker section of the [API reference](https://www.expressivecss.com/llm.md) also covers Initialization, Display, Date format options, Internationalization, Calendar keyboard navigation, Date range, Multiple dates, Properties.\n\n#### Rules\n\nNo component-specific `semantics.json` rules apply to this guide.\n"
117
+ },
118
+ {
119
+ "file": "description-list.md",
120
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Description list\nA record's details as label and value pairs, in two columns that stack on compact screens.\n\nComponent ID: `description-list`\n\n[Component documentation](https://www.expressivecss.com/description-list.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/description-list.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: An order page shows Status, Ship to and Total for one order as a description list. A table of fifty orders with those columns is a data table.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-15); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-15, `llm.md#description-list`): Label and value columns with a divider per pair, a compact stacked layout, div-wrapped groups and several values per label.\n\nWeb adaptation: A native dl, dt and dd; the browser exposes the label-value pairing.\n\nKnown boundary: Two columns, the compact stack, one divider per pair, div-wrapped groups and several values per label are checked in a browser; enlarged text and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#description-list).\n\n#### Contract\n\nA `dl.description-list` pairs each `<dt>` label with the `<dd>` values after it. Labels take the first column from Medium widths up, with a divider above each pair; at Compact widths, and inside a `.query-container` narrower than 600px, each label sits above its value. A `<div>` may wrap each pair and adds no box. Several `<dd>` elements under one label stack without a divider. Use stat tiles for big numbers instead. CSS only; there is no JavaScript.\n\n#### Syntax\n\n```html\n<dl class=\"description-list\">\n <dt>Status</dt><dd>Shipped</dd>\n <dt>Ship to</dt><dd>Alvin Lee, 12 Harbor Road, Portland</dd>\n <dt>Total</dt><dd>$87.00</dd>\n</dl>\n```\n\n#### Also documented\n\nThe Description list section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `description-list-is-a-dl`: Write .description-list as a <dl> with <dt> labels and <dd> values. Divs drawn as two columns read as an unpaired run of text.\n"
89
121
  },
90
122
  {
91
123
  "file": "dialogs.md",
92
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Dialogs\nImportant prompts in a user flow. Dedicated to a single task.\n\nComponent ID: `dialogs`\n\n[Component documentation](https://www.expressivecss.com/dialogs.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/dialogs.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. [Google guidance](https://m3.material.io/components/dialogs/overview)\n\nExample: Saving successfully should not force dismissal of a dialog. Use a snackbar for that feedback and inline feedback for a persistent nonblocking issue; reserve a confirmation dialog for a decision that must precede the action.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: overview-specs-guidelines-accessibility-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/dialogs/overview).\n\nRendered prose reviewed; collapsed token tables, image-only measurements, runtime and full parity unverified. See the design priority component review for requirement-level findings.\n\n[Specification link](https://m3.material.io/components/dialogs/specs); full specs unreviewed.\n\nSupport (2026-09-07, `llm.md#dialogs`): Basic and full-screen dialogs.\n\nWeb adaptation: Native dialog.showModal()/close(); shared dialog behavior is separate from AutoInit.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#dialogs).\n\n#### Contract\n\nMaterial Design 3 dialogs, from the HTML.\n\nA `<dialog>` is a basic dialog. A heading is the headline, a `<p>` (or a wrapping `<div>`) is supporting text, and the last child `<form method=\"dialog\">` or `<nav>` is the action row. `dialog.max` is the full-screen variant. There are no `.modal`, `modal-header`, `modal-content`, or `modal-footer` classes — the element is the component.\n\nTokens follow the [M3 dialog spec](https://m3.material.io/components/dialogs/specs). The container is `surface`, 28dp corners, 280–560dp wide, elevation 3. The headline is `headline-small` / `on-surface`; supporting text is `body-medium`. The scrim is `--md-comp-scrim-color`. Actions sit at the end with an 8dp gap.\n\n#### Syntax\n\n```html\n<button type=\"button\" onclick=\"document.getElementById('dialog1').showModal()\">\n Show\n</button>\n\n<dialog id=\"dialog1\" aria-labelledby=\"use-location-services-title\">\n <h2 id=\"use-location-services-title\">Use location services?</h2>\n <p>Let the app use your location to suggest nearby stops.</p>\n <form method=\"dialog\">\n <button type=\"submit\" class=\"text\" value=\"disagree\">Disagree</button>\n <button type=\"submit\" value=\"agree\">Agree</button>\n </form>\n</dialog>\n```\n\n#### Methods\n\n- `.showModal()`: Open as a modal, with the scrim.\n- `.close()`: Close the dialog.\n\n#### Also documented\n\nThe Dialogs section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens, Full-screen.\n\n#### Rules\n\n- `dialog-is-named`: A <dialog> takes no name from its heading. Point aria-labelledby at that heading, or give it an aria-label - otherwise it opens announced as just \"dialog\".\n"
124
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Dialogs\nImportant prompts in a user flow, each for a single task.\n\nComponent ID: `dialogs`\n\n[Component documentation](https://www.expressivecss.com/dialogs.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/dialogs.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. [Google guidance](https://m3.material.io/components/dialogs/overview)\n\nExample: Saving successfully should not force dismissal of a dialog. Use a snackbar for that feedback and inline feedback for a persistent nonblocking issue; reserve a confirmation dialog for a decision that must precede the action.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: overview-specs-guidelines-accessibility-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/dialogs/overview).\n\nRendered prose reviewed; collapsed token tables, image-only measurements, runtime and full parity unverified. See the design priority component review for requirement-level findings.\n\n[Specification link](https://m3.material.io/components/dialogs/specs); full specs unreviewed.\n\nSupport (2026-09-07, `llm.md#dialogs`): Basic and full-screen dialogs.\n\nWeb adaptation: Native dialog.showModal()/close(); shared dialog behavior is separate from AutoInit.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#dialogs).\n\n#### Contract\n\nMaterial Design 3 dialogs, from the HTML.\n\nA `<dialog>` is a basic dialog. A heading is the headline, a `<p>` (or a wrapping `<div>`) is supporting text, and the last child `<form method=\"dialog\">` or `<nav>` is the action row. `dialog.max` is the full-screen variant. There are no `.modal`, `modal-header`, `modal-content`, or `modal-footer` classes — the element is the component.\n\nTokens follow the [M3 dialog spec](https://m3.material.io/components/dialogs/specs). The container is `surface`, 28dp corners, 280–560dp wide, elevation 3. The headline is `headline-small` / `on-surface`; supporting text is `body-medium`. The scrim is `--md-comp-scrim-color`. Actions sit at the end with an 8dp gap. A basic dialog fades in on the fast effects spring and grows from 96% on the default spatial spring (`--md-comp-basic-dialog-effects-easing` and `-effects-duration`, `--md-comp-basic-dialog-easing` and `-duration`), so `motion=\"expressive\"` reaches it. It closes at once and opens without moving when reduced motion is requested. `dialog.max` and sheets keep their own motion.\n\n#### Syntax\n\n```html\n<button type=\"button\" onclick=\"document.getElementById('dialog1').showModal()\">\n Show\n</button>\n\n<dialog id=\"dialog1\" aria-labelledby=\"use-location-services-title\">\n <h2 id=\"use-location-services-title\">Use location services?</h2>\n <p>Let the app use your location to suggest nearby stops.</p>\n <form method=\"dialog\">\n <button type=\"submit\" class=\"text\" value=\"disagree\">Disagree</button>\n <button type=\"submit\" value=\"agree\">Agree</button>\n </form>\n</dialog>\n```\n\n#### Methods\n\n- `.showModal()`: Open as a modal, with the scrim.\n- `.close()`: Close the dialog.\n\n#### Also documented\n\nThe Dialogs section of the [API reference](https://www.expressivecss.com/llm.md) also covers Confirm from script, Tokens, Full-screen.\n\n#### Rules\n\n- `dialog-is-named`: A <dialog> takes no name from its heading. Point aria-labelledby at that heading, or give it an aria-label - otherwise it opens announced as just \"dialog\".\n"
93
125
  },
94
126
  {
95
127
  "file": "drag-handle.md",
96
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Drag handle\nThe bar that says a thing can be dragged — and nothing that does the dragging.\n\nComponent ID: `drag-handle`\n\n[Component documentation](https://www.expressivecss.com/drag-handle.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/drag-handle.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Related Google guidance](https://m3.material.io/components/bottom-sheets/overview)\n\n#### Material mapping\n\nRelationship: related. Related guidance is not a dedicated component specification.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/bottom-sheets/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/bottom-sheets/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/bottom-sheets/guidelines).\n\nSupport (2026-09-13, `llm.md#drag-handle`): Decorative grip and actionable button grip.\n\nWeb adaptation: The host element determines semantics; no general dragging engine.\n\nKnown boundary: A grip outside supported sheets implements neither dragging nor keyboard or non-drag pointer alternatives.\n\nKnown boundary: Standalone grip styling supplies no drag, resize or reorder operation.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#drag-handle).\n\n#### Contract\n\nThe bar that makes something legible as draggable: a bottom sheet's grabber, the gutter between two panes, the grip on a reorderable row.\n\nWhich element you write *is* the semantic. A `<span class=\"drag-handle\" aria-hidden=\"true\">` is decoration and is the usual case — the handle draws a bar and contains no text, so exposed it arrives in the reading order as an unlabelled blank. A `<button class=\"drag-handle\">` with an `aria-label` is a control, written only when activating it does something — inside a bottom sheet it always does, and everywhere else that is the page's to arrange. Never `aria-hidden` a button: that hides it from assistive technology without taking it out of the tab order. The hover, focus and pressed states are scoped to the button spelling, so a decorative handle does not light up under a passing pointer.\n\n**Nothing here drags.** No script belongs to this component and ExpressiveCSS ships no reordering behaviour. The one thing a handle is wired to is the bottom sheet's, below. Provide both keyboard operation and a single-pointer alternative without dragging, such as clickable \"Move up\" / \"Move down\" buttons or a position input. Arrow keys alone cover only the keyboard path. [WCAG 2.2 SC 2.5.7](https://www.w3.org/WAI/WCAG22/Understanding/dragging-movements.html) requires the pointer alternative unless dragging is essential or the unmodified user agent owns the functionality. Verify the alternatives independently; the handle does not implement either one.\n\n#### Syntax\n\n```html\n<section aria-label=\"Editor\">…</section>\n<span class=\"drag-handle\" aria-hidden=\"true\"></span>\n<section aria-label=\"Preview\">…</section>\n```\n\n#### Rules\n\n- `drag-handle-is-hidden-or-a-control`: A drag handle has no text and reports nothing, so on any element but a <button> it is decoration and needs aria-hidden=\"true\". Left exposed it arrives as an unlabelled blank in the reading order. Make it a <button> with a name if it is meant to be operated.\n- `drag-handle-button-is-not-hidden`: aria-hidden on a <button> hides it from assistive technology without taking it out of the tab order, so keyboard focus lands on something that is not there. Decoration is a <span>; a control stays exposed.\n- `drag-handle-button-is-named`: The handle is a bar drawn in CSS - there is no text inside it to be named by. A handle worth making a control is worth an aria-label saying what it moves.\n"
128
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Drag handle\nThe bar that marks a thing as draggable. It does not do the dragging.\n\nComponent ID: `drag-handle`\n\n[Component documentation](https://www.expressivecss.com/drag-handle.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/drag-handle.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Related Google guidance](https://m3.material.io/components/bottom-sheets/overview)\n\n#### Material mapping\n\nRelationship: related. Related guidance is not a dedicated component specification.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/bottom-sheets/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/bottom-sheets/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/bottom-sheets/guidelines).\n\nSupport (2026-09-13, `llm.md#drag-handle`): Decorative grip and actionable button grip.\n\nWeb adaptation: The host element determines semantics; no general dragging engine.\n\nKnown boundary: A grip outside supported sheets implements neither dragging nor keyboard or non-drag pointer alternatives.\n\nKnown boundary: Standalone grip styling supplies no drag, resize or reorder operation.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#drag-handle).\n\n#### Contract\n\nThe bar that makes something legible as draggable: a bottom sheet's grabber, the gutter between two panes, the grip on a reorderable row.\n\nWhich element you write *is* the semantic. A `<span class=\"drag-handle\" aria-hidden=\"true\">` is decoration and is the usual case — the handle draws a bar and contains no text, so exposed it arrives in the reading order as an unlabelled blank. A `<button class=\"drag-handle\">` with an `aria-label` is a control, written only when activating it does something — inside a bottom sheet it always does, and everywhere else that is the page's to arrange. Never `aria-hidden` a button: that hides it from assistive technology without taking it out of the tab order. The hover, focus and pressed states are scoped to the button spelling, so a decorative handle does not light up under a passing pointer.\n\n**Nothing here drags on its own.** Four places wire it: the bottom sheet below, the pane layouts, where a `<button class=\"drag-handle\">` between two panes resizes them (see Panes, \"Resizing\"), data tables, where one ending a header cell resizes its column (see Data table, \"Column resize\"), and sortable lists, where a decorative handle drags its row and Move up and Move down buttons cover keyboard and single-pointer use (see Lists, \"Sortable\"). Anywhere else the page supplies reordering. Provide both keyboard operation and a single-pointer alternative without dragging, such as clickable \"Move up\" / \"Move down\" buttons or a position input. Arrow keys alone cover only the keyboard path. [WCAG 2.2 SC 2.5.7](https://www.w3.org/WAI/WCAG22/Understanding/dragging-movements.html) requires the pointer alternative unless dragging is essential or the unmodified user agent owns the functionality. Verify the alternatives independently; the handle does not implement either one.\n\n#### Syntax\n\n```html\n<section aria-label=\"Editor\">…</section>\n<span class=\"drag-handle\" aria-hidden=\"true\"></span>\n<section aria-label=\"Preview\">…</section>\n```\n\n#### Rules\n\n- `drag-handle-is-hidden-or-a-control`: A drag handle has no text and reports nothing, so on any element but a <button> it is decoration and needs aria-hidden=\"true\". Left exposed it arrives as an unlabelled blank in the reading order. Make it a <button> with a name if it is meant to be operated.\n- `drag-handle-button-is-not-hidden`: aria-hidden on a <button> hides it from assistive technology without taking it out of the tab order, so keyboard focus lands on something that is not there. Decoration is a <span>; a control stays exposed.\n- `drag-handle-button-is-named`: The handle is a bar drawn in CSS - there is no text inside it to be named by. A handle worth making a control is worth an aria-label saying what it moves.\n"
97
129
  },
98
130
  {
99
131
  "file": "drop-zone.md",
100
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Drop zone\nA large target for choosing or dropping files, with a list of what was chosen.\n\nComponent ID: `drop-zone`\n\n[Component documentation](https://www.expressivecss.com/drop-zone.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/drop-zone.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. No dedicated entry in the reviewed Google component inventory.\n\nExample: An optional attachment on a support form is a file input in the form. A drop zone would give one small field the weight of the whole page.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#drop-zone`): A label-sized native file input with a dragover state and a list of chosen file names and locale-formatted sizes.\n\nWeb adaptation: The native input covers the label, so clicks, keys and drops are the browser's; the shared Forms behavior marks the drag and fills the list.\n\nKnown boundary: Input coverage, the dragover state, the file list and keyboard focus are checked in a browser with synthetic events; real operating-system drags and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#drop-zone).\n\n#### Contract\n\nA `.drop-zone` holds a `<label>` with an icon, a prompt and a native `<input type=\"file\">`, and an optional `<ul class=\"drop-zone-files\">` after it. The input is stretched invisibly over the whole label, so the browser takes a click, Enter, Space or a dropped file exactly as it does on a plain file input, and the label text names it. The Forms behavior adds `dragover` to the zone while files are dragged over it and lists each chosen file's name and size, formatted for the page's locale, in `.drop-zone-files`. The root bundle installs it on load, and the modular entry with `Forms.Init()`. It listens on the document, so zones added later work too. Resetting the form empties the list.\n\nThe browser does not apply `accept` to dropped files, so check type and size where the files are used. A dropped file replaces the selection, as it does on any file input; add `multiple` to take several at once.\n\n#### Syntax\n\n```html\n<div class=\"drop-zone\">\n <label>\n <span class=\"material-symbols\" aria-hidden=\"true\">upload_file</span>\n Drop files here or browse\n <small>PDF or PNG, up to 10 MB</small>\n <input type=\"file\" multiple accept=\".pdf,.png\">\n </label>\n <ul class=\"drop-zone-files\" aria-live=\"polite\"></ul>\n</div>\n```\n\n#### Also documented\n\nThe Drop zone section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `drop-zone-input-is-in-the-label`: Put the file input inside the .drop-zone <label>. The label names the input and the input covers the label; a loose input is neither named nor a drop target.\n- `drop-zone-files-are-a-list`: Write .drop-zone-files as a <ul>. The behavior fills it with one <li> per file, and a list tells assistive technology how many files were chosen.\n"
132
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Drop zone\nA large target for choosing or dropping files, with a list of what was chosen.\n\nComponent ID: `drop-zone`\n\n[Component documentation](https://www.expressivecss.com/drop-zone.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/drop-zone.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. No dedicated entry in the reviewed Google component inventory.\n\nExample: An optional attachment on a support form is a file input in the form. A drop zone would give one small field the weight of the whole page.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#drop-zone`): A label-sized native file input with a dragover state and a list of chosen file names and locale-formatted sizes.\n\nWeb adaptation: The native input covers the label, so clicks, keys and drops are the browser's; the shared Forms behavior marks the drag and fills the list.\n\nKnown boundary: Input coverage, the dragover state, the file list and keyboard focus are checked in a browser with synthetic events; real operating-system drags and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#drop-zone).\n\n#### Contract\n\nA large target for choosing or dropping files, with an optional list and independent type, size and count limits.\n\nA `.drop-zone` holds a `<label>` with an icon, a prompt and a native `<input type=\"file\">`, and an optional `<ul class=\"drop-zone-files\">` after it. The input covers the label, so clicking, keyboard input and file dropping reach the native control. Forms marks dragover and lists each selected file's name and localized size. The root bundle installs Forms on load; modular consumers call `Forms.Init()`. Document-level input/change handling also supports dynamically inserted zones and open shadow roots.\n\nA dropped file replaces the selection; add multiple to take several files. `accept` alone is a picker hint. Add data-validate-types for type checks, data-max-size for a per-file byte limit or data-max-files for a count limit. Unmarked zones retain their existing behavior. Reset clears lists after dispatch/default handling; cancellation preserves the current view, and a newer selection keeps its state.\n\n#### Syntax\n\n```html\n<div class=\"drop-zone\">\n <label>\n <span class=\"material-symbols\" aria-hidden=\"true\">upload_file</span>\n Drop files here or browse\n <small>PDF or PNG files</small>\n <input type=\"file\" multiple accept=\".pdf,.png\">\n </label>\n <ul class=\"drop-zone-files\" aria-live=\"polite\"></ul>\n</div>\n```\n\n#### Also documented\n\nThe Drop zone section of the [API reference](https://www.expressivecss.com/llm.md) also covers Type validation, Per-file size limit, File count, Tokens.\n\n#### Rules\n\n- `drop-zone-input-is-in-the-label`: Put the file input inside the .drop-zone <label>. The label names the input and the input covers the label; a loose input is neither named nor a drop target.\n- `drop-zone-files-are-a-list`: Write .drop-zone-files as a <ul>. The behavior fills it with one <li> per file, and a list tells assistive technology how many files were chosen.\n- `drop-zone-type-error-is-live-status`: An opted-in file-validation zone needs a direct [data-error] area with role=\"status\" and aria-live=\"polite\". The runtime writes real error text there and describes the file input with it.\n"
101
133
  },
102
134
  {
103
135
  "file": "empty-state.md",
104
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Empty state\nWhat a view shows when it has nothing to list: an icon, a heading, text and actions.\n\nComponent ID: `empty-state`\n\n[Component documentation](https://www.expressivecss.com/empty-state.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/empty-state.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A failed save after pressing Save is feedback for a snackbar or the form. An empty state would replace the user's content with a placeholder.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#empty-state`): Centered icon in a tonal circle or an illustration, a heading, supporting text and a wrapping row of actions.\n\nWeb adaptation: A plain div around authored heading, text and buttons; the page picks the heading level.\n\nKnown boundary: Centering, the icon circle and the actions row are checked in a browser; announcements when a search empties a list and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#empty-state).\n\n#### Contract\n\nA `.empty-state` centers its content in the space the missing items would fill. In order, it holds an optional leading icon, which sits in a 96px tonal circle, or an `<img>` illustration; a heading at the level the page outline needs; a `<p>` of supporting text; and a `.empty-state-actions` row of buttons. CSS only; there is no JavaScript.\n\nSay what is empty and what the user can do about it. Use it for a first run, a search with no results, or content the user cannot see yet. Hide the icon from assistive technology, and give an illustration `alt=\"\"` when the heading already says what it shows.\n\n#### Syntax\n\n```html\n<div class=\"empty-state\">\n <span class=\"material-symbols\" aria-hidden=\"true\">inbox</span>\n <h2>No messages yet</h2>\n <p>Messages from your team show up here.</p>\n <div class=\"empty-state-actions\">\n <button class=\"filled\">New message</button>\n </div>\n</div>\n```\n\n#### Also documented\n\nThe Empty state section of the [API reference](https://www.expressivecss.com/llm.md) also covers Search with no results, Tokens.\n\n#### Rules\n\n- `empty-state-image-has-alt`: Give an empty-state illustration alt=\"\" when the heading already says what it shows, or alt text when the image adds information.\n"
136
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Empty state\nWhat a view shows when it has nothing to list: an icon, a heading, text and actions.\n\nComponent ID: `empty-state`\n\n[Component documentation](https://www.expressivecss.com/empty-state.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/empty-state.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A failed save after pressing Save is feedback for a snackbar or the form. An empty state would replace the user's content with a placeholder.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#empty-state`): Centered icon in a tonal circle or an illustration, a heading, supporting text and a wrapping row of actions.\n\nWeb adaptation: A plain div around authored heading, text and buttons; the page picks the heading level.\n\nKnown boundary: Centering, the icon circle and the actions row are checked in a browser; announcements when a search empties a list and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#empty-state).\n\n#### Contract\n\nA `.empty-state` centers its content in the space the missing items would fill. In order, it holds an optional leading icon, which sits in a 96px tonal circle, or an `<img>` illustration; a heading at the level the page outline needs; a `<p>` of supporting text; and a `.empty-state-actions` row of buttons. CSS only; there is no JavaScript.\n\nSay what is empty and what the user can do about it. Use it for a first run, a search with no results, or content the user cannot see yet. Hide the icon from assistive technology, and give an illustration `alt=\"\"` when the heading already says what it shows.\n\n#### Syntax\n\n```html\n<div class=\"empty-state\">\n <span class=\"material-symbols\" aria-hidden=\"true\">inbox</span>\n <h2>No messages yet</h2>\n <p>Messages from your team show up here.</p>\n <div class=\"empty-state-actions\">\n <button class=\"filled\">New message</button>\n </div>\n</div>\n```\n\n#### Also documented\n\nThe Empty state section of the [API reference](https://www.expressivecss.com/llm.md) also covers Search with no results, Tokens.\n\n#### Rules\n\n- `empty-state-image-has-alt`: Give an empty-state illustration alt=\"\" when the heading already says what it shows, or alt text when the image adds information.\n"
105
137
  },
106
138
  {
107
139
  "file": "fab.md",
108
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Floating action button\nA circular action that can open a menu of related shortcuts.\n\nComponent ID: `fab`\n\n[Component documentation](https://www.expressivecss.com/floating-action-button.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/floating-action-button.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/floating-action-button/overview)\n\nExample: Speed dials have been removed. Replace legacy fab or fixed-action-btn wrappers with fab-menu and labelled actions; use a toolbar for persistent commands.\n\n- expanded window: variant. Use the extended FAB when the action benefits from a visible label.\n\n#### Material mapping\n\nRelationship: component. Google no longer recommends small FABs, baseline extended FABs or surface-colored FABs. The 40dp FAB and sizeless extend variant are removed. Use circle extra for the 56dp FAB, or its medium and large sizes. Extended FABs require extend small, extend medium or extend large; the small extended FAB remains recommended. Use primary/secondary/tertiary container roles instead of surface colors. Use fab-menu for labelled related actions; speed-dial APIs are removed.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/floating-action-button/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/floating-action-button/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/floating-action-button/guidelines).\n\n[Related Google component](https://m3.material.io/components/extended-fab/overview).\n\n[Related Google component](https://m3.material.io/components/fab-menu/overview).\n\nSupport (2026-09-13, `llm.md#floating-action-button`): FAB sizes, extended FABs, and menus of related actions.\n\nWeb adaptation: Floating action behavior uses the documented FAB runtime.\n\nKnown boundary: Full visual parity and spoken assistive-technology output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#fab).\n\n#### Contract\n\nUse a standalone FAB for the primary action or a FAB menu for related labelled actions.\n\n`fab-menu` is the FAB that expands into a list of labelled actions. The trigger is an ordinary FAB; the actions are a `<ul>` (or `<menu>`) of pills, each an icon and a label at 56dp on a full corner. Click the FAB to expand; click it again, click outside, or press Escape to collapse.\n\nExpanded is the framework's state, not the author's: the constructor stamps `aria-expanded` on the trigger and `open()` / `close()` move it along with the `active` class. Do not write either into the markup.\n\n#### Syntax\n\n```html\n<div class=\"fab-menu\">\n <button type=\"button\" class=\"button extra circle\" aria-label=\"Create\">\n <span class=\"material-symbols\" aria-hidden=\"true\">add</span>\n <span class=\"material-symbols\" aria-hidden=\"true\">close</span>\n </button>\n <ul>\n <li><button type=\"button\"><span class=\"material-symbols\" aria-hidden=\"true\">mode_edit</span><span>Compose</span></button></li>\n <li><button type=\"button\"><span class=\"material-symbols\" aria-hidden=\"true\">image</span><span>Add photo</span></button></li>\n </ul>\n</div>\n```\n\n#### Methods\n\n- `.open()`: Opens the FAB menu.\n- `.close()`: Closes the FAB menu.\n- `.destroy()`: Destroy the plugin instance and tear down its event handlers.\n\n#### Also documented\n\nThe Floating Action Button section of the [API reference](https://www.expressivecss.com/llm.md) also covers FAB menu, Initialization, Properties, Speed-dial migration.\n\n#### Rules\n\n- `icon-only-control-is-named`: Every icon is hidden from assistive technology, so a control whose only content is one has no name left. Give it an aria-label.\n- `fab-no-small-variant`: The 40dp small FAB has been removed. Use circle extra for a 56dp FAB.\n- `extended-fab-requires-size`: The baseline extended FAB has been removed. Specify extend small, extend medium or extend large.\n- `fab-no-surface-color`: Use a primary, secondary or tertiary container color for a FAB instead of a surface color.\n- `fab-menu-not-a-composite-widget`: The FAB menu takes no composite role; its actions are reached with Tab, not arrow keys.\n- `fab-menu-expanded-is-not-authored`: Expanded is dynamic state, so the framework owns it. The constructor stamps aria-expanded on the trigger and every open() and close() rewrites it; authoring it states a value that is about to be overwritten.\n"
140
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Floating action button\nA circular action that can open a menu of related shortcuts.\n\nComponent ID: `fab`\n\n[Component documentation](https://www.expressivecss.com/floating-action-button.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/floating-action-button.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/floating-action-button/overview)\n\nExample: Speed dials have been removed. Replace legacy fab or fixed-action-btn wrappers with fab-menu and labelled actions; use a toolbar for persistent commands.\n\n- expanded window: variant. Use the extended FAB when the action benefits from a visible label.\n\n#### Material mapping\n\nRelationship: component. Google no longer recommends small FABs, baseline extended FABs or surface-colored FABs. The 40dp FAB and sizeless extend variant are removed. Use circle extra for the 56dp FAB, or its medium and large sizes. Extended FABs require extend small, extend medium or extend large; the small extended FAB remains recommended. Use primary/secondary/tertiary container roles instead of surface colors. Use fab-menu for labelled related actions; speed-dial APIs are removed.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/floating-action-button/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/floating-action-button/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/floating-action-button/guidelines).\n\n[Related Google component](https://m3.material.io/components/extended-fab/overview).\n\n[Related Google component](https://m3.material.io/components/fab-menu/overview).\n\nSupport (2026-09-13, `llm.md#floating-action-button`): FAB sizes, extended FABs, and menus of related actions.\n\nWeb adaptation: Floating action behavior uses the documented FAB runtime.\n\nKnown boundary: Full visual parity and spoken assistive-technology output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#fab).\n\n#### Contract\n\nUse a standalone FAB for the primary action or a FAB menu for related labelled actions.\n\n`fab-menu` is the FAB that expands into a list of labelled actions. The trigger is an ordinary FAB; the actions are a `<ul>` (or `<menu>`) of pills, each an icon and a label at 56dp on a full corner. Click the FAB to expand; click it again, click outside, or press Escape to collapse.\n\nExpanded is the framework's state, not the author's: the constructor stamps `aria-expanded` on the trigger and `open()` / `close()` move it along with the `active` class. Do not write either into the markup.\n\n#### Syntax\n\n```html\n<div class=\"fab-menu\">\n <button type=\"button\" class=\"button extra circle\" aria-label=\"Create\">\n <span class=\"material-symbols\" aria-hidden=\"true\">add</span>\n <span class=\"material-symbols\" aria-hidden=\"true\">close</span>\n </button>\n <ul>\n <li><button type=\"button\"><span class=\"material-symbols\" aria-hidden=\"true\">mode_edit</span><span>Compose</span></button></li>\n <li><button type=\"button\"><span class=\"material-symbols\" aria-hidden=\"true\">image</span><span>Add photo</span></button></li>\n </ul>\n</div>\n```\n\n#### Methods\n\n- `.open()`: Opens the FAB menu.\n- `.close()`: Closes the FAB menu.\n- `.destroy()`: Destroy the plugin instance and tear down its event handlers.\n\n#### Also documented\n\nThe Floating Action Button section of the [API reference](https://www.expressivecss.com/llm.md) also covers FAB menu, Initialization, Properties, Speed-dial migration.\n\n#### Rules\n\n- `icon-only-control-is-named`: Every icon is hidden from assistive technology, so a control whose only content is one has no name left. Give it an aria-label.\n- `fab-no-small-variant`: The 40dp small FAB has been removed. Use circle extra for a 56dp FAB.\n- `extended-fab-requires-size`: The baseline extended FAB has been removed. Specify extend small, extend medium or extend large.\n- `fab-no-surface-color`: Use a primary, secondary or tertiary container color for a FAB instead of a surface color.\n- `fab-menu-not-a-composite-widget`: The FAB menu takes no composite role; its actions are reached with Tab, not arrow keys.\n- `fab-menu-expanded-is-not-authored`: Expanded is dynamic state, so the framework owns it. The constructor stamps aria-expanded on the trigger and every open() and close() rewrites it; authoring it states a value that is about to be overwritten.\n"
109
141
  },
110
142
  {
111
143
  "file": "fieldsets.md",
112
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Fieldsets\nGrouped form sections, from the HTML.\n\nComponent ID: `fieldsets`\n\n[Component documentation](https://www.expressivecss.com/fieldsets.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/fieldsets.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Related Google guidance](https://m3.material.io/components/text-fields/overview)\n\nExample: A promotional feature block needs a section or card. Use a fieldset when a shared legend explains related inputs, such as delivery preferences.\n\n#### Material mapping\n\nRelationship: related. Related guidance is not a dedicated component specification.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/text-fields/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/text-fields/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/text-fields/guidelines).\n\nSupport (2026-09-13, `llm.md#fieldsets`): Outlined, filled, and rounded form groups.\n\nWeb adaptation: Native fieldset/legend; related text-field tokens do not make this an M3 component.\n\nKnown boundary: Named native groups and disabled descendants are checked; long legends and nested groups remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#fieldsets).\n\n#### Contract\n\nA `<fieldset>` is the group. A `<legend>` is the headline. Everything else is a field — `.field`, radios, switches, a `.inline` row of radios, a trailing `<small>` as supporting text. There is no wrapper class. They are CSS only. There is no JavaScript component and nothing to AutoInit.\n\nMaterial Design 3 has no fieldset component. Tokens follow the outlined grouping container used around related form content, and the [outlined text-field](https://m3.material.io/components/text-fields/specs) shape so a group of fields matches the fields. The container is 4dp corners and a 1dp `outline-variant` stroke. The legend is `title-small` / `on-surface`. Supporting text is `body-small`. Padding is 16dp; children sit 16dp apart. Disabled is 38%.\n\n#### Syntax\n\n```html\n<fieldset>\n <legend>Name</legend>\n <div class=\"field\">\n <input id=\"first\" type=\"text\" placeholder=\" \">\n <label for=\"first\">First name</label>\n </div>\n <div class=\"field\">\n <input id=\"last\" type=\"text\" placeholder=\" \">\n <label for=\"last\">Last name</label>\n </div>\n</fieldset>\n```\n\n#### Also documented\n\nThe Fieldsets section of the [API reference](https://www.expressivecss.com/llm.md) also covers Choices, Variants, Disabled.\n\n#### Rules\n\n- `fieldset-has-legend`: A fieldset without a legend groups controls visually but names nothing.\n"
144
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Fieldsets\nGrouped form sections, from the HTML.\n\nComponent ID: `fieldsets`\n\n[Component documentation](https://www.expressivecss.com/fieldsets.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/fieldsets.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Related Google guidance](https://m3.material.io/components/text-fields/overview)\n\nExample: A promotional feature block needs a section or card. Use a fieldset when a shared legend explains related inputs, such as delivery preferences.\n\n#### Material mapping\n\nRelationship: related. Related guidance is not a dedicated component specification.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/text-fields/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/text-fields/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/text-fields/guidelines).\n\nSupport (2026-09-13, `llm.md#fieldsets`): Outlined, filled, and rounded form groups.\n\nWeb adaptation: Native fieldset/legend; related text-field tokens do not make this an M3 component.\n\nKnown boundary: Named native groups and disabled descendants are checked; long legends and nested groups remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#fieldsets).\n\n#### Contract\n\nA `<fieldset>` is the group. A `<legend>` is the headline. Everything else is a field — `.field`, radios, switches, a `.inline` row of radios, a trailing `<small>` as supporting text. There is no wrapper class. They are CSS only. There is no JavaScript component and nothing to AutoInit.\n\nMaterial Design 3 has no fieldset component. Tokens follow the outlined grouping container used around related form content, and the [outlined text-field](https://m3.material.io/components/text-fields/specs) shape so a group of fields matches the fields. The container is 4dp corners and a 1dp `outline-variant` stroke. The legend is `title-small` / `on-surface`. Supporting text is `body-small`. Padding is 16dp; children sit 16dp apart. Disabled is 38%.\n\n#### Syntax\n\n```html\n<fieldset>\n <legend>Name</legend>\n <div class=\"field\">\n <input id=\"first\" type=\"text\" placeholder=\" \">\n <label for=\"first\">First name</label>\n </div>\n <div class=\"field\">\n <input id=\"last\" type=\"text\" placeholder=\" \">\n <label for=\"last\">Last name</label>\n </div>\n</fieldset>\n```\n\n#### Also documented\n\nThe Fieldsets section of the [API reference](https://www.expressivecss.com/llm.md) also covers Choices, Variants, Disabled.\n\n#### Rules\n\n- `fieldset-has-legend`: A fieldset without a legend groups controls visually but names nothing.\n"
113
145
  },
114
146
  {
115
147
  "file": "footer.md",
116
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Footer\nSite navigation and extra information at the end of a page.\n\nComponent ID: `footer`\n\n[Component documentation](https://www.expressivecss.com/footer.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/footer.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Home/Search/Profile should not require scrolling to a footer. Use a navigation bar or rail for those destinations.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-09-13); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. Full visual parity and spoken output unverified.\n\nSupport (2026-09-13, `llm.md#footer`): Native footer with navigation columns and copyright content.\n\nWeb adaptation: Web page landmark styled with theme roles.\n\nKnown boundary: A document-level contentinfo landmark is checked; section-scoped footers and responsive legal links remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#footer).\n\n#### Contract\n\nFooters are a good place for site navigation and extra information. This is where people look after they finish the page, or when they want more about the site.\n\nPut the page in the three HTML5 landmarks `header`, `main`, and `footer`. Anatomy is the HTML: a bare `<footer>` is the component, each `<nav>` is a column of links with an `<h2>` heading, and a trailing `<small>` is the copyright bar. There is no `page-footer` or `footer-copyright` class, and no JavaScript component.\n\nExpressive’s footer is unfilled by default. It draws a dashed top border and uses the theme tokens for paragraph and link color — not a solid primary bar.\n\n#### Syntax\n\n```html\n<footer>\n <section>\n <h2>Product</h2>\n <p>A column with no links is not navigation — it is a section with a heading.</p>\n </section>\n <nav aria-labelledby=\"footer-links\">\n <h2 id=\"footer-links\">Links</h2>\n <a href=\"/about\">About</a>\n <a href=\"/contact\">Contact</a>\n </nav>\n <small>\n <span>&copy; 2026 Copyright Text</span>\n <a href=\"/privacy\">Privacy</a>\n </small>\n</footer>\n```\n\n#### Also documented\n\nThe Footer section of the [API reference](https://www.expressivecss.com/llm.md) also covers Sticky Footer.\n\n#### Rules\n\n- `footer-nav-has-links`: A footer column with no links is not navigation - it is a <section> with a heading. Marking it <nav> spends a landmark on prose.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
148
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Footer\nSite navigation and extra information at the end of a page.\n\nComponent ID: `footer`\n\n[Component documentation](https://www.expressivecss.com/footer.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/footer.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Home/Search/Profile should not require scrolling to a footer. Use a navigation bar or rail for those destinations.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-09-13); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. Full visual parity and spoken output unverified.\n\nSupport (2026-09-13, `llm.md#footer`): Native footer with navigation columns and copyright content.\n\nWeb adaptation: Web page landmark styled with theme roles.\n\nKnown boundary: A document-level contentinfo landmark is checked; section-scoped footers and responsive legal links remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#footer).\n\n#### Contract\n\nFooters are a good place for site navigation and extra information. This is where people look after they finish the page, or when they want more about the site.\n\nPut the page in the three HTML5 landmarks `header`, `main`, and `footer`. Anatomy is the HTML: a bare `<footer>` is the component, each `<nav>` is a column of links with an `<h2>` heading, and a trailing `<small>` is the copyright bar. There is no `page-footer` or `footer-copyright` class, and no JavaScript component.\n\nExpressive’s footer is unfilled by default. It draws a dashed top border and uses the theme tokens for paragraph and link color — not a solid primary bar.\n\n#### Syntax\n\n```html\n<footer>\n <section>\n <h2>Product</h2>\n <p>A column with no links is not navigation — it is a section with a heading.</p>\n </section>\n <nav aria-labelledby=\"footer-links\">\n <h2 id=\"footer-links\">Links</h2>\n <a href=\"/about\">About</a>\n <a href=\"/contact\">Contact</a>\n </nav>\n <small>\n <span>&copy; 2026 Copyright Text</span>\n <a href=\"/privacy\">Privacy</a>\n </small>\n</footer>\n```\n\n#### Also documented\n\nThe Footer section of the [API reference](https://www.expressivecss.com/llm.md) also covers Sticky footer.\n\n#### Rules\n\n- `footer-nav-has-links`: A footer column with no links is not navigation - it is a <section> with a heading. Marking it <nav> spends a landmark on prose.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
149
+ },
150
+ {
151
+ "file": "funnel-chart.md",
152
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Funnel chart\nSteps people take in order, each drawn as a stage as wide as the people who reach it.\n\nComponent ID: `funnel-chart`\n\n[Component documentation](https://www.expressivecss.com/funnel-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/funnel-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Visits, signups, trials and paid accounts this month is a funnel chart. Signups by channel is a bar chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-25); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-25, `llm.md#funnel-chart`): Stages drawn from a figure's data table, one per row from the second column in table order, centered and as wide as their value against the largest, gaps and values of zero or less left without a stage, row labels at the inline start, a pointer and keyboard tooltip that adds each stage's percentage of the first row, update() width motion and data-watch.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the stages are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Stage widths and order in left-to-right and right-to-left pages, gaps, labels, the tooltip and its percentages, keyboard steps, the pointer, update() motion and data-watch are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. There are no tapered stages, colors per row, step-to-step conversion rates or horizontal funnels.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#funnel-chart).\n\n#### Contract\n\nA `<figure class=\"funnel-chart\">` takes a `<figcaption>` and a `<table>`. The first column labels each row and the second holds its value. `AutoInit()` draws each row as a stage, from the top down in table order, centered and as wide as its value against the largest one, which fills the chart. Each row's label sits beside its stage at the inline start. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"funnel-chart\">\n <figcaption>Signups this month</figcaption>\n <table>\n <thead>\n <tr><th>Step</th><th>People</th><th>Revenue</th></tr>\n </thead>\n <tbody>\n <tr><th>Visited</th><td>12,000</td><td>$0</td></tr>\n <tr><th>Signed up</th><td>4,800</td><td>$0</td></tr>\n <tr><th>Paid</th><td>640</td><td>$19,200</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Funnel chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `funnel-chart-is-a-figure-with-a-table`: Write .funnel-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `funnel-chart-has-a-caption`: Name a .funnel-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
117
153
  },
118
154
  {
119
155
  "file": "heatmap-chart.md",
120
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Heatmap chart\nA data table drawn as a grid of cells shaded by value, for patterns across two categories such as days and hours.\n\nComponent ID: `heatmap-chart`\n\n[Component documentation](https://www.expressivecss.com/heatmap-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/heatmap-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Visits by day of the week and time of day is a heatmap chart. Visits per day over a month is a line or column chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-07); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-07, `llm.md#heatmap-chart`): Every body cell of a figure's data table drawn as a shaded grid cell, with row headers beside the grid, column headers below, a scale naming the lowest and highest cells, values, data-label short or hidden labels, data-min and data-max, gaps, and a pointer and keyboard tooltip that moves cell by cell.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the grid, labels and scale are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, cell shades and gaps, the fixed scale, values and their text color, short and running labels, the scale ends, RTL, page overflow from a wide table, the tooltip, pointer and keyboard movement round the grid and destroy are checked in Chromium, Firefox and WebKit; screen reader output, forced colors and enlarged text remain unverified. A wide grid narrows its cells rather than scrolling.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#heatmap-chart).\n\n#### Contract\n\nA `<figure class=\"heatmap-chart\">` takes the same `<figcaption>` and `<table>` as a column chart. `AutoInit()` draws every body cell as a cell of a grid, one grid row per table row and one grid column per value column, shaded from the lowest value to the highest. The row headers sit at the inline start, the column headers below, and a scale under the grid names the lowest and highest cells. A tooltip shows the cell under the pointer. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"heatmap-chart values\">\n <figcaption>Revenue by rep, per month</figcaption>\n <table>\n <thead>\n <tr><th>Rep</th><th>Jan</th><th>Feb</th><th>Mar</th></tr>\n </thead>\n <tbody>\n <tr><th>Priya</th><td data-value=\"45000\">$45K</td><td data-value=\"58000\">$58K</td><td data-value=\"85000\">$85K</td></tr>\n <tr><th>Daniel</th><td data-value=\"88000\">$88K</td><td data-value=\"34000\">$34K</td><td data-value=\"45000\">$45K</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Heatmap chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `heatmap-chart-is-a-figure-with-a-table`: Write .heatmap-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `heatmap-chart-has-a-caption`: Name a .heatmap-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
156
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Heatmap chart\nA data table drawn as a grid of cells shaded by value, for patterns across two categories such as days and hours.\n\nComponent ID: `heatmap-chart`\n\n[Component documentation](https://www.expressivecss.com/heatmap-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/heatmap-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Visits by day of the week and time of day is a heatmap chart. Visits per day over a month is a line or column chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-07); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-07, `llm.md#heatmap-chart`): Every body cell of a figure's data table drawn as a shaded grid cell, with row headers beside the grid, column headers below, a scale naming the lowest and highest cells, values, data-label short or hidden labels, data-min and data-max, gaps, and a pointer and keyboard tooltip that moves cell by cell.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the grid, labels and scale are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, cell shades and gaps, the fixed, diverging and stepped scales, values and their text color, short and running labels, the scale ends, RTL, page overflow from a wide table, scrolling a wide grid, the tooltip, pointer and keyboard movement round the grid and destroy are checked in Chromium, Firefox and WebKit; screen reader output, forced colors and enlarged text remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#heatmap-chart).\n\n#### Contract\n\nA `<figure class=\"heatmap-chart\">` takes the same `<figcaption>` and `<table>` as a column chart. `AutoInit()` draws every body cell as a cell of a grid, one grid row per table row and one grid column per value column, shaded from the lowest value to the highest. The row headers sit at the inline start, the column headers below, and a scale under the grid names the lowest and highest cells. A tooltip shows the cell under the pointer. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"heatmap-chart values\">\n <figcaption>Revenue by rep, per month</figcaption>\n <table>\n <thead>\n <tr><th>Rep</th><th>Jan</th><th>Feb</th><th>Mar</th></tr>\n </thead>\n <tbody>\n <tr><th>Priya</th><td data-value=\"45000\">$45K</td><td data-value=\"58000\">$58K</td><td data-value=\"85000\">$85K</td></tr>\n <tr><th>Daniel</th><td data-value=\"88000\">$88K</td><td data-value=\"34000\">$34K</td><td data-value=\"45000\">$45K</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Heatmap chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `heatmap-chart-is-a-figure-with-a-table`: Write .heatmap-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `heatmap-chart-has-a-caption`: Name a .heatmap-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
157
+ },
158
+ {
159
+ "file": "hover-card.md",
160
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Hover card\nShows a card of preview content after a delay when a link is hovered or gets keyboard focus.\n\nComponent ID: `hover-card`\n\n[Component documentation](https://www.expressivecss.com/hover-card.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/hover-card.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: An author name in an article previews the author's profile in a hover card; an icon button's label is a tooltip, and a note people open on press is a popover.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-25); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-25, `llm.md#hover-card`): A link with hover-card-trigger whose aria-details names a .popover with popover=\"hint\"; the card shows against the link after enterDelay of hover or keyboard focus, stays while the pointer or focus is on the link or card, hides after exitDelay or at once on Escape with focus back on the link, and a tap or click follows the link.\n\nWeb adaptation: Script timers decide when the card shows, and showPopover({ source }) makes the link its anchor and invoker, so the browser places it, puts it next in the Tab order and light-dismisses it. Interest invokers (interestfor) exist only in Chromium, so they are not used.\n\nKnown boundary: Hover delay, a pointer that passes over, staying open on the card, hiding after exitDelay, keyboard focus, Tab into the card, Escape with focus back on the link, focus leaving the card and a click that focuses the link are checked in Chromium, Firefox and WebKit; spoken output and aria-details support in screen readers remain unverified. Touch shows no card, and the card's content is static markup.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#hover-card).\n\n#### Contract\n\nAdd `hover-card-trigger` to an `<a href>` and point its `aria-details` at the card, a `.popover` with `popover=\"hint\"`. `AutoInit()` starts every `.hover-card-trigger`. Once the pointer or keyboard focus has rested on the link for `enterDelay`, 500 ms by default, the card shows against the link: below it with start edges aligned, flipped to the other side when it would not fit. A pointer that only passes over the link shows nothing.\n\nThe card stays while the pointer is on the link or the card, and while focus is on either. When both are left, it hides after `exitDelay`, 300 ms by default, so a pointer that crosses the gap to the card keeps it open. Focus stays on the link when the card shows, and Tab moves into the card's links and buttons. Escape hides the card and takes focus in it back to the link. A click elsewhere hides it too. One hint card shows at a time, and a hover card leaves an open menu or popover alone. Several links can name the same card, such as a name mentioned twice, and the card shows against whichever link showed it last.\n\nA tap or click follows the link and shows no card, so the linked page has to hold the same content for touch screens. Keep the card a short preview of where the link goes, and put anything to act on in the page, a popover opened by a button or a dialog. Do not also give the link `interestfor`, which Chromium would handle on its own as well.\n\n#### Syntax\n\n```html\n<p>Written by <a class=\"hover-card-trigger\" href=\"/people/ada\" aria-details=\"ada-card\">Ada Lovelace</a>.</p>\n<div id=\"ada-card\" class=\"popover\" popover=\"hint\">\n <h3>Ada Lovelace</h3>\n <p>Mathematician. Wrote the first published program, for the Analytical Engine.</p>\n <p><a href=\"/people/ada/articles\">All articles by Ada</a></p>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `enterDelay` | Number | `500` | How long the pointer or keyboard focus rests on the link before the card shows, in milliseconds. |\n| `exitDelay` | Number | `300` | How long the card stays after the pointer and focus leave the link and the card, in milliseconds. |\n\n#### Methods\n\n- `.open()`: Shows the card now, without `enterDelay`.\n- `.close()`: Hides the card now, without `exitDelay`.\n- `.destroy()`: Hides a card the instance showed and removes the listeners. The link and card keep the markup as written.\n\n#### Also documented\n\nThe Hover card section of the [API reference](https://www.expressivecss.com/llm.md) also covers Accessibility, Events.\n\n#### Rules\n\n- `hover-card-trigger-is-a-link`: Put hover-card-trigger on an <a href>. The card previews where the link goes, and a tap or click still has to follow it; a button already has a popover that opens on press.\n- `hover-card-names-its-card`: Point the trigger's aria-details at the id of its card, a [popover] element in the page. The component finds the card through it, and screen readers use it to reach the card.\n"
121
161
  },
122
162
  {
123
163
  "file": "icon-buttons.md",
124
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Icon buttons\nA single icon as the whole control, in four styles and five sizes.\n\nComponent ID: `icon-buttons`\n\n[Component documentation](https://www.expressivecss.com/icon-buttons.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/icon-buttons.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/icon-buttons/overview)\n\nExample: A novel approval workflow should use a labeled button. A tooltip on an ambiguous icon cannot replace a clear label or the control's accessible name.\n\n#### Material mapping\n\nRelationship: component. Google deprecates baseline filled, tonal and outlined token sets in favor of updated tokens, not those current icon-button variants. Do not rename framework tokens from Android examples.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/icon-buttons/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/icon-buttons/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/icon-buttons/guidelines).\n\nSupport (2026-09-13, `llm.md#icon-buttons`): Standard, filled, tonal, outlined; five sizes; narrow, wide, and square.\n\nWeb adaptation: Accessible name belongs to the control; the glyph is decorative.\n\nKnown boundary: Elevated and text are not icon-button variants.\n\nKnown boundary: Existing group checks cover selected icon colors and press shapes, not all standalone target sizes and disabled states.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#icon-buttons).\n\n#### Contract\n\nOne action as one icon. Add `icon-button` to a `<button>`, or to an `<a>` when it navigates, and put a single `<span class=\"material-symbols\" aria-hidden=\"true\">` inside. The icon is hidden, so `aria-label` is the only name the control has — an icon button without one fails the semantics suite.\n\nIt is a component, not a modifier on the common button: M3 gives it its own token families (`md.comp.icon-button.{xsmall…xlarge}` and `md.comp.icon-button-{standard,filled,tonal,outlined}`), so `.icon-button` takes none of the common-button geometry. Do not write `class=\"button icon-button\"`.\n\n#### Syntax\n\n```html\n<button type=\"button\" class=\"icon-button\" aria-label=\"Add\">\n <span class=\"material-symbols\" aria-hidden=\"true\">add</span>\n</button>\n\n<a class=\"icon-button\" href=\"/inbox\" aria-label=\"Inbox\">\n <span class=\"material-symbols\" aria-hidden=\"true\">inbox</span>\n</a>\n```\n\n#### Also documented\n\nThe Icon buttons section of the [API reference](https://www.expressivecss.com/llm.md) also covers Styles, Sizes, width and shape, Disabled and tokens.\n\n#### Rules\n\n- `icon-button-is-a-control`: An icon button is a control: <button>, or <a href> when it navigates. A <div>, or an anchor with no href, is neither focusable nor announced.\n- `icon-button-link-is-not-fake-disabled`: A link cannot be disabled by a class. `pointer-events: none` stops the pointer and nothing else, so this stays in the tab order and still navigates on Enter - it looks inert and is not. Drop the href, or the link.\n- `icon-button-icon-hidden`: The ligature is real text and is read out verbatim. The icon in an icon button is decoration - the control carries the name - so it is aria-hidden=\"true\".\n- `icon-button-is-named`: The icon is hidden from assistive technology, so an icon button has nothing left to be named by. Give it an aria-label.\n"
164
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Icon buttons\nA single icon as the whole control, in four styles and five sizes.\n\nComponent ID: `icon-buttons`\n\n[Component documentation](https://www.expressivecss.com/icon-buttons.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/icon-buttons.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/icon-buttons/overview)\n\nExample: A novel approval workflow should use a labeled button. A tooltip on an ambiguous icon cannot replace a clear label or the control's accessible name.\n\n#### Material mapping\n\nRelationship: component. Google deprecates baseline filled, tonal and outlined token sets in favor of updated tokens, not those current icon-button variants. Do not rename framework tokens from Android examples.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/icon-buttons/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/icon-buttons/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/icon-buttons/guidelines).\n\nSupport (2026-09-13, `llm.md#icon-buttons`): Standard, filled, tonal, outlined; five sizes; narrow, wide, and square.\n\nWeb adaptation: Accessible name belongs to the control; the glyph is decorative.\n\nKnown boundary: Elevated and text are not icon-button variants.\n\nKnown boundary: Existing group checks cover selected icon colors and press shapes, not all standalone target sizes and disabled states.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#icon-buttons).\n\n#### Contract\n\nOne action as one icon. Add `icon-button` to a `<button>`, or to an `<a>` when it navigates, and put a single `<span class=\"material-symbols\" aria-hidden=\"true\">` inside. The icon is hidden, so `aria-label` is the only name the control has — an icon button without one fails the semantics suite.\n\nIt is a component, not a modifier on the common button: M3 gives it its own token families (`md.comp.icon-button.{xsmall…xlarge}` and `md.comp.icon-button-{standard,filled,tonal,outlined}`), so `.icon-button` takes none of the common-button geometry. Do not write `class=\"button icon-button\"`.\n\n#### Syntax\n\n```html\n<button type=\"button\" class=\"icon-button\" aria-label=\"Add\">\n <span class=\"material-symbols\" aria-hidden=\"true\">add</span>\n</button>\n\n<a class=\"icon-button\" href=\"/inbox\" aria-label=\"Inbox\">\n <span class=\"material-symbols\" aria-hidden=\"true\">inbox</span>\n</a>\n```\n\n#### Also documented\n\nThe Icon buttons section of the [API reference](https://www.expressivecss.com/llm.md) also covers Styles, Sizes, width and shape, Disabled and tokens, Copy button.\n\n#### Rules\n\n- `icon-button-is-a-control`: An icon button is a control: <button>, or <a href> when it navigates. A <div>, or an anchor with no href, is neither focusable nor announced.\n- `icon-button-link-is-not-fake-disabled`: A link cannot be disabled by a class. `pointer-events: none` stops the pointer and nothing else, so this stays in the tab order and still navigates on Enter - it looks inert and is not. Drop the href, or the link.\n- `icon-button-icon-hidden`: The ligature is real text and is read out verbatim. The icon in an icon button is decoration - the control carries the name - so it is aria-hidden=\"true\".\n- `icon-button-is-named`: The icon is hidden from assistive technology, so an icon button has nothing left to be named by. Give it an aria-label.\n"
165
+ },
166
+ {
167
+ "file": "image-compare.md",
168
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Image compare\nTwo images of the same scene with a divider that reveals one over the other.\n\nComponent ID: `image-compare`\n\n[Component documentation](https://www.expressivecss.com/image-compare.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/image-compare.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A photo before and after retouching is an image compare. Five product photos are a carousel.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-15); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-15, `llm.md#image-compare`): Two stacked images, a divider and handle driven by a native range input, keyboard and pointer control, RTL and a focus ring.\n\nWeb adaptation: A native range input covers the images invisibly; the ImageCompare behavior writes its value to the divider position.\n\nKnown boundary: The initial position, the reveal by arrow keys, the focus ring, RTL, the named slider and Slider staying off are checked in a browser; screen reader output remains unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#image-compare).\n\n#### Contract\n\nA `.image-compare` holds two `<img>` and a native `<input type=\"range\">`. The range is the control, so keys, drags and taps work as on any slider and its value is announced; it covers the images invisibly. The `ImageCompare` behavior writes the value to `--md-comp-image-compare-position`, which clips the second image and places the divider and handle. RTL starts the first image on the right. Slider leaves this range alone. Give each image alt text that says which it is, and name the range. `Expressive.ImageCompare.sync(range)` places a compare added after load; the root bundle starts the behavior on load, and the modular entry with `ImageCompare.Init()`.\n\n#### Syntax\n\n```html\n<div class=\"image-compare\">\n <img src=\"before.jpg\" alt=\"Before: the faded original photo\" width=\"800\" height=\"500\">\n <img src=\"after.jpg\" alt=\"After: the restored photo\" width=\"800\" height=\"500\">\n <input type=\"range\" min=\"0\" max=\"100\" value=\"50\" aria-label=\"Compare before and after\">\n</div>\n```\n\n#### Rules\n\n- `image-compare-has-a-range`: Put an <input type=\"range\"> in the .image-compare. It is the control people use, by pointer or keyboard; without it the divider cannot move.\n- `image-compare-range-is-named`: Name the range, such as aria-label=\"Compare before and after\". It is the only control and has no visible label.\n- `image-compare-images-have-alt`: Give each image alt text that says which it is, such as \"Before: faded photo\". The comparison means nothing if neither image is described.\n"
169
+ },
170
+ {
171
+ "file": "input-mask.md",
172
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Input mask\nFormats a text field's value to a pattern, such as a phone or card number, as people type, paste or compose.\n\nComponent ID: `input-mask`\n\n[Component documentation](https://www.expressivecss.com/input-mask.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/input-mask.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A US phone number field takes data-mask=\"(000) 000-0000\" with inputmode=\"numeric\" and a format hint; a name field stays a plain text field.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-25); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-25, `llm.md#input-mask`): A data-mask pattern on a text, tel, search, url or password input, where 0 takes a digit, a a letter, * either and a backslash escapes a literal; typed, pasted, autofilled and composed text formatted with the caret after the edit, Backspace and Delete across literals, saved and reset values formatted, the formatted value submitted and validated, and formatting before the page's input listeners.\n\nWeb adaptation: A capture-phase input listener rewrites the native value, so the form, constraint validation and FormData see what the field shows. Composing input is skipped and the field formats on compositionend, and a beforeinput listener turns a one-character delete next to a literal into a delete of the slot character across it.\n\nKnown boundary: Typing, inserted pastes, deletes across literals, composition in both event orders, saved and reset values, listener order, Keypad keys and destroy() are checked in a browser; real IMEs, Android keyboards, clipboard pastes and spoken output remain unverified, and undo after a formatted edit does nothing. The form submits only the formatted value, and masks cannot change with the value.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#input-mask).\n\n#### Contract\n\nAdd `data-mask` to an `<input>` of type `text`, `tel`, `search`, `url` or `password`. `AutoInit()` starts every one. In the mask, `0` takes a digit, `a` takes a letter and `*` takes either. Every other character is a literal the mask writes itself, and a backslash makes the next character a literal, so `\\0` writes a zero.\n\nAs a person types, each character that fits fills the next slot, and the mask writes the literals that come before it: typing `5551` into `(000) 000-0000` shows `(555) 1`. A character that fits no slot is dropped, as is one past the end. A paste or autofill keeps the characters that fit and skips the rest, so `555.123.4567` becomes `(555) 123-4567`. A typed character matches only the literal that comes next, so in a `+1 (000) 000-0000` mask a typed leading 1 fills the first slot; a paste or autofill that starts with `+1` matches the prefix. Leave a country code out of the mask, or say in the supporting text not to type it. A mask with no slot leaves the field alone. The caret stays after the last character the edit added. Backspace right after a literal, or Delete right before one, removes the digit across it. Full-width digits from an IME count as digits, and while an IME composes, the field waits and formats once the composition ends.\n\nThe form submits the value as the field shows it, `(555) 123-4567`, and `pattern`, `required` and the length checks run on that value. A saved value is formatted when the component starts, without an `input` or `change` event, and again after a form reset. The component formats before the page's own `input` listeners run, so they read the formatted value. A value a script sets is formatted once the script fires an `input` event. Keypad keys format like typing; with the caret right after a literal, the Keypad's Backspace moves the caret before the literal, and a second press deletes the digit.\n\n#### Syntax\n\n```html\n<div class=\"field\">\n <input id=\"phone\" name=\"phone\" type=\"tel\" inputmode=\"numeric\" autocomplete=\"tel-national\" placeholder=\" \" data-mask=\"(000) 000-0000\" aria-describedby=\"phone_help\">\n <label for=\"phone\">Phone</label>\n <small id=\"phone_help\">Format: (555) 123-4567</small>\n</div>\n```\n\n#### Methods\n\n- `.destroy()`: Removes the listeners and leaves the value as it is.\n\n#### Rules\n\n- `input-mask-needs-a-caret`: Put data-mask on an <input> of type text, tel, search, url or password. Other input types have no caret to place and reject a formatted value, and AutoInit skips them; a <textarea> is not masked.\n"
125
173
  },
126
174
  {
127
175
  "file": "keypad.md",
128
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Keypad and Keyboard\nOn-screen keys that type into a text field: a PIN or dial pad, or a full keyboard.\n\nComponent ID: `keypad`\n\n[Component documentation](https://www.expressivecss.com/keypad.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/keypad.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A café kiosk asks for a four-digit loyalty PIN with a keypad under a password field. A checkout form on a phone uses a text field with inputmode=\"numeric\" instead.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-11); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-11, `llm.md#keypad-and-keyboard`): A three-column keypad of round keys with two-cell wide keys and a keyboard of rows sized from a column count with an optional number row, typing into the field named by aria-controls with value keys, Backspace, Enter, a one-shot Shift, data-key keys inside the field's wrapper, dial-pad letters, styled call keys, an onKey callback and a pressed shape morph.\n\nWeb adaptation: Keys are native buttons in a named group; edits go through the field's value and fire a composed input event, Enter uses requestSubmit() so validation runs, and inputmode=\"none\" on the field keeps the device keyboard closed. Browsers check minlength only on what a person types, so a minimum length is written as pattern.\n\nKnown boundary: Typing at the caret, maxlength, pattern validation on Enter, Backspace including emoji, Enter submitting a form and adding a new line in a textarea, a one-shot Shift with capitals shown, number inputs, disabled fieldsets, in-field keys, shadow roots, focus staying in the field, the keypad grid and wide keys, keyboard key widths and the number row, the pressed shape and destroy are checked in a browser; screen reader output, forced colors, enlarged text and touch devices remain unverified. There is no caps lock, no symbol layer switching and no hardware key highlighting, and minlength does not apply to keypad edits; the page owns layouts beyond the keys it writes.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#keypad).\n\n#### Contract\n\nA `.keypad` is three columns of round 72dp keys for PINs, codes and amounts. A `.keyboard` is a surface of rows, each child a row of keys, for text. Both are a `role=\"group\"` with an `aria-label`, and `aria-controls` names the `<input>` or `<textarea>` they type into. Every key is a `<button type=\"button\">`. A key with a `value` types it at the caret, replacing any selection, and the field gets the same `input` event typing would fire. `AutoInit()` starts every `.keypad` and `.keyboard`.\n\n#### Syntax\n\n```html\n<div class=\"field outlined\">\n <input id=\"pin\" type=\"password\" inputmode=\"none\" maxlength=\"6\" placeholder=\" \" autocomplete=\"off\">\n <label for=\"pin\">PIN</label>\n</div>\n\n<div class=\"keypad\" role=\"group\" aria-label=\"PIN keypad\" aria-controls=\"pin\">\n <button type=\"button\" value=\"1\">1</button>\n <button type=\"button\" value=\"2\">2</button>\n <!-- … 3 to 9 … -->\n <span></span>\n <button type=\"button\" value=\"0\">0</button>\n <button type=\"button\" data-key=\"Backspace\" aria-label=\"Delete\">\n <span class=\"material-symbols\" aria-hidden=\"true\">backspace</span>\n </button>\n</div>\n\n<div class=\"keyboard\" role=\"group\" aria-label=\"Keyboard\" aria-controls=\"message\">\n <div class=\"numbers\"><button type=\"button\" value=\"1\">1</button><!-- … 2 to 0 … --></div>\n <div><button type=\"button\" value=\"q\">q</button><!-- … --></div>\n <div><!-- a to l --></div>\n <div>\n <button type=\"button\" data-key=\"Shift\" aria-pressed=\"false\" aria-label=\"Shift\">\n <span class=\"material-symbols\" aria-hidden=\"true\">shift</span>\n </button>\n <!-- z to m -->\n <button type=\"button\" data-key=\"Backspace\" aria-label=\"Delete\">\n <span class=\"material-symbols\" aria-hidden=\"true\">backspace</span>\n </button>\n </div>\n <div>\n <button type=\"button\" value=\" \" aria-label=\"Space\"></button>\n <button type=\"button\" data-key=\"Enter\" aria-label=\"New line\">\n <span class=\"material-symbols\" aria-hidden=\"true\">keyboard_return</span>\n </button>\n </div>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `onKey` | Function | `null` | Called after every key press with the text typed or the function key's name (`'Backspace'`, `'Enter'`, `'Shift'`). Works without `aria-controls`. |\n\n#### Methods\n\n- `.destroy()`: Stops the keypad typing. The shared listeners on the document or shadow root go with the last keypad there.\n\n#### Also documented\n\nThe Keypad and Keyboard section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens, Properties.\n\n#### Rules\n\n- `keypad-is-group`: A keypad or keyboard is a set of related keys. Give it role=\"group\" and an aria-label so its keys are announced together.\n- `keypad-is-labelled`: Name the keypad or keyboard with aria-label, for example \"PIN keypad\".\n- `keypad-key-type`: A <button> inside a form defaults to type=submit, so every key would submit it. Give each key type=\"button\".\n"
176
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Keypad and Keyboard\nOn-screen keys that type into a text field: a PIN or dial pad, or a full keyboard.\n\nComponent ID: `keypad`\n\n[Component documentation](https://www.expressivecss.com/keypad.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/keypad.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A café kiosk asks for a four-digit loyalty PIN with a keypad under a password field. A checkout form on a phone uses a text field with inputmode=\"numeric\" instead.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-11); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-11, `llm.md#keypad-and-keyboard`): A three-column keypad of round keys with two-cell wide keys and a keyboard of rows sized from a column count with an optional number row, typing into the field named by aria-controls with value keys, Backspace, Enter, a one-shot Shift, data-key keys inside the field's wrapper, dial-pad letters, styled call keys, an onKey callback and a pressed shape morph.\n\nWeb adaptation: Keys are native buttons in a named group; edits go through the field's value and fire a composed input event, Enter uses requestSubmit() so validation runs, and inputmode=\"none\" on the field keeps the device keyboard closed. Browsers check minlength only on what a person types, so the keypad checks it on its own edits.\n\nKnown boundary: Typing at the caret, maxlength, pattern validation on Enter, Backspace including emoji, Enter submitting a form and adding a new line in a textarea, a one-shot Shift with capitals shown, Caps Lock, Layer keys, hardware key highlight, minlength on keypad edits, number inputs, disabled fieldsets, in-field keys, shadow roots, focus staying in the field, the keypad grid and wide keys, keyboard key widths and the number row, the pressed shape and destroy are checked in a browser; screen reader output, forced colors, enlarged text and touch devices remain unverified. There is no caps lock, no symbol layer switching and no hardware key highlighting, and minlength does not apply to keypad edits; the page owns layouts beyond the keys it writes.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#keypad).\n\n#### Contract\n\nA `.keypad` is three columns of round 72dp keys for PINs, codes and amounts. A `.keyboard` is a surface of rows, each child a row of keys, for text. Both are a `role=\"group\"` with an `aria-label`, and `aria-controls` names the `<input>` or `<textarea>` they type into. Every key is a `<button type=\"button\">`. A key with a `value` types it at the caret, replacing any selection, and the field gets the same `input` event typing would fire. `AutoInit()` starts every `.keypad` and `.keyboard`.\n\n#### Syntax\n\n```html\n<div class=\"field outlined\">\n <input id=\"pin\" type=\"password\" inputmode=\"none\" maxlength=\"6\" placeholder=\" \" autocomplete=\"off\">\n <label for=\"pin\">PIN</label>\n</div>\n\n<div class=\"keypad\" role=\"group\" aria-label=\"PIN keypad\" aria-controls=\"pin\">\n <button type=\"button\" value=\"1\">1</button>\n <button type=\"button\" value=\"2\">2</button>\n <!-- … 3 to 9 … -->\n <span></span>\n <button type=\"button\" value=\"0\">0</button>\n <button type=\"button\" data-key=\"Backspace\" aria-label=\"Delete\">\n <span class=\"material-symbols\" aria-hidden=\"true\">backspace</span>\n </button>\n</div>\n\n<div class=\"keyboard\" role=\"group\" aria-label=\"Keyboard\" aria-controls=\"message\">\n <div class=\"numbers\"><button type=\"button\" value=\"1\">1</button><!-- … 2 to 0 … --></div>\n <div><button type=\"button\" value=\"q\">q</button><!-- … --></div>\n <div><!-- a to l --></div>\n <div>\n <button type=\"button\" data-key=\"Shift\" aria-pressed=\"false\" aria-label=\"Shift\">\n <span class=\"material-symbols\" aria-hidden=\"true\">shift</span>\n </button>\n <!-- z to m -->\n <button type=\"button\" data-key=\"Backspace\" aria-label=\"Delete\">\n <span class=\"material-symbols\" aria-hidden=\"true\">backspace</span>\n </button>\n </div>\n <div>\n <button type=\"button\" value=\" \" aria-label=\"Space\"></button>\n <button type=\"button\" data-key=\"Enter\" aria-label=\"New line\">\n <span class=\"material-symbols\" aria-hidden=\"true\">keyboard_return</span>\n </button>\n </div>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `onKey` | Function | `null` | Called after every key press with the text typed or the function key's name (`'Backspace'`, `'Enter'`, `'Shift'`, `'CapsLock'`, `'Layer'`). Works without `aria-controls`. |\n| `i18n.tooShort` | Function | `(minLength) => 'Use at least ' + minLength + ' characters.'` | The validation message for a keypad-typed value shorter than the field's `minlength`. |\n\n#### Methods\n\n- `.destroy()`: Stops the keypad typing. The shared listeners on the document or shadow root go with the last keypad there.\n\n#### Also documented\n\nThe Keypad and Keyboard section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens, Properties.\n\n#### Rules\n\n- `keypad-is-group`: A keypad or keyboard is a set of related keys. Give it role=\"group\" and an aria-label so its keys are announced together.\n- `keypad-is-labelled`: Name the keypad or keyboard with aria-label, for example \"PIN keypad\".\n- `keypad-key-type`: A <button> inside a form defaults to type=submit, so every key would submit it. Give each key type=\"button\".\n"
129
177
  },
130
178
  {
131
179
  "file": "kpi.md",
132
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### KPI\nOne key figure in a card, with a trend chip, a sparkline and details.\n\nComponent ID: `kpi`\n\n[Component documentation](https://www.expressivecss.com/kpi.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/kpi.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Revenue, orders and churn across the top of a sales dashboard, each with its last 10 days, are KPIs. Twelve figures with no trend are a stats row.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-08); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-08, `llm.md#kpi`): A card with a labelled value in a description list, a trend chip with up, down and negative states, a tonal icon, a corner action, a sparkline, a progress bar and a footer.\n\nWeb adaptation: A <dl> pairs the value with its label inside a card <article>; the arrow is decoration, the text carries the direction and the sparkline's table is its text alternative.\n\nKnown boundary: Part placement, the value type role, arrow direction, the negative chip and chart colors and term-definition pairing are checked in a browser; enlarged text and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#kpi).\n\n#### Contract\n\nAn `<article class=\"kpi\">` holds a `<dl>` with a `<dt>` label, a `<dd class=\"kpi-value\">` and an optional `<dd class=\"kpi-trend\">` chip. The description list keeps the value paired with its label for assistive technology. The article is a card, so `filled` and `outlined` apply, and KPIs in a grid row stretch to one height with their footers aligned. CSS only; a chart inside is drawn by its own script.\n\n#### Syntax\n\n```html\n<article class=\"kpi outlined\">\n <dl>\n <dt><span class=\"kpi-icon material-symbols-outlined\" aria-hidden=\"true\">payments</span>Revenue</dt>\n <dd class=\"kpi-value\">$48,200</dd>\n <dd class=\"kpi-trend up\">+12.5%</dd>\n </dl>\n <button type=\"button\" class=\"icon-button kpi-actions\" aria-label=\"Revenue options\">\n <span class=\"material-symbols-outlined\" aria-hidden=\"true\">more_vert</span>\n </button>\n <figure class=\"line-chart sparkline area\">\n <figcaption class=\"visually-hidden\">Revenue over 10 days, in thousands</figcaption>\n <table>\n <thead><tr><th>Day</th><th>Revenue</th></tr></thead>\n <tbody><tr><th>Day 1</th><td>40.1</td></tr><tr><th>Day 2</th><td>42.3</td></tr></tbody>\n </table>\n </figure>\n <div class=\"kpi-footer\">Compared with $42,800 last month</div>\n</article>\n```\n\n#### Also documented\n\nThe KPI section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `kpi-is-a-card-with-a-labelled-value`: Write .kpi as an <article> holding a <dl> whose <dt> label comes right before the <dd class=\"kpi-value\">. The article makes it a card, and without the pairing a screen reader reads a bare number with nothing to say what it is. Not fragmentSafe: what it detects is an omission.\n"
180
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### KPI\nOne key figure in a card, with a trend chip, a sparkline and details.\n\nComponent ID: `kpi`\n\n[Component documentation](https://www.expressivecss.com/kpi.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/kpi.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Revenue, orders and churn across the top of a sales dashboard, each with its last 10 days, are KPIs. Twelve figures with no trend are a stats row.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-08); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-08, `llm.md#kpi`): A card with a labelled value in a description list, a trend chip with up, down and negative states, a tonal icon, a corner action, a sparkline, a progress bar and a footer.\n\nWeb adaptation: A <dl> pairs the value with its label inside a card <article>; the arrow is decoration, the text carries the direction and the sparkline's table is its text alternative.\n\nKnown boundary: Part placement, the value type role, arrow direction, the negative chip and chart colors and term-definition pairing are checked in a browser; enlarged text and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#kpi).\n\n#### Contract\n\nAn `<article class=\"kpi\">` holds a `<dl>` with a `<dt>` label, a `<dd class=\"kpi-value\">` and an optional `<dd class=\"kpi-trend\">` chip. The description list keeps the value paired with its label for assistive technology. The article is a card, so `filled` and `outlined` apply, and KPIs in a grid row stretch to one height with their footers aligned. CSS only; a chart inside is drawn by its own script.\n\n#### Syntax\n\n```html\n<article class=\"kpi outlined\">\n <dl>\n <dt><span class=\"kpi-icon material-symbols-outlined\" aria-hidden=\"true\">payments</span>Revenue</dt>\n <dd class=\"kpi-value\">$48,200</dd>\n <dd class=\"kpi-trend up\">+12.5%</dd>\n </dl>\n <button type=\"button\" class=\"icon-button kpi-actions\" aria-label=\"Revenue options\">\n <span class=\"material-symbols-outlined\" aria-hidden=\"true\">more_vert</span>\n </button>\n <figure class=\"line-chart sparkline area\">\n <figcaption class=\"visually-hidden\">Revenue over 10 days, in thousands</figcaption>\n <table>\n <thead><tr><th>Day</th><th>Revenue</th></tr></thead>\n <tbody><tr><th>Day 1</th><td>40.1</td></tr><tr><th>Day 2</th><td>42.3</td></tr></tbody>\n </table>\n </figure>\n <div class=\"kpi-footer\">Compared with $42,800 last month</div>\n</article>\n```\n\n#### Also documented\n\nThe KPI section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `kpi-is-a-card-with-a-labelled-value`: Write .kpi as an <article> holding a <dl> whose <dt> label comes right before the <dd class=\"kpi-value\">. The article makes it a card, and without the pairing a screen reader reads a bare number with nothing to say what it is. Not fragmentSafe: what it detects is an omission.\n"
133
181
  },
134
182
  {
135
183
  "file": "lightbox.md",
136
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Lightbox\nLightbox for enlarge-on-click images.\n\nComponent ID: `lightbox`\n\n[Component documentation](https://www.expressivecss.com/media.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/media.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A payment confirmation needs a dialog, even if its design contains an image. Lightbox is for inspecting enlarged media.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-09-13); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\nSupport (2026-09-13, `llm.md#lightbox`): Enlarge-on-activation image and dismissal.\n\nWeb adaptation: ExpressiveCSS media behavior, not an upstream component contract.\n\nKnown boundary: Enlargement and teardown are checked; modal containment, reduced motion and destruction during closing remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#lightbox).\n\n#### Contract\n\nLightbox supports Space and native button activation. Repeated open or close requests are ignored; reopening during closing cancels the old transition. Canceled transitions do not report completion. Reduced motion skips animation and finishes pending transitions when enabled while mounted. Destroy cancels pending work, retains the original image, restores owned attributes and styles, and preserves trigger focus without overriding application focus. Shared ancestor overflow remains visible until the last open Lightbox releases it. This does not supply a modal-gallery focus contract.\n\nLightbox is Expressive’s material-style enlarge-on-click image. Click an image with `lightboxed` and it centers and grows. Click it again, scroll, or press Escape to dismiss. `AutoInit()` starts every `.lightboxed` image except those marked `no-autoinit`.\n\n#### Syntax\n\n```html\n<img class=\"lightboxed\" tabindex=\"0\" role=\"button\" width=\"650\" alt=\"A mountain lake\" src=\"images/sample-1.jpg\">\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `inDuration` | Number | `275` | Open transition duration, in milliseconds. |\n| `outDuration` | Number | `200` | Close transition duration, in milliseconds. |\n| `onOpenStart` | Function | `null` | Called before the lightbox opens. |\n| `onOpenEnd` | Function | `null` | Called after the lightbox opens. |\n| `onCloseStart` | Function | `null` | Called before the lightbox closes. |\n| `onCloseEnd` | Function | `null` | Called after the lightbox closes. |\n\n#### Methods\n\n- `.open()`: Open the lightbox.\n- `.close()`: Close the lightbox.\n- `.destroy()`: Destroy the plugin instance and tear down its event handlers.\n\n#### Also documented\n\nThe Lightbox section of the [API reference](https://www.expressivecss.com/llm.md) also covers Initialization, Properties, Captions.\n\n#### Rules\n\n- `lightbox-trigger-is-operable`: A .lightboxed image opens an overlay on click, which makes it a control - and a bare <img> is not focusable and not operable from the keyboard. Wrap it in a <button>, or give it tabindex=\"0\" and a role.\n"
184
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Lightbox\nEnlarge an image in a lightbox when it is clicked.\n\nComponent ID: `lightbox`\n\n[Component documentation](https://www.expressivecss.com/media.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/media.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A payment confirmation needs a dialog, even if its design contains an image. Lightbox is for inspecting enlarged media.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-09-13); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\nSupport (2026-09-13, `llm.md#lightbox`): Enlarge-on-activation image and dismissal.\n\nWeb adaptation: ExpressiveCSS media behavior, not an upstream component contract.\n\nKnown boundary: Enlargement and teardown are checked; modal containment, reduced motion and destruction during closing remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#lightbox).\n\n#### Contract\n\nLightbox supports Space and native button activation. Repeated open or close requests are ignored; reopening during closing cancels the old transition. Canceled transitions do not report completion. Reduced motion skips animation and finishes pending transitions when enabled while mounted. Destroy cancels pending work, retains the original image, restores owned attributes and styles, and preserves trigger focus without overriding application focus. The open image, its overlay and its caption are manual popovers in the browser's top layer, so a clipping or transformed ancestor does not cut the image off, and Lightbox leaves its ancestors' styles alone. Tab still closes it rather than being trapped: this does not supply a modal-gallery focus contract.\n\nLightbox is Expressive’s material-style enlarge-on-click image. Click an image with `lightboxed` and it centers and grows. Click it again, scroll, or press Escape to dismiss. `AutoInit()` starts every `.lightboxed` image except those marked `no-autoinit`.\n\n#### Syntax\n\n```html\n<img class=\"lightboxed\" tabindex=\"0\" role=\"button\" width=\"650\" alt=\"A mountain lake\" src=\"images/sample-1.jpg\">\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `inDuration` | Number | `275` | Open transition duration, in milliseconds. |\n| `outDuration` | Number | `200` | Close transition duration, in milliseconds. |\n| `onOpenStart` | Function | `null` | Called before the lightbox opens. |\n| `onOpenEnd` | Function | `null` | Called after the lightbox opens. |\n| `onCloseStart` | Function | `null` | Called before the lightbox closes. |\n| `onCloseEnd` | Function | `null` | Called after the lightbox closes. |\n\n#### Methods\n\n- `.open()`: Open the lightbox.\n- `.close()`: Close the lightbox.\n- `.destroy()`: Destroy the plugin instance and tear down its event handlers.\n\n#### Also documented\n\nThe Lightbox section of the [API reference](https://www.expressivecss.com/llm.md) also covers Initialization, Properties, Captions.\n\n#### Rules\n\n- `lightbox-trigger-is-operable`: A .lightboxed image opens an overlay on click, which makes it a control - and a bare <img> is not focusable and not operable from the keyboard. Wrap it in a <button>, or give it tabindex=\"0\" and a role.\n"
137
185
  },
138
186
  {
139
187
  "file": "line-chart.md",
140
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Line chart\nTrends over time drawn from a data table, with a tooltip, a legend and a sparkline size.\n\nComponent ID: `line-chart`\n\n[Component documentation](https://www.expressivecss.com/line-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/line-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Monthly revenue against last year and a target is a line chart with three series. The same figures for twenty regions are a data table.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#line-chart`): Smooth monotone lines drawn from a figure's data table, with area, stacked, fade and sparkline modifiers, dashed series, gaps, a fixed y range, x labels, a legend, and a pointer and keyboard tooltip.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the SVG is aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, gaps, the y range, RTL, the tooltip, keyboard movement, forced-color swatches and destroy are checked in a browser; screen reader output and enlarged text remain unverified. There is no y axis labelling.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#line-chart).\n\n#### Contract\n\nA `<figure class=\"line-chart\">` holds a `<figcaption>` and a `<table>`. The first column labels the x axis; each further column is a series named by its header cell. `AutoInit()` draws the table as smooth SVG lines with x labels, a legend when there is more than one series, and a cursor and tooltip that follow the pointer. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"line-chart fade\">\n <figcaption>Monthly revenue, 2026</figcaption>\n <table>\n <thead>\n <tr><th>Month</th><th>Revenue</th><th class=\"dashed\">Target</th></tr>\n </thead>\n <tbody>\n <tr><th>Jan</th><td>$4,400</td><td>$5,200</td></tr>\n <tr><th>Feb</th><td>$3,400</td><td>$5,400</td></tr>\n <tr><th>Mar</th><td>$3,700</td><td>$5,100</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Line chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `line-chart-is-a-figure-with-a-table`: Write .line-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `line-chart-has-a-caption`: Name a .line-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
188
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Line chart\nTrends over time drawn from a data table, with a tooltip, a legend and a sparkline size.\n\nComponent ID: `line-chart`\n\n[Component documentation](https://www.expressivecss.com/line-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/line-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Monthly revenue against last year and a target is a line chart with three series. The same figures for twenty regions are a data table.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#line-chart`): Smooth monotone lines drawn from a figure's data table, with area, stacked, fade and sparkline modifiers, dashed series, gaps, a fixed y range, x labels, round y axis labels with .axis, a legend, and a pointer and keyboard tooltip.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the SVG is aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, gaps, the y range, RTL, the tooltip, keyboard movement, forced-color swatches and destroy are checked in a browser; screen reader output and enlarged text remain unverified. There is no y axis labelling.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#line-chart).\n\n#### Contract\n\nA `<figure class=\"line-chart\">` holds a `<figcaption>` and a `<table>`. The first column labels the x axis; each further column is a series named by its header cell. `AutoInit()` draws the table as smooth SVG lines with x labels, a legend when there is more than one series, and a cursor and tooltip that follow the pointer. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"line-chart fade\">\n <figcaption>Monthly revenue, 2026</figcaption>\n <table>\n <thead>\n <tr><th>Month</th><th>Revenue</th><th class=\"dashed\">Target</th></tr>\n </thead>\n <tbody>\n <tr><th>Jan</th><td>$4,400</td><td>$5,200</td></tr>\n <tr><th>Feb</th><td>$3,400</td><td>$5,400</td></tr>\n <tr><th>Mar</th><td>$3,700</td><td>$5,100</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Line chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `line-chart-is-a-figure-with-a-table`: Write .line-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `line-chart-has-a-caption`: Name a .line-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
141
189
  },
142
190
  {
143
191
  "file": "lists.md",
144
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Lists\nContinuous vertical indexes of text and images.\n\nComponent ID: `lists`\n\n[Component documentation](https://www.expressivecss.com/lists.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/lists.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/lists/overview)\n\nExample: A product with a large image, description, price, and several actions may need a card. A simple product-name index remains a list.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/lists/overview).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/lists/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/lists/guidelines).\n\nSupport (2026-09-13, `llm.md#lists`): Standard and segmented lists.\n\nWeb adaptation: Native list semantics; visual selection alone does not justify aria-selected.\n\nKnown boundary: Ordinary native lists and child controls do not implement Google composite arrow navigation.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#lists).\n\n#### Contract\n\nContinuous, vertical indexes of text and images. Use a list so people can find an item and act on it. There is no JavaScript — the HTML is the component.\n\nThe leading visual is the first icon, `<img>`, or `<input>`. The trailing action is the last icon, `<kbd>`, `<button>`, `<time>`, or `.meta`. A following `<p>` is supporting text. Mark the current row with `active` or `selected` — **not** `aria-selected`, which is not valid on a plain `listitem` and needs a role (`option`, `tab`, `row`, `treeitem`) that brings a keyboard contract with it.\n\nTwo variants: standard (the default) and `segmented`.\n\n#### Syntax\n\n```html\n<ul class=\"list\">\n <li>\n <span class=\"material-symbols\" aria-hidden=\"true\">star</span>\n List item\n <kbd>⌘C</kbd>\n </li>\n <li class=\"selected\">\n <span class=\"material-symbols\" aria-hidden=\"true\">star</span>\n List item\n <kbd>⌘C</kbd>\n </li>\n</ul>\n```\n\n#### Also documented\n\nThe Lists section of the [API reference](https://www.expressivecss.com/llm.md) also covers Standard, Segmented, Text, Icons, Links.\n\n#### Rules\n\n- `list-item-not-selected`: aria-selected is not valid on a listitem. A plain <ul> has nowhere to put it - either give the item a role that takes it (option, tab, row, treeitem) and implement that role’s keyboard contract, or drop the attribute.\n"
192
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Lists\nContinuous vertical indexes of text and images.\n\nComponent ID: `lists`\n\n[Component documentation](https://www.expressivecss.com/lists.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/lists.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/lists/overview)\n\nExample: A product with a large image, description, price, and several actions may need a card. A simple product-name index remains a list.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/lists/overview).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/lists/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/lists/guidelines).\n\nSupport (2026-09-13, `llm.md#lists`): Standard and segmented lists.\n\nWeb adaptation: Native list semantics; visual selection alone does not justify aria-selected.\n\nKnown boundary: Lists of single link or button rows move focus with Up, Down, Home and End as a shortcut; rows stay Tab stops, and lists with several controls per row, selection lists and listbox semantics are not provided.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#lists).\n\n#### Contract\n\nContinuous, vertical indexes of text and images. Use a list so people can find an item and act on it. The HTML is the component; the script only adds arrow keys to lists of link or button rows.\n\nThe leading visual is the first icon, `<img>`, or `<input>`. The trailing action is the last icon, `<kbd>`, `<button>`, `<time>`, or `.meta`. A following `<p>` is supporting text. Mark the current row with `active` or `selected` — **not** `aria-selected`, which is not valid on a plain `listitem` and needs a role (`option`, `tab`, `row`, `treeitem`) that brings a keyboard contract with it.\n\nTwo variants: standard (the default) and `segmented`.\n\n#### Syntax\n\n```html\n<ul class=\"list\">\n <li>\n <span class=\"material-symbols\" aria-hidden=\"true\">star</span>\n List item\n <kbd>⌘C</kbd>\n </li>\n <li class=\"selected\">\n <span class=\"material-symbols\" aria-hidden=\"true\">star</span>\n List item\n <kbd>⌘C</kbd>\n </li>\n</ul>\n```\n\n#### Also documented\n\nThe Lists section of the [API reference](https://www.expressivecss.com/llm.md) also covers Standard, Segmented, Text, Icons, Links, Sortable, Long lists.\n\n#### Rules\n\n- `list-item-not-selected`: aria-selected is not valid on a listitem. A plain <ul> has nowhere to put it - either give the item a role that takes it (option, tab, row, treeitem) and implement that role’s keyboard contract, or drop the attribute.\n- `sortable-items-move-without-dragging`: Give each .sortable item a [data-move=\"up\"] and a [data-move=\"down\"] button, in the row or in a menu. Dragging is a pointer gesture; the buttons are the keyboard path and the single-pointer alternative WCAG 2.5.7 requires.\n- `list-unstyled-has-list-role`: WebKit drops the list role from a list with no markers, so screen readers stop announcing it as a list. Give a .list-unstyled list role=\"list\", or the role it should have instead.\n"
145
193
  },
146
194
  {
147
195
  "file": "loading-indicator.md",
148
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Loading indicator\nA shape that morphs while it spins, for waits under five seconds.\n\nComponent ID: `loading-indicator`\n\n[Component documentation](https://www.expressivecss.com/loading-indicator.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/loading-indicator.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/loading-indicator/overview)\n\nExample: A file upload reporting 42% needs determinate progress, not the morphing loading shape. A longer unmeasured job needs indeterminate progress. Do not flash an indicator for an effectively instant response or announce a fabricated value.\n\n#### Material mapping\n\nRelationship: component. Use for brief unknown waits, typically under five seconds. Do not replace progress that transitions from unknown to determinate, or circular progress within buttons.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/loading-indicator/overview).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/loading-indicator/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/loading-indicator/guidelines).\n\nSupport (2026-09-13, `llm.md#loading-indicator`): Uncontained and contained morphing loading indicator.\n\nWeb adaptation: CSS-only; the author supplies an accessible waiting message.\n\nKnown boundary: The framework requires a named status while Google recommends progressbar; an indeterminate progressbar validly omits aria-valuenow.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#loading-indicator).\n\n#### Contract\n\nM3 Expressive's indicator for a short wait — a shape that morphs while it spins.\n\nReach for it whenever the wait is under about five seconds and you cannot report a percentage. It replaces most short indeterminate circular uses. Keep a progress indicator for a process that will transition to a determinate value; Google's button guidance also retains circular progress. `.progress` supports both linear bars and determinate cases.\n\nOne element, CSS-only, no JavaScript plugin. The element is empty, so it reports nothing on its own. Supply a named waiting state and meaningful status text updates; verify spoken delivery with assistive technology. An indeterminate `progressbar` is also valid and omits `aria-valuenow`. The current framework contract requires a named status. A name alone does not prove an announcement.\n\n#### Syntax\n\n```html\n<span class=\"loading-indicator\" role=\"status\" aria-label=\"Loading\"></span>\n```\n\n#### Also documented\n\nThe Loading indicator section of the [API reference](https://www.expressivecss.com/llm.md) also covers Contained, Tokens.\n\n#### Rules\n\n- `loading-indicator-reports-itself`: The current loading-indicator contract requires role=\"status\". Provide meaningful waiting text updates and verify announcement delivery; a role alone does not prove spoken output.\n- `loading-indicator-is-named`: Name the waiting state with aria-label or aria-labelledby. Verify meaningful status updates separately from its accessible name.\n"
196
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Loading indicator\nA shape that morphs while it spins, for waits under five seconds.\n\nComponent ID: `loading-indicator`\n\n[Component documentation](https://www.expressivecss.com/loading-indicator.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/loading-indicator.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/loading-indicator/overview)\n\nExample: A file upload reporting 42% needs determinate progress, not the morphing loading shape. A longer unmeasured job needs indeterminate progress. Do not flash an indicator for an effectively instant response or announce a fabricated value.\n\n#### Material mapping\n\nRelationship: component. Use for brief unknown waits, typically under five seconds. Do not replace progress that transitions from unknown to determinate, or circular progress within buttons.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/loading-indicator/overview).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/loading-indicator/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/loading-indicator/guidelines).\n\nSupport (2026-09-13, `llm.md#loading-indicator`): Uncontained and contained morphing loading indicator.\n\nWeb adaptation: CSS-only; the author supplies an accessible waiting message.\n\nKnown boundary: The framework requires a named status while Google recommends progressbar; an indeterminate progressbar validly omits aria-valuenow.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#loading-indicator).\n\n#### Contract\n\nM3 Expressive's indicator for a short wait — a shape that morphs while it spins.\n\nReach for it whenever the wait is under about five seconds and you cannot report a percentage. It replaces most short indeterminate circular uses. Keep a progress indicator for a process that will transition to a determinate value; Google's button guidance also retains circular progress. `.progress` supports both linear bars and determinate cases.\n\nOne element, CSS-only, no JavaScript plugin. The element is empty, so it reports nothing on its own. Supply a named waiting state and meaningful status text updates; verify spoken delivery with assistive technology. An indeterminate `progressbar` is also valid and omits `aria-valuenow`. The current framework contract requires a named status. A name alone does not prove an announcement.\n\n#### Syntax\n\n```html\n<span class=\"loading-indicator\" role=\"status\" aria-label=\"Loading\"></span>\n```\n\n#### Also documented\n\nThe Loading indicator section of the [API reference](https://www.expressivecss.com/llm.md) also covers Contained, Tokens.\n\n#### Rules\n\n- `loading-indicator-reports-itself`: The current loading-indicator contract requires role=\"status\". Provide meaningful waiting text updates and verify announcement delivery; a role alone does not prove spoken output.\n- `loading-indicator-is-named`: Name the waiting state with aria-label or aria-labelledby. Verify meaningful status updates separately from its accessible name.\n"
197
+ },
198
+ {
199
+ "file": "loading-overlay.md",
200
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Loading overlay\nCovers part of a page while it loads, so nothing inside takes input until the page clears it.\n\nComponent ID: `loading-overlay`\n\n[Component documentation](https://www.expressivecss.com/loading-overlay.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/loading-overlay.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A settings form that saves to a server takes a loading overlay while the request runs; a dashboard tile that refreshes in place while staying readable needs a loading indicator alone.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-25); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-25, `llm.md#loading-overlay`): A region the page marks aria-busy=\"true\" shows a surface scrim and its loading indicator and is made inert until the attribute goes, with focus taken from the control inside it, keeps an inert state the author set, announces the start and the finish in a status region after it with i18n strings, returns focus to the control that had it, and stops following the attribute on destroy().\n\nWeb adaptation: The native inert attribute blocks pointer and keyboard input instead of script handlers. The page owns aria-busy, and the script follows it with a MutationObserver. Inert content leaves the accessibility tree, so the script adds a visually hidden role=\"status\" region outside the region.\n\nKnown boundary: Input blocking, the author's inert state, the status region, focus return, the scrim and indicator geometry and destroy() are checked in Chromium, Firefox and WebKit; spoken output from the status region and real forced-colors themes remain unverified. The scrim does not follow the content of a region that scrolls itself; the docs say to wrap the scroller.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#loading-overlay).\n\n#### Contract\n\nA `.loading-overlay` region holds a `.loading-indicator` as a direct child and the content. `AutoInit()` starts every `.loading-overlay`. The page sets `aria-busy=\"true\"` on the region when its work starts, and removes the attribute, or sets any other value, when the work is done; there is no method for either. While the region is busy, a scrim covers it, the indicator shows in its centre, and the script makes the region `inert`, so nothing inside takes a click, a tap or keyboard focus. When it clears, the region gets back the `inert` state it had before.\n\n#### Syntax\n\n```html\n<form class=\"loading-overlay\">\n <span class=\"loading-indicator contained\" role=\"status\" aria-label=\"Saving\"></span>\n <div class=\"field\">\n <input id=\"name\" type=\"text\" placeholder=\" \">\n <label for=\"name\">Name</label>\n </div>\n <button type=\"submit\" class=\"filled\">Save</button>\n</form>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `i18n` | Object | `{ loading: 'Loading', loaded: 'Loaded' }` | What the status region announces when the region becomes busy and when it clears. |\n\n#### Methods\n\n- `.destroy()`: Stops following `aria-busy`, removes the status region, and gives the region back the `inert` state it had before. The scrim still follows the attribute, so remove `aria-busy` too if the region should look available.\n\n#### Also documented\n\nThe Loading overlay section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `loading-overlay-has-an-indicator`: Give a .loading-overlay a .loading-indicator as a direct child. It is what shows over the scrim while the region is busy.\n"
149
201
  },
150
202
  {
151
203
  "file": "map.md",
152
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Map\nAn interactive map with markers, popups, routes, arcs, clusters and controls, built on MapLibre.\n\nComponent ID: `map`\n\n[Component documentation](https://www.expressivecss.com/map.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/map.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `manual`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A store locator shows each store as a marker button that opens its hours, beside a list of the same stores with addresses. A contact page with one address writes it out and links to a map app.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-09); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-09, `llm.md#map`): MapLibre map with themed OpenFreeMap styles, markers with labels, tooltips, popups and dragging, routes, arcs, clusters, and zoom, compass, locate and full screen controls.\n\nWeb adaptation: The page passes in the maplibre-gl module. Markers, popups and controls are authored HTML moved into MapLibre; routes, arcs and clusters are GeoJSON layers whose colors are read from theme tokens and redrawn when the theme changes.\n\nKnown boundary: Marker placement, the popup opened from its marker with aria-expanded, Escape and map-click closing with focus back on the marker, zoom limits, the compass bearing, theme-colored routes, arcs and clusters redrawn after a theme swap, removeOverlay and destroy are checked in a browser; the locate and full screen controls, marker dragging, spoken output, touch gestures and forced colors remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#map).\n\n#### Contract\n\nA `.map` holds `.map-marker`, `.map-popup` and `.map-controls` children. ExpressiveCSS does not bundle MapLibre. Install `maplibre-gl`, load `maplibre-gl/dist/maplibre-gl.css` before ExpressiveCSS, and pass the module to `MapView.init()`. `AutoInit()` does not start the map. The class is `MapView` so importing it does not hide JavaScript's `Map`. Until the script runs, markers, popups and controls are hidden.\n\nThe default style is OpenFreeMap's Positron in the light theme and its Dark style in the dark theme; neither needs an API key. The map swaps style when the `theme` attribute or the system color scheme changes. Pass `style` as a URL or object to keep one style, or `{ light, dark }`. Keep the provider's attribution visible.\n\n- A `.map-marker` sits at its `data-lng-lat`, longitude first. With no content of its own it is a primary dot; content inside replaces the dot. A `.map-marker-label` shows below it (`.top`: above), and a child `.tooltip` shows on hover and focus. - A `<button class=\"map-marker\">` with `aria-controls` naming a `.map-popup` opens it and sets `aria-expanded`. A map click or Escape closes it and focus returns to the marker. A `.map-popup` with its own `data-lng-lat` and no marker opens at init. - `data-draggable` lets the reader drag a marker. When the drag ends, `data-lng-lat` holds the new position and the marker fires `change`. - `.map-controls` stacks icon buttons in the top right, or `.top-left`, `.bottom-left`, `.bottom-right`; `.map-control-group` joins buttons on one surface. `data-action` is `zoom-in`, `zoom-out` (disabled at the zoom limits), `compass` (resets north; the icon follows the bearing), `locate` (centers on the reader and marks the spot) or `fullscreen` (sets `aria-pressed`; hidden where unsupported). - Routes, arcs and clusters are data, added with methods. Lines and clusters take theme colors where the map is and are redrawn in the new colors when the theme changes. The map does not plan routes. - Name the map with `role=\"region\"` and `aria-label`, and give what matters, such as addresses, in text too.\n\n#### Syntax\n\n```html\n<div class=\"map\" role=\"region\" aria-label=\"Chicago stores\">\n <button type=\"button\" class=\"map-marker\" data-lng-lat=\"-87.6278,41.8820\"\n aria-label=\"Loop store\" aria-controls=\"loop\">\n <span class=\"map-marker-label\">Loop</span>\n </button>\n <div class=\"map-popup\" id=\"loop\">\n <h3>Loop store</h3>\n <p>Open until 9 p.m.</p>\n </div>\n <div class=\"map-controls\">\n <div class=\"map-control-group\">\n <button type=\"button\" class=\"icon-button\" data-action=\"zoom-in\" aria-label=\"Zoom in\">\n <span class=\"material-symbols\" aria-hidden=\"true\">add</span>\n </button>\n <button type=\"button\" class=\"icon-button\" data-action=\"zoom-out\" aria-label=\"Zoom out\">\n <span class=\"material-symbols\" aria-hidden=\"true\">remove</span>\n </button>\n </div>\n </div>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `maplibregl` | Module | `null` | The `maplibre-gl` module. Required. |\n| `style` | String, Object | OpenFreeMap Positron and Dark | A style URL or object, or `{ light, dark }` to follow the theme. |\n| `center` | Array | `[0, 20]` | Starting `[longitude, latitude]`. |\n| `zoom` | Number | `1` | Starting zoom level. |\n| `mapOptions` | Object | `{}` | More MapLibre `Map` options, such as `maxZoom` or `bearing`. |\n\n#### Methods\n\n- `.addRoute(id, coordinates, options)`: Draws a line through `[longitude, latitude]` points in the route color, 4px wide. Options: `color`, `width`, `opacity`, `dashArray`. Replaces an overlay with the same id.\n- `.addArcs(id, arcs, options)`: Draws a curve for each `{ from, to }` in the arc color, 2px wide, with the same options.\n- `.addClusters(id, data, options)`: Draws GeoJSON points, or a GeoJSON URL, as clusters that show their count when the style has fonts and zoom in when clicked. Options: `color`, `radius` (50), `maxZoom` (14). The layers are `${id}-clusters`, `${id}-count` and `${id}-points`.\n- `.removeOverlay(id)`: Removes a route, arcs or clusters.\n- `.map and .markers`: The MapLibre map and markers, for anything else.\n- `.destroy()`: Removes the map and puts the authored children back.\n\n#### Also documented\n\nThe Map section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `map-is-named`: Name the map with aria-label or aria-labelledby, and give it role=\"region\". MapLibre's canvas has no useful name of its own.\n- `map-popup-marker-is-button`: A marker that opens a popup is a <button type=\"button\"> with aria-label, so the keyboard can reach and open it.\n"
204
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Map\nAn interactive map with markers, popups, routes, arcs, clusters and controls, built on MapLibre.\n\nComponent ID: `map`\n\n[Component documentation](https://www.expressivecss.com/map.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/map.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `manual`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A store locator shows each store as a marker button that opens its hours, beside a list of the same stores with addresses. A contact page with one address writes it out and links to a map app.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-09); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-09, `llm.md#map`): MapLibre map with themed OpenFreeMap styles, markers with labels, tooltips, popups and dragging, routes, arcs, clusters, and zoom, compass, locate and full screen controls.\n\nWeb adaptation: The page passes in the maplibre-gl module. Markers, popups and controls are authored HTML moved into MapLibre; routes, arcs and clusters are GeoJSON layers whose colors are read from theme tokens and redrawn when the theme changes.\n\nKnown boundary: Marker placement, the popup opened from its marker with aria-expanded, Escape and map-click closing with focus back on the marker, zoom limits, the compass bearing, theme-colored routes, arcs and clusters redrawn after a theme swap, removeOverlay and destroy are checked in a browser; the locate and full screen controls, marker dragging, spoken output, touch gestures and forced colors remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#map).\n\n#### Contract\n\nA `.map` holds `.map-marker`, `.map-popup` and `.map-controls` children. ExpressiveCSS does not bundle MapLibre. Install `maplibre-gl`, load `maplibre-gl/dist/maplibre-gl.css` before ExpressiveCSS, and pass the module to `MapView.init()`. `AutoInit()` does not start the map. The class is `MapView` so importing it does not hide JavaScript's `Map`. Until the script runs, markers, popups and controls are hidden.\n\nThe default style is OpenFreeMap's Positron in the light theme and its Dark style in the dark theme; neither needs an API key. The map swaps style when the `theme` attribute or the system color scheme changes. Pass `style` as a URL or object to keep one style, or `{ light, dark }`. Keep the provider's attribution visible.\n\n- A `.map-marker` sits at its `data-lng-lat`, longitude first. A value that is not two numbers with a latitude between -90 and 90 is skipped with a console warning. With no content of its own it is a primary dot; content inside replaces the dot. A `.map-marker-label` shows below it (`.top`: above), and a child `.tooltip` shows on hover and focus. - A `<button class=\"map-marker\">` with `aria-controls` naming a `.map-popup` opens it and sets `aria-expanded`. A map click or Escape closes it and focus returns to the marker. A `.map-popup` with its own `data-lng-lat` and no marker opens at init. - `data-draggable` lets the reader drag a marker. When the drag ends, `data-lng-lat` holds the new position and the marker fires `change`. - `.map-controls` stacks icon buttons in the top right, or `.top-left`, `.bottom-left`, `.bottom-right`; `.map-control-group` joins buttons on one surface. `data-action` is `zoom-in`, `zoom-out` (disabled at the zoom limits), `compass` (resets north; the icon follows the bearing), `locate` (centers on the reader and marks the spot) or `fullscreen` (sets `aria-pressed`; hidden where unsupported). - Routes, arcs and clusters are data, added with methods. Lines and clusters take theme colors where the map is and are redrawn in the new colors when the theme changes. The map does not plan routes. - Name the map with `role=\"region\"` and `aria-label`, and give what matters, such as addresses, in text too.\n\n#### Syntax\n\n```html\n<div class=\"map\" role=\"region\" aria-label=\"Chicago stores\">\n <button type=\"button\" class=\"map-marker\" data-lng-lat=\"-87.6278,41.8820\"\n aria-label=\"Loop store\" aria-controls=\"loop\">\n <span class=\"map-marker-label\">Loop</span>\n </button>\n <div class=\"map-popup\" id=\"loop\">\n <h3>Loop store</h3>\n <p>Open until 9 p.m.</p>\n </div>\n <div class=\"map-controls\">\n <div class=\"map-control-group\">\n <button type=\"button\" class=\"icon-button\" data-action=\"zoom-in\" aria-label=\"Zoom in\">\n <span class=\"material-symbols\" aria-hidden=\"true\">add</span>\n </button>\n <button type=\"button\" class=\"icon-button\" data-action=\"zoom-out\" aria-label=\"Zoom out\">\n <span class=\"material-symbols\" aria-hidden=\"true\">remove</span>\n </button>\n </div>\n </div>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `maplibregl` | Module | `null` | The `maplibre-gl` module. Required. |\n| `style` | String, Object | OpenFreeMap Positron and Dark | A style URL or object, or `{ light, dark }` to follow the theme. |\n| `center` | Array | `[0, 20]` | Starting `[longitude, latitude]`. |\n| `zoom` | Number | `1` | Starting zoom level. |\n| `mapOptions` | Object | `{}` | More MapLibre `Map` options, such as `maxZoom` or `bearing`. |\n\n#### Methods\n\n- `.addRoute(id, coordinates, options)`: Draws a line through `[longitude, latitude]` points in the route color, 4px wide. Options: `color`, `width`, `opacity`, `dashArray`. Replaces an overlay with the same id.\n- `.addArcs(id, arcs, options)`: Draws a curve for each `{ from, to }` in the arc color, 2px wide, with the same options.\n- `.addClusters(id, data, options)`: Draws GeoJSON points, or a GeoJSON URL, as clusters that show their count when the style has fonts and zoom in when clicked. Options: `color`, `radius` (50), `maxZoom` (14). The layers are `${id}-clusters`, `${id}-count` and `${id}-points`.\n- `.removeOverlay(id)`: Removes a route, arcs or clusters.\n- `.map and .markers`: The MapLibre map and markers, for anything else.\n- `.destroy()`: Removes the map and puts the authored children back.\n\n#### Also documented\n\nThe Map section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `map-is-named`: Name the map with aria-label or aria-labelledby, and give it role=\"region\". MapLibre's canvas has no useful name of its own.\n- `map-popup-marker-is-button`: A marker that opens a popup is a <button type=\"button\"> with aria-label, so the keyboard can reach and open it.\n"
153
205
  },
154
206
  {
155
207
  "file": "marquee.md",
156
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Marquee\nA row of logos, images or text that scrolls without end.\n\nComponent ID: `marquee`\n\n[Component documentation](https://www.expressivecss.com/marquee.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/marquee.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Twelve customer logos under a landing page hero are a marquee. Four featured products people should open are a carousel or a card grid.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-08); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-08, `llm.md#marquee`): Two identical groups that loop horizontally or vertically, with reverse, edge fades, a paused class, hover and focus pausing, token duration and gap, a reversed direction in RTL and a hand-scrolled strip under reduced motion.\n\nWeb adaptation: A visual copy of the item list marked aria-hidden, with its links taken out of the tab order, so assistive technology and the keyboard meet each item once.\n\nKnown boundary: The group placement, the half-cycle position, the right-to-left and vertical directions, reverse, pausing on hover, focus and .paused, the hidden copy in the accessibility tree and the reduced-motion stop are checked in a browser; spoken output and long-running smoothness remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#marquee).\n\n#### Contract\n\nA `.marquee` holds two identical `<ul class=\"marquee-content\">` groups. The second is a copy that fills the gap as the first scrolls out, so mark it `aria-hidden=\"true\"` and give each link or button in it `tabindex=\"-1\"`. Each group is at least as wide as the strip and spreads short content out, so one copy is enough. Hovering over the strip or focusing a link inside pauses it. CSS only.\n\n#### Syntax\n\n```html\n<div class=\"marquee fade\">\n <ul class=\"marquee-content\" aria-label=\"Customers\">\n <li><img src=\"launchpad.svg\" alt=\"Launchpad\"></li>\n <li><a href=\"/customers/greenleaf\"><img src=\"greenleaf.svg\" alt=\"Greenleaf\"></a></li>\n </ul>\n <ul class=\"marquee-content\" aria-hidden=\"true\">\n <li><img src=\"launchpad.svg\" alt=\"Launchpad\"></li>\n <li><a href=\"/customers/greenleaf\" tabindex=\"-1\"><img src=\"greenleaf.svg\" alt=\"Greenleaf\"></a></li>\n </ul>\n</div>\n```\n\n#### Also documented\n\nThe Marquee section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `marquee-copy-is-hidden`: The second .marquee-content repeats the first so the loop has no gap. Mark it aria-hidden=\"true\" and give each link or button in it tabindex=\"-1\", or a screen reader reads every item twice.\n"
208
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Marquee\nA row of logos, images or text that scrolls without end.\n\nComponent ID: `marquee`\n\n[Component documentation](https://www.expressivecss.com/marquee.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/marquee.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Twelve customer logos under a landing page hero are a marquee. Four featured products people should open are a carousel or a card grid.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-08); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-08, `llm.md#marquee`): Two identical groups that loop horizontally or vertically, with reverse, edge fades, a paused class, hover and focus pausing, token duration and gap, a reversed direction in RTL and a hand-scrolled strip under reduced motion.\n\nWeb adaptation: A visual copy of the item list marked aria-hidden, with its links taken out of the tab order, so assistive technology and the keyboard meet each item once.\n\nKnown boundary: The group placement, the half-cycle position, the right-to-left and vertical directions, reverse, pausing on hover, focus and .paused, the hidden copy in the accessibility tree and the reduced-motion stop are checked in a browser; spoken output and long-running smoothness remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#marquee).\n\n#### Contract\n\nA `.marquee` holds two identical `<ul class=\"marquee-content\">` groups. The second is a copy that fills the gap as the first scrolls out, so mark it `aria-hidden=\"true\"` and give each link or button in it `tabindex=\"-1\"`. Each group is at least as wide as the strip and spreads short content out, so one copy is enough. Hovering over the strip or focusing a link inside pauses it. CSS only.\n\n#### Syntax\n\n```html\n<div class=\"marquee fade\">\n <ul class=\"marquee-content\" aria-label=\"Customers\">\n <li><img src=\"launchpad.svg\" alt=\"Launchpad\"></li>\n <li><a href=\"/customers/greenleaf\"><img src=\"greenleaf.svg\" alt=\"Greenleaf\"></a></li>\n </ul>\n <ul class=\"marquee-content\" aria-hidden=\"true\">\n <li><img src=\"launchpad.svg\" alt=\"Launchpad\"></li>\n <li><a href=\"/customers/greenleaf\" tabindex=\"-1\"><img src=\"greenleaf.svg\" alt=\"Greenleaf\"></a></li>\n </ul>\n</div>\n```\n\n#### Also documented\n\nThe Marquee section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `marquee-copy-is-hidden`: The second .marquee-content repeats the first so the loop has no gap. Mark it aria-hidden=\"true\" and give each link or button in it tabindex=\"-1\", or a screen reader reads every item twice.\n"
209
+ },
210
+ {
211
+ "file": "mega-menu.md",
212
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Mega menu\nSite navigation whose top items open wide panels of grouped links, on native popovers.\n\nComponent ID: `mega-menu`\n\n[Component documentation](https://www.expressivecss.com/mega-menu.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/mega-menu.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A docs site with Products, Solutions and Resources sections uses a mega menu. A text editor with File, Edit and View commands uses a menubar.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-15); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-15, `llm.md#mega-menu`): Links and disclosure buttons in a row, wide panels of grouped links below the bar, a turning chevron and current-page state.\n\nWeb adaptation: Native popovers opened with popovertarget; the browser owns opening, light dismiss, Escape, focus order and the expanded state.\n\nKnown boundary: Panel placement, the expanded state, Escape, focus order into the panel, the turning chevron and stacking on a phone are checked in a browser; screen reader output remains unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#mega-menu).\n\n#### Contract\n\nA `nav.mega-menu` holds links and `<button>`s. Each button names a `.mega-menu-panel` with `popovertarget`, so the panel is a native popover: the browser opens it, closes it on Escape or an outside click, puts it next in the Tab order and exposes the button's expanded state. There is no JavaScript and no menu role; these are links read and tabbed in order. The panel opens below the bar, spans the window less `--md-comp-mega-menu-margin`, and lays its `<section>` groups out in as many columns as fit. A `<small>` in a link is a description line. Use a menubar for application commands.\n\n#### Syntax\n\n```html\n<nav class=\"mega-menu\" aria-label=\"Main\">\n <a href=\"/\" aria-current=\"page\">Home</a>\n <button type=\"button\" popovertarget=\"products\">Products</button>\n <div id=\"products\" class=\"mega-menu-panel\" popover>\n <section>\n <h2>Build</h2>\n <ul>\n <li><a href=\"/editor\">Editor<small>Write and preview</small></a></li>\n <li><a href=\"/api\">API</a></li>\n </ul>\n </section>\n </div>\n <a href=\"/pricing\">Pricing</a>\n</nav>\n```\n\n#### Rules\n\n- `mega-menu-is-a-named-nav`: Write .mega-menu as a <nav> with an aria-label. It is site navigation: a landmark people jump to, not a generic row.\n- `mega-menu-panel-is-a-popover`: Give .mega-menu-panel the popover attribute and open it with popovertarget on its button. The browser then owns opening, Escape, light dismiss and the button's expanded state.\n- `mega-menu-is-not-a-composite-widget`: A mega menu is links and disclosure buttons reached with Tab. A menu, menubar or any other composite role promises arrow-key navigation it does not have; use a menubar for application commands.\n- `mega-menu-items-are-not-menuitems`: Leave the bar's links, buttons and panels without menu or menuitem roles. They are reached with Tab, and those roles promise arrow keys and typeahead the mega menu does not have.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
157
213
  },
158
214
  {
159
215
  "file": "menu.md",
160
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Menu\nMaterial Design 3 menus, from the HTML.\n\nComponent ID: `menu`\n\n[Component documentation](https://www.expressivecss.com/menu.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/menu.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/menus/overview)\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/menus/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/menus/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/menus/guidelines).\n\nSupport (2026-10-06, `llm.md#menu`): Vertical menus, nested flyouts, grouping, standard/vibrant styles, and context menus opened at the pointer.\n\nWeb adaptation: Native menu/li structure; Menu owns opening and keyboard behavior.\n\nKnown boundary: Closing focus exclusion, scoped nested keys, RTL flyout placement and representative item targets have browser checks. Spoken output and full visual parity remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#menu).\n\n#### Contract\n\nClosing menus are excluded from keyboard focus, and close callbacks preserve focus they move. Submenu entry/return keys mirror in RTL; typeahead stays in the active list and skips headings and disabled items. Escape closes the innermost submenu first. Fine-pointer hover opens flyouts; keyboard activation uses Enter, Space, or the submenu entry arrow.\n\nInside open or closed shadow roots, `closeOnClick` controls item clicks just as it does in the document. Submenu triggers keep the parent menu open; outside clicks dismiss it.\n\nA `<menu>` is the surface. Each `<li>` is an item. An icon leads its label by default; add `.suffix` to send it to the trailing edge, since a lone icon is indistinguishable from a leading one in CSS. A `<kbd>` or a `.badge` is always trailing content. An `<li class=\"divider\" role=\"separator\">` is a divider — `<menu>` is a list and its content model permits only `<li>`, so a bare `<hr>` between entries is invalid (it still renders); a `.gap` splits groups; a `.label` is a heading. A nested `<menu>` is a flyout. The trigger’s `data-target` must match the menu’s `id`. `.menu-trigger` is the JavaScript contract.\n\n#### Syntax\n\n```html\n<button class=\"menu-trigger\" data-target=\"menu1\">Drop me</button>\n<menu id=\"menu1\">\n <li><button type=\"button\">One</button></li>\n <li><button type=\"button\">Two</button></li>\n <li class=\"divider\" role=\"separator\"></li>\n <li><button type=\"button\">Three</button></li>\n <li>\n <button type=\"button\">\n <span class=\"material-symbols\" aria-hidden=\"true\">cloud</span>\n <span>Five</span>\n </button>\n </li>\n</menu>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `alignment` | String | `'left'` | Edge the menu is aligned to. `'left'` or `'right'`. |\n| `autoFocus` | Boolean | `true` | If true, automatically focus the menu for keyboard navigation. This option is named `autoFocus`, not `autoTrigger`. |\n| `constrainWidth` | Boolean | `true` | If true, the menu is as wide as the trigger. M3 menus are independently sized — pass `false` for that. |\n| `container` | Element | `null` | Element that will contain the menu. When omitted, the menu is moved next to the trigger. |\n| `coverTrigger` | Boolean | `false` | If false, the menu opens below the trigger (the M3 placement). Pass `true` to cover the trigger. |\n| `closeOnClick` | Boolean | `true` | If true, close the menu when an item is clicked. |\n| `hover` | Boolean | `false` | If true, the menu opens on hover instead of click. |\n| `contextMenu` | Boolean | `false`; `true` with `data-context-menu` | If true, the trigger is a region and the menu opens at the pointer on right-click, long-press, the menu key or Shift+F10, instead of on click. |\n| `inDuration` | Number | `150` | Enter transition duration, in milliseconds. |\n| `outDuration` | Number | `250` | Exit transition duration, in milliseconds. |\n| `onOpenStart` | Function | `null` | Called when the menu starts opening. |\n| `onOpenEnd` | Function | `null` | Called when opening completes; superseded transitions and destruction cancel it. |\n| `onCloseStart` | Function | `null` | Called when the menu starts closing. |\n| `onCloseEnd` | Function | `null` | Called when closing completes; reopening and destruction cancel it. |\n| `onItemClick` | Function | `null` | Called when an item is clicked. Receives the `li`. |\n\n#### Methods\n\n- `.open()`: Open the menu.\n- `.close()`: Close the menu. With `autoFocus`, focus returns to the trigger when it remains in the menu. Focus moved to an outside control is preserved.\n- `.recalculateDimensions()`: While the menu is open, recalculate its dimensions if its contents have changed.\n- `.destroy()`: Destroy the plugin instance and tear down its event handlers.\n\n#### Also documented\n\nThe Menu section of the [API reference](https://www.expressivecss.com/llm.md) also covers Context menu, Initialization, Properties.\n\n#### Rules\n\n- `menu-children-are-list-items`: <menu> is a list: its content model permits only <li>. A bare <hr> between entries is invalid - put the separator inside an <li>, or use role=separator there.\n- `context-menu-region-is-not-a-control`: data-context-menu goes on a region of content. A button or link already has an action for Enter and click; give it its own menu trigger instead.\n"
216
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Menu\nMaterial Design 3 menus, from the HTML.\n\nComponent ID: `menu`\n\n[Component documentation](https://www.expressivecss.com/menu.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/menu.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/menus/overview)\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/menus/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/menus/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/menus/guidelines).\n\nSupport (2026-10-06, `llm.md#menu`): Vertical menus, nested flyouts, grouping, standard/vibrant styles, and context menus opened at the pointer.\n\nWeb adaptation: Native menu/li structure; Menu owns opening and keyboard behavior.\n\nKnown boundary: Closing focus exclusion, scoped nested keys, RTL flyout placement and representative item targets have browser checks. Spoken output and full visual parity remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#menu).\n\n#### Contract\n\nClosing menus are excluded from keyboard focus, and close callbacks preserve focus they move. Submenu entry/return keys mirror in RTL; typeahead stays in the active list and skips headings and disabled items. Escape closes the innermost submenu first. Fine-pointer hover opens flyouts; keyboard activation uses Enter, Space, or the submenu entry arrow.\n\nInside open or closed shadow roots, `closeOnClick` controls item clicks just as it does in the document. Submenu triggers keep the parent menu open; outside clicks dismiss it.\n\nA `<menu>` is the surface. Each `<li>` is an item. An icon leads its label by default; add `.suffix` to send it to the trailing edge, since a lone icon is indistinguishable from a leading one in CSS. A `<kbd>` or a `.badge` is always trailing content. An `<li class=\"divider\" role=\"separator\">` is a divider — `<menu>` is a list and its content model permits only `<li>`, so a bare `<hr>` between entries is invalid and is not styled; a `.gap` splits groups; a `.label` is a heading. A nested `<menu>` is a flyout. The trigger’s `data-target` must match the menu’s `id`. `.menu-trigger` is the JavaScript contract.\n\n#### Syntax\n\n```html\n<button class=\"menu-trigger\" data-target=\"menu1\">Drop me</button>\n<menu id=\"menu1\">\n <li><button type=\"button\">One</button></li>\n <li><button type=\"button\">Two</button></li>\n <li class=\"divider\" role=\"separator\"></li>\n <li><button type=\"button\">Three</button></li>\n <li>\n <button type=\"button\">\n <span class=\"material-symbols\" aria-hidden=\"true\">cloud</span>\n <span>Five</span>\n </button>\n </li>\n</menu>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `alignment` | String | `'left'` | Edge the menu is aligned to. `'left'` or `'right'`. |\n| `autoFocus` | Boolean | `true` | If true, automatically focus the menu for keyboard navigation. A menu opened by hover leaves focus where it was until Space, Enter or an arrow key on the trigger. This option is named `autoFocus`, not `autoTrigger`. |\n| `constrainWidth` | Boolean | `true` | If true, the menu is as wide as the trigger. M3 menus are independently sized — pass `false` for that. |\n| `container` | Element | `null` | Element that will contain the menu. When omitted, the menu is moved next to the trigger. |\n| `coverTrigger` | Boolean | `false` | If false, the menu opens below the trigger (the M3 placement). Pass `true` to cover the trigger. |\n| `placement` | String | `'bottom'` | The side of the trigger the menu opens on when it fits there: `'bottom'` or `'top'`. It opens on the other side when that has more room. |\n| `closeOnClick` | Boolean | `true` | If true, close the menu when an item is clicked. A checkbox item stays open on a click or Space and closes on Enter. A radio item stays open on Space. |\n| `hover` | Boolean | `false` | If true, the menu opens on hover instead of click. |\n| `contextMenu` | Boolean | `false`; `true` with `data-context-menu` | If true, the trigger is a region and the menu opens at the pointer on right-click, long-press, the menu key or Shift+F10, instead of on click. |\n| `inDuration` | Number | `150` | Enter transition duration, in milliseconds. |\n| `outDuration` | Number | `250` | Exit transition duration, in milliseconds. |\n| `onOpenStart` | Function | `null` | Called when the menu starts opening. |\n| `onOpenEnd` | Function | `null` | Called when opening completes; superseded transitions and destruction cancel it. |\n| `onCloseStart` | Function | `null` | Called when the menu starts closing. |\n| `onCloseEnd` | Function | `null` | Called when closing completes; reopening and destruction cancel it. |\n| `onItemClick` | Function | `null` | Called when an item is clicked. Receives the `li`. |\n\n#### Methods\n\n- `.open()`: Open the menu.\n- `.close()`: Close the menu. With `autoFocus`, focus returns to the trigger when it remains in the menu. Focus moved to an outside control is preserved.\n- `.recalculateDimensions()`: While the menu is open, recalculate its dimensions if its contents have changed.\n- `.destroy()`: Destroy the plugin instance and tear down its event handlers.\n\n#### Also documented\n\nThe Menu section of the [API reference](https://www.expressivecss.com/llm.md) also covers Checkbox and radio items, Context menu, Initialization, Properties.\n\n#### Rules\n\n- `menu-children-are-list-items`: <menu> is a list: its content model permits only <li>. A bare <hr> between entries is invalid - put the separator inside an <li>, or use role=separator there.\n- `menu-group-is-named`: A <ul role=group> in a menu is a set of choices, and its name is what a screen reader announces on entering it. Give it aria-label or aria-labelledby.\n- `context-menu-region-is-not-a-control`: data-context-menu goes on a region of content. A button or link already has an action for Enter and click; give it its own menu trigger instead.\n"
217
+ },
218
+ {
219
+ "file": "menubar.md",
220
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Menubar\nAn application's row of menus, such as File, Edit and View, with the menubar keyboard contract.\n\nComponent ID: `menubar`\n\n[Component documentation](https://www.expressivecss.com/menubar.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/menubar.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: File, Edit and View in a document editor are a menubar. One \"More\" button with five actions is a single menu.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-15); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-15, `llm.md#menubar`): A menubar of menu triggers with one tab stop, Left, Right, Home and End along the bar, Down to open, and Left and Right between open menus.\n\nWeb adaptation: Each item opens an ordinary Menu; the Menubar component adds the menubar and menuitem roles and the roving tab stop at runtime.\n\nKnown boundary: Roles, the roving tab stop, arrow, Home and End movement, opening, moving between open menus, flyout rows, Escape and RTL are checked in a browser; screen reader output remains unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#menubar).\n\n#### Contract\n\nA `.menubar` holds `.menu-trigger` buttons, each opening an ordinary Menu written after the bar. The `Menubar` component sets `role=\"menubar\"` on the bar and `role=\"menuitem\"` on each button, and keeps one Tab stop that follows focus. Left and Right move along the bar and wrap, Home and End go to the ends, and both mirror in RTL. Down, Enter and Space open a menu on its first item. In an open menu, Left and Right open the neighbouring menu unless the focused row opens or closes a submenu. Escape closes it and returns to the bar. `AutoInit()` starts it after Menu, and its `menuOptions` (default `{ constrainWidth: false, coverTrigger: false }`) replace each item's Menu options. Name the bar with `aria-label`.\n\n#### Syntax\n\n```html\n<div class=\"menubar\" aria-label=\"Editor\">\n <button type=\"button\" class=\"menu-trigger\" data-target=\"file-menu\">File</button>\n <button type=\"button\" class=\"menu-trigger\" data-target=\"edit-menu\">Edit</button>\n</div>\n<menu id=\"file-menu\">\n <li><button type=\"button\">New</button></li>\n</menu>\n<menu id=\"edit-menu\">\n <li><button type=\"button\">Undo</button></li>\n</menu>\n```\n\n#### Rules\n\n- `menubar-is-named`: Name the menubar with aria-label, such as \"Editor\". A page can hold several, and the name says which commands they are.\n- `menubar-holds-only-triggers`: A .menubar holds only .menu-trigger buttons. Anything else breaks the menubar's arrow-key order; put the menus themselves after the bar.\n"
161
221
  },
162
222
  {
163
223
  "file": "message-scroller.md",
164
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Message scroller\nA chat scroll container that anchors turns, follows streamed replies and keeps its place as history loads.\n\nComponent ID: `message-scroller`\n\n[Component documentation](https://www.expressivecss.com/message-scroller.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/message-scroller.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A support chat that streams replies needs a message scroller so the reply stays in view; a fixed list of three FAQ answers does not.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-08); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-08, `llm.md#message-scroller`): Following streamed output, release on scrolling up, a scroll-to-latest button, turn anchors with a peek of the previous message, prepend preservation, opening at the end, start or last turn, and jumping to a message by id.\n\nWeb adaptation: A native scroll container; the component sets scroll position, a spacer and the button's inert state. The page owns role=\"log\" and aria-busy.\n\nKnown boundary: Following, release on scrolling up, the inert button, prepend preservation, turn anchoring with the spacer, saved-transcript positions and jump to a message are checked in a browser; announcements in a role=\"log\" viewport, touch and scrollbar dragging, and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#message-scroller).\n\n#### Contract\n\nA `.message-scroller` frame holds a `.message-scroller-viewport` and an optional `.message-scroller-button`. Every child of the viewport is one item, usually a `.message` or `.message-group`. The frame fills its parent, so the parent sets the height. `AutoInit()` starts every `.message-scroller`.\n\nWhile the reader is at the end, the viewport follows new content, including text streamed into the last message. Scrolling toward the start with the wheel, touch, the scrollbar or the Up, Page Up and Home keys stops following; reaching the end again, or pressing the button, resumes it. Mark the message that starts a turn with `data-scroll-anchor`, usually each of the user's messages. When an anchor is appended, the viewport scrolls it to the top with 64px of the previous message in view, and blank space below it makes room for the reply until the reply fills the viewport. Older messages inserted at the start keep the visible message in place, and anchors among them do not scroll.\n\nName the viewport with `aria-label` and give it `tabindex=\"0\"` so the keyboard can scroll it. On a conversation that receives messages, make the viewport the log with `role=\"log\"` and `aria-relevant=\"additions\"`, and set `aria-busy=\"true\"` on it while a reply streams. The button is inert while the viewport is at the end; give it an `aria-label`.\n\n#### Syntax\n\n```html\n<div class=\"message-scroller\">\n <div class=\"message-scroller-viewport\" role=\"log\" aria-label=\"Conversation\"\n aria-relevant=\"additions\" tabindex=\"0\">\n <div id=\"m1\" class=\"message end\" data-scroll-anchor>\n <p class=\"message-bubble\">Can you check why the install fails on CI?</p>\n </div>\n <div id=\"m2\" class=\"message\">\n <p class=\"message-bubble\">Sure. Which package manager does the workspace use?</p>\n </div>\n </div>\n <button type=\"button\" class=\"icon-button tonal message-scroller-button\"\n aria-label=\"Scroll to latest message\">\n <span class=\"material-symbols\" aria-hidden=\"true\">arrow_downward</span>\n </button>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `autoScroll` | Boolean | `true` | If true, the viewport follows new content while the reader is at the end, and scrolls a newly appended anchor to the top. |\n| `defaultScrollPosition` | String | `'end'` | Where the transcript opens: `'end'`, `'start'`, or `'last-anchor'`, the start of the last turn. `'last-anchor'` opens at the end when that turn fits. A hidden viewport opens when it is first shown. |\n| `peek` | Number | `64` | Pixels of the previous message left in view above an anchored turn or a message reached with `scrollToMessage()`. |\n\n#### Methods\n\n- `.scrollToEnd()`: Scrolls to the latest message and resumes following.\n- `.scrollToStart()`: Scrolls to the first message.\n- `.scrollToMessage(id)`: Scrolls the element with this id to the top of the viewport, below the peek, and stops following. Returns `false` when the viewport holds no such element.\n- `.destroy()`: Removes the listeners, observers and spacer the component added.\n\n#### Also documented\n\nThe Message scroller section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `message-scroller-viewport-is-named`: Name the viewport with aria-label. It is a focusable scroll region, and a screen reader announces the name when focus lands on it.\n"
224
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Message scroller\nA chat scroll container that anchors turns, follows streamed replies and keeps its place as history loads.\n\nComponent ID: `message-scroller`\n\n[Component documentation](https://www.expressivecss.com/message-scroller.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/message-scroller.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A support chat that streams replies needs a message scroller so the reply stays in view; a fixed list of three FAQ answers does not.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-08); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-08, `llm.md#message-scroller`): Following streamed output, release on scrolling up, a scroll-to-latest button, turn anchors with a peek of the previous message, prepend preservation, opening at the end, start or last turn, and jumping to a message by id.\n\nWeb adaptation: A native scroll container; the component sets scroll position, a spacer and the button's inert state. The page owns role=\"log\" and aria-busy.\n\nKnown boundary: Following, release on scrolling up, the inert button, prepend preservation, turn anchoring with the spacer, saved-transcript positions and jump to a message are checked in a browser; announcements in a role=\"log\" viewport, touch and scrollbar dragging, and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#message-scroller).\n\n#### Contract\n\nA `.message-scroller` frame holds a `.message-scroller-viewport` and an optional `.message-scroller-button`. Every child of the viewport is one item, usually a `.message` or `.message-group`. The frame fills its parent, so the parent sets the height. `AutoInit()` starts every `.message-scroller`.\n\nWhile the reader is at the end, the viewport follows new content, including text streamed into the last message. Scrolling toward the start with the wheel, touch, the scrollbar or the Up, Page Up and Home keys stops following; reaching the end again, or pressing the button, resumes it. Mark the message that starts a turn with `data-scroll-anchor`, usually each of the user's messages. When an anchor is appended, the viewport scrolls it to the top with 64px of the previous message in view, and blank space below it makes room for the reply until the reply fills the viewport. Older messages inserted at the start keep the visible message in place, and anchors among them do not scroll.\n\nName the viewport with `aria-label` and give it `tabindex=\"0\"` so the keyboard can scroll it. On a conversation that receives messages, make the viewport the log with `role=\"log\"` and `aria-relevant=\"additions\"`, and set `aria-busy=\"true\"` on it while a reply streams. The button is inert while the viewport is at the end; give it an `aria-label`.\n\n#### Syntax\n\n```html\n<div class=\"message-scroller\">\n <div class=\"message-scroller-viewport\" role=\"log\" aria-label=\"Conversation\"\n aria-relevant=\"additions\" tabindex=\"0\">\n <div id=\"m1\" class=\"message end\" data-scroll-anchor>\n <p class=\"message-bubble\">Can you check why the install fails on CI?</p>\n </div>\n <div id=\"m2\" class=\"message\">\n <p class=\"message-bubble\">Sure. Which package manager does the workspace use?</p>\n </div>\n </div>\n <button type=\"button\" class=\"icon-button tonal message-scroller-button\"\n aria-label=\"Scroll to latest message\">\n <span class=\"material-symbols\" aria-hidden=\"true\">arrow_downward</span>\n </button>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `autoScroll` | Boolean | `true` | If true, the viewport follows new content while the reader is at the end, and scrolls a newly appended anchor to the top. |\n| `defaultScrollPosition` | String | `'end'` | Where the transcript opens: `'end'`, `'start'`, or `'last-anchor'`, the start of the last turn. `'last-anchor'` opens at the end when that turn fits. A hidden viewport opens when it is first shown. |\n| `peek` | Number | `64` | Pixels of the previous message left in view above an anchored turn or a message reached with `scrollToMessage()`. |\n\n#### Methods\n\n- `.scrollToEnd()`: Scrolls to the latest message and resumes following.\n- `.scrollToStart()`: Scrolls to the first message.\n- `.scrollToMessage(id)`: Scrolls the element with this id to the top of the viewport, below the peek, and stops following. Returns `false` when the viewport holds no such element.\n- `.destroy()`: Removes the listeners, observers and spacer the component added.\n\n#### Also documented\n\nThe Message scroller section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `message-scroller-viewport-is-named`: Name the viewport with aria-label. It is a focusable scroll region, and a screen reader announces the name when focus lands on it.\n"
165
225
  },
166
226
  {
167
227
  "file": "message.md",
168
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Message\nOne message in a conversation, with an optional avatar, header and footer.\n\nComponent ID: `message`\n\n[Component documentation](https://www.expressivecss.com/message.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/message.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A \"Saved\" confirmation after an edit is app feedback, not a conversation. Use a snackbar for it, not a message bubble.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#message`): Start and end alignment, avatar, header, footer actions, message groups and outlined attachment bubbles.\n\nWeb adaptation: Plain div parts on a CSS grid; the page owns role=\"log\" on a live conversation.\n\nKnown boundary: Avatar anchoring, start and end sides in LTR and RTL, and grouped corners are checked in a browser; announcements for appended messages and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#message).\n\n#### Contract\n\nA `.message` lays out one message. It places an optional `.message-avatar`, `.message-header` and `.message-footer` around a single surface, usually a `.message-bubble`. Every other child is the surface, so wrap several elements in one `<div>` to keep them together. Messages sit on the start side; add `end` for the sender's own messages, which also turns the bubble to `primary-container`. Write the parts as `<div>`: outside sectioning content a `<header>` is a banner landmark and a `<footer>` is contentinfo.\n\nThe avatar is an `<img>`, or a `<span>` holding initials or an icon. It lines up with the bottom of the surface and stays clear of the footer. Give the image an empty `alt` when a header already names the sender. Put `role=\"log\"` on a conversation that receives new messages so screen readers announce them. CSS only; there is no JavaScript.\n\n#### Syntax\n\n```html\n<div role=\"log\" aria-label=\"Conversation with Oliver\">\n <div class=\"message end\">\n <p class=\"message-bubble\">Deploying to prod real quick.</p>\n <div class=\"message-footer\">Delivered</div>\n </div>\n <div class=\"message\">\n <img class=\"message-avatar\" src=\"oliver.jpg\" alt=\"\">\n <div class=\"message-header\">Oliver</div>\n <p class=\"message-bubble\">It's 4:55 PM. On a Friday.</p>\n </div>\n</div>\n```\n\n#### Also documented\n\nThe Message section of the [API reference](https://www.expressivecss.com/llm.md) also covers Groups, Footer actions, Attachments, Tokens.\n\n#### Rules\n\n- `message-parts-are-not-landmarks`: Write .message-header and .message-footer as <div>. Outside sectioning content a <header> is a banner landmark and a <footer> is contentinfo, so a conversation would fill the landmark menu with one of each per message.\n"
228
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Message\nOne message in a conversation, with an optional avatar, header and footer.\n\nComponent ID: `message`\n\n[Component documentation](https://www.expressivecss.com/message.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/message.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A \"Saved\" confirmation after an edit is app feedback, not a conversation. Use a snackbar for it, not a message bubble.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#message`): Start and end alignment, avatar, header, footer actions, message groups and outlined attachment bubbles.\n\nWeb adaptation: Plain div parts on a CSS grid; the page owns role=\"log\" on a live conversation.\n\nKnown boundary: Avatar anchoring, start and end sides in LTR and RTL, and grouped corners are checked in a browser; announcements for appended messages and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#message).\n\n#### Contract\n\nA `.message` lays out one message. It places an optional `.message-avatar`, `.message-header` and `.message-footer` around a single surface, usually a `.message-bubble`. Every other child is the surface, so wrap several elements in one `<div>` to keep them together. Messages sit on the start side; add `end` for the sender's own messages, which also turns the bubble to `primary-container`. Write the parts as `<div>`: outside sectioning content a `<header>` is a banner landmark and a `<footer>` is contentinfo.\n\nThe avatar is an `<img>`, or a `<span>` holding initials or an icon. It lines up with the bottom of the surface and stays clear of the footer. Give the image an empty `alt` when a header already names the sender. Put `role=\"log\"` on a conversation that receives new messages so screen readers announce them. CSS only; there is no JavaScript.\n\n#### Syntax\n\n```html\n<div role=\"log\" aria-label=\"Conversation with Oliver\">\n <div class=\"message end\">\n <p class=\"message-bubble\">Deploying to prod real quick.</p>\n <div class=\"message-footer\">Delivered</div>\n </div>\n <div class=\"message\">\n <img class=\"message-avatar\" src=\"oliver.jpg\" alt=\"\">\n <div class=\"message-header\">Oliver</div>\n <p class=\"message-bubble\">It's 4:55 PM. On a Friday.</p>\n </div>\n</div>\n```\n\n#### Also documented\n\nThe Message section of the [API reference](https://www.expressivecss.com/llm.md) also covers Groups, Footer actions, Attachments, Tokens.\n\n#### Rules\n\n- `message-parts-are-not-landmarks`: Write .message-header and .message-footer as <div>. Outside sectioning content a <header> is a banner landmark and a <footer> is contentinfo, so a conversation would fill the landmark menu with one of each per message.\n"
169
229
  },
170
230
  {
171
231
  "file": "mixed-chart.md",
172
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Mixed chart\nColumns, lines and areas together from one data table, with a second scale for values in other units.\n\nComponent ID: `mixed-chart`\n\n[Component documentation](https://www.expressivecss.com/mixed-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/mixed-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Monthly revenue as columns with a dashed target line, or ad spend with conversions on a second scale, is a mixed chart. Three revenue lines for three plans are a line chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-07); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-07, `llm.md#mixed-chart`): Columns, smooth lines and areas drawn per series from a figure's data table by header class, with dashed lines, a second scale for end series with its own fixed range, x labels, a legend, a sparkline modifier, and a pointer and keyboard tooltip with points on the lines.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the columns and SVG are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, both scales, gaps, the legend, the tooltip, cursor points and keyboard movement are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. There is no y axis labelling for either scale, no always-on markers and no stacking.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#mixed-chart).\n\n#### Contract\n\nA `<figure class=\"mixed-chart\">` takes the same `<figcaption>` and `<table>` as a line chart. `AutoInit()` draws each series by its header cell's class: `column` as columns from zero, `area` as a line over a fading fill, and any other series as a line, with `dashed` for a dashed line. Columns sit behind the lines. A legend names the series and a tooltip follows the pointer from row to row. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"mixed-chart\" data-end-min=\"0\" data-end-max=\"35\">\n <figcaption>Revenue ($K), profit margin and customer growth</figcaption>\n <table>\n <thead>\n <tr>\n <th>Month</th>\n <th class=\"column\">Revenue</th>\n <th class=\"area end\">Profit margin</th>\n <th class=\"dashed end\">Customer growth</th>\n </tr>\n </thead>\n <tbody>\n <tr><th>Jan</th><td>$420</td><td>18%</td><td>4%</td></tr>\n <tr><th>Feb</th><td>$460</td><td>19%</td><td>5%</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Mixed chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `mixed-chart-is-a-figure-with-a-table`: Write .mixed-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `mixed-chart-has-a-caption`: Name a .mixed-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
232
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Mixed chart\nColumns, lines and areas together from one data table, with a second scale for values in other units.\n\nComponent ID: `mixed-chart`\n\n[Component documentation](https://www.expressivecss.com/mixed-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/mixed-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Monthly revenue as columns with a dashed target line, or ad spend with conversions on a second scale, is a mixed chart. Three revenue lines for three plans are a line chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-07); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-07, `llm.md#mixed-chart`): Columns, smooth lines and areas drawn per series from a figure's data table by header class, with dashed lines, a second scale for end series with its own fixed range, x labels, round labels for both scales with .axis, aligned to shared grid lines, a legend, a sparkline modifier, and a pointer and keyboard tooltip with points on the lines.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the columns and SVG are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, both scales, gaps, stacked columns, points, straight lines, the legend, the tooltip, cursor points and keyboard movement are checked in a browser; screen reader output, forced colors and enlarged text remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#mixed-chart).\n\n#### Contract\n\nA `<figure class=\"mixed-chart\">` takes the same `<figcaption>` and `<table>` as a line chart. `AutoInit()` draws each series by its header cell's class: `column` as columns from zero, `area` as a line over a fading fill, and any other series as a line, with `dashed` for a dashed line. Columns sit behind the lines. A legend names the series and a tooltip follows the pointer from row to row. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"mixed-chart\" data-end-min=\"0\" data-end-max=\"35\">\n <figcaption>Revenue ($K), profit margin and customer growth</figcaption>\n <table>\n <thead>\n <tr>\n <th>Month</th>\n <th class=\"column\">Revenue</th>\n <th class=\"area end\">Profit margin</th>\n <th class=\"dashed end\">Customer growth</th>\n </tr>\n </thead>\n <tbody>\n <tr><th>Jan</th><td>$420</td><td>18%</td><td>4%</td></tr>\n <tr><th>Feb</th><td>$460</td><td>19%</td><td>5%</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Mixed chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `mixed-chart-is-a-figure-with-a-table`: Write .mixed-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `mixed-chart-has-a-caption`: Name a .mixed-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
173
233
  },
174
234
  {
175
235
  "file": "navigation-bar.md",
176
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Navigation bar\nSwitch between UI views on compact and medium screens.\n\nComponent ID: `navigation-bar`\n\n[Component documentation](https://www.expressivecss.com/navigation-bar.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/navigation-bar.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/navigation-bar/overview)\n\nExample: Overview/Activity within an account page should not displace Home/Search/Profile in app navigation. Use tabs for those local panels and preserve the app destinations across widths.\n\n- expanded window: replace-with [navigation-rail](./navigation-rail.md). Use persistent peer navigation at wider widths.\n\n#### Material mapping\n\nRelationship: component. Google replaces the baseline bar with the flexible bar. The documented stacked/horizontal navigation-bar layouts remain current; do not exclude the family by its name.\n\nUpstream: overview-specs-guidelines-accessibility-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/navigation-bar/overview).\n\nRendered prose reviewed; collapsed token tables, image-only measurements, runtime and full parity unverified. See the design priority component review for requirement-level findings.\n\nSupport (2026-09-07, `llm.md#navigation-bar`): Stacked and horizontal destination layouts.\n\nWeb adaptation: Links navigate; the application marks the current destination.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#navigation-bar).\n\n#### Contract\n\nSwitch between UI views on compact and medium screens. A `nav.navigation-bar` holds 3–5 destinations of equal importance. Destinations do not change from screen to screen. There is no JavaScript — mark the current view with `aria-current=\"page\"` (or `active`).\n\nThis is not the app bar. The app bar names the current page and holds 1–2 actions. Use a navigation bar in compact windows, a bar or rail in medium windows, and an expanded navigation rail in wider windows.\n\nDefault. Icon above the label. The selected destination puts a pill behind the icon and fills the glyph.\n\n#### Syntax\n\n```html\n<nav class=\"navigation-bar\" aria-label=\"Main\">\n <a href=\"/\" aria-current=\"page\">\n <span class=\"material-symbols\" aria-hidden=\"true\">home</span>\n Home\n </a>\n <a href=\"/browse\">\n <span class=\"material-symbols\" aria-hidden=\"true\">explore</span>\n Browse\n </a>\n <a href=\"/radio\">\n <span class=\"material-symbols\" aria-hidden=\"true\">radio</span>\n Radio\n </a>\n <a href=\"/library\">\n <span class=\"material-symbols\" aria-hidden=\"true\">library_music</span>\n Library\n </a>\n</nav>\n```\n\n#### Also documented\n\nThe Navigation bar section of the [API reference](https://www.expressivecss.com/llm.md) also covers Stacked, Horizontal, Fixed.\n\n#### Rules\n\n- `navigation-bar-marks-current`: The active destination needs aria-current=\"page\".\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
236
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Navigation bar\nSwitch between UI views on compact and medium screens.\n\nComponent ID: `navigation-bar`\n\n[Component documentation](https://www.expressivecss.com/navigation-bar.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/navigation-bar.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/navigation-bar/overview)\n\nExample: Overview/Activity within an account page should not displace Home/Search/Profile in app navigation. Use tabs for those local panels and preserve the app destinations across widths.\n\n- expanded window: replace-with [navigation-rail](./navigation-rail.md). Use persistent peer navigation at wider widths.\n\n#### Material mapping\n\nRelationship: component. Google replaces the baseline bar with the flexible bar. The documented stacked/horizontal navigation-bar layouts remain current; do not exclude the family by its name.\n\nUpstream: overview-specs-guidelines-accessibility-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/navigation-bar/overview).\n\nRendered prose reviewed; collapsed token tables, image-only measurements, runtime and full parity unverified. See the design priority component review for requirement-level findings.\n\nSupport (2026-09-07, `llm.md#navigation-bar`): Stacked and horizontal destination layouts.\n\nWeb adaptation: Links navigate; the application marks the current destination.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#navigation-bar).\n\n#### Contract\n\nSwitch between UI views on compact and medium screens. A `nav.navigation-bar` holds 3–5 destinations of equal importance. Destinations do not change from screen to screen. There is no JavaScript — mark the current view with `aria-current=\"page\"` (or `active`).\n\nThis is not the app bar. The app bar names the current page and holds 1–2 actions. Use a navigation bar in compact windows, a bar or rail in medium windows, and an expanded navigation rail in wider windows.\n\nDefault. Icon above the label. The selected destination puts a pill behind the icon and fills the glyph.\n\n#### Syntax\n\n```html\n<nav class=\"navigation-bar\" aria-label=\"Main\">\n <a href=\"/\" aria-current=\"page\">\n <span class=\"material-symbols\" aria-hidden=\"true\">home</span>\n Home\n </a>\n <a href=\"/browse\">\n <span class=\"material-symbols\" aria-hidden=\"true\">explore</span>\n Browse\n </a>\n <a href=\"/radio\">\n <span class=\"material-symbols\" aria-hidden=\"true\">radio</span>\n Radio\n </a>\n <a href=\"/library\">\n <span class=\"material-symbols\" aria-hidden=\"true\">library_music</span>\n Library\n </a>\n</nav>\n```\n\n#### Also documented\n\nThe Navigation bar section of the [API reference](https://www.expressivecss.com/llm.md) also covers Stacked, Horizontal, Fixed.\n\n#### Rules\n\n- `navigation-bar-marks-current`: The active destination needs aria-current=\"page\".\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
177
237
  },
178
238
  {
179
239
  "file": "navigation-rail.md",
180
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Navigation rail\nSwitch between UI views on mid-sized devices.\n\nComponent ID: `navigation-rail`\n\n[Component documentation](https://www.expressivecss.com/navigation-rail.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/navigation-rail.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/navigation-rail/overview)\n\n- compact window: replace-with [navigation-bar](./navigation-bar.md). Compact peer destinations fit the bottom edge.\n- expanded window: variant. Use the expanded rail when labels should remain visible.\n\n#### Material mapping\n\nRelationship: component. Google replaces the baseline rail with the collapsed rail and the drawer with the expanded rail. The documented collapsed, expanded and modal variants remain current. Retain a drawer only for compatibility, an explicit request or a verified target-version nesting gap.\n\nUpstream: overview-specs-guidelines-accessibility-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/navigation-rail/overview).\n\nRendered prose reviewed; collapsed token tables, image-only measurements, runtime and full parity unverified. See the design priority component review for requirement-level findings.\n\n[Specification link](https://m3.material.io/components/navigation-rail/specs); full specs unreviewed.\n\nSupport (2026-09-07, `llm.md#navigation-rail`): Collapsed, expanded, and modal rails.\n\nWeb adaptation: The compact expanded rail uses a scrim and dismissal behavior.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#navigation-rail).\n\n#### Contract\n\nSwitch between UI views on mid-sized devices. A `nav.navigation-rail` holds 3–7 destinations plus an optional FAB. Put it in the same place on every screen.\n\nCollapsed is 96dp with the icon above the label. Add `expanded` for 220–360dp, icon and label on one row, and an extended FAB. The menu button toggles that class (`AutoInit()` starts it). On compact windows an expanded rail is modal — a scrim, and Escape or a scrim tap collapses it. Add `modal` to keep that overlay at every breakpoint.\n\nEscape closes an open nested menu first. A second Escape collapses the modal rail and returns focus from inside it to the rail toggle, unless a close callback moves focus elsewhere.\n\n#### Syntax\n\n```html\n<nav class=\"navigation-rail\" aria-label=\"Main\">\n <button type=\"button\" aria-label=\"Menu\">\n <span class=\"material-symbols\" aria-hidden=\"true\">menu</span>\n </button>\n <a class=\"button extra\" href=\"/compose\">\n <span class=\"material-symbols\" aria-hidden=\"true\">edit</span>\n <span>Compose</span>\n </a>\n <a href=\"/\" aria-current=\"page\">\n <span class=\"material-symbols\" aria-hidden=\"true\">star</span>\n Label\n </a>\n <a href=\"/two\" aria-label=\"Starred, 3 unread\">\n <span class=\"material-symbols\" aria-hidden=\"true\">star<span class=\"badge\">3</span></span>\n Label\n </a>\n</nav>\n```\n\n#### Rules\n\n- `navigation-rail-marks-current`: The active destination needs aria-current=\"page\".\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
240
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Navigation rail\nSwitch between UI views on mid-sized devices.\n\nComponent ID: `navigation-rail`\n\n[Component documentation](https://www.expressivecss.com/navigation-rail.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/navigation-rail.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/navigation-rail/overview)\n\n- compact window: replace-with [navigation-bar](./navigation-bar.md). Compact peer destinations fit the bottom edge.\n- expanded window: variant. Use the expanded rail when labels should remain visible.\n\n#### Material mapping\n\nRelationship: component. Google replaces the baseline rail with the collapsed rail and the drawer with the expanded rail. The documented collapsed, expanded and modal variants remain current. Retain a drawer only for compatibility, an explicit request or a verified target-version nesting gap.\n\nUpstream: overview-specs-guidelines-accessibility-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/navigation-rail/overview).\n\nRendered prose reviewed; collapsed token tables, image-only measurements, runtime and full parity unverified. See the design priority component review for requirement-level findings.\n\n[Specification link](https://m3.material.io/components/navigation-rail/specs); full specs unreviewed.\n\nSupport (2026-09-07, `llm.md#navigation-rail`): Collapsed, expanded, and modal rails.\n\nWeb adaptation: The compact expanded rail uses a scrim and dismissal behavior.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#navigation-rail).\n\n#### Contract\n\nSwitch between UI views on mid-sized devices. A `nav.navigation-rail` holds 3–7 destinations plus an optional FAB. Put it in the same place on every screen.\n\nCollapsed is 96dp with the icon above the label. Add `expanded` for 220–360dp, icon and label on one row, and an extended FAB. The menu button toggles that class (`AutoInit()` starts it). On compact windows an expanded rail is modal — a scrim, and Escape or a scrim tap collapses it. Add `modal` to keep that overlay at every breakpoint. Expanding and collapsing run on the default spatial spring (`--md-comp-nav-rail-motion` and `--md-comp-nav-rail-motion-duration`), so `motion=\"expressive\"` lets the rail overshoot its width slightly before it settles.\n\nEscape closes an open nested menu first. A second Escape collapses the modal rail and returns focus from inside it to the rail toggle, unless a close callback moves focus elsewhere.\n\n#### Syntax\n\n```html\n<nav class=\"navigation-rail\" aria-label=\"Main\">\n <button type=\"button\" aria-label=\"Menu\">\n <span class=\"material-symbols\" aria-hidden=\"true\">menu</span>\n </button>\n <a class=\"button\" href=\"/compose\">\n <span class=\"material-symbols\" aria-hidden=\"true\">edit</span>\n <span>Compose</span>\n </a>\n <a href=\"/\" aria-current=\"page\">\n <span class=\"material-symbols\" aria-hidden=\"true\">star</span>\n Label\n </a>\n <a href=\"/two\" aria-label=\"Starred, 3 unread\">\n <span class=\"material-symbols\" aria-hidden=\"true\">star<span class=\"badge\">3</span></span>\n Label\n </a>\n</nav>\n```\n\n#### Rules\n\n- `navigation-rail-marks-current`: The active destination needs aria-current=\"page\".\n- `navigation-rail-panel-is-a-popover`: Give .navigation-rail-panel the popover attribute and put it right after the destination button that names it with popovertarget. The browser then owns opening, Escape, light dismiss and the button's expanded state.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
241
+ },
242
+ {
243
+ "file": "org-chart.md",
244
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Organization chart\nA reporting line drawn top down, with each person as a card under their manager.\n\nComponent ID: `org-chart`\n\n[Component documentation](https://www.expressivecss.com/org-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/org-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `native`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Twelve people in four teams under a CIO are an org chart. Three hundred employees someone needs to search are a data table with a manager column.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-16); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-16, `llm.md#organization-chart`): Nested lists of native disclosures drawn as cards under their manager, with connector lines, a role band, an avatar and a name, profile links on cards without reports, mirrored order in RTL and sideways scrolling when wide.\n\nWeb adaptation: Native <details> managers and nested lists; every manager is a Tab stop and no tree role is promised.\n\nKnown boundary: Cards side by side under their manager in LTR and RTL, the connector bar and drops, a wide chart scrolling with its start reachable, Enter folding a team away, the caret under reduced motion, the caret in forced colors in Chromium and the nested lists in the accessibility tree are checked in a browser; spoken output, the rest of forced colors and enlarged text remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#org-chart).\n\n#### Contract\n\nA `<ul class=\"org-chart\">` nests lists, one `<li>` per person. A person with reports holds a `<details open>`: its `<summary class=\"org-chart-node\">` is the card and its nested `<ul>` the reports, so the browser folds a team away. A person without reports holds a `<div class=\"org-chart-node\">`, or an `<a class=\"org-chart-node\">` to their profile. A card stacks an optional `.org-chart-role` band, which must come first, an `.avatar` and an `.org-chart-name`. The stylesheet draws the connecting lines. CSS only.\n\n#### Syntax\n\n```html\n<ul class=\"org-chart\" aria-label=\"Company staff\">\n <li>\n <details open>\n <summary class=\"org-chart-node\">\n <span class=\"org-chart-role\">CIO</span>\n <img class=\"avatar large\" src=\"marisol.jpg\" alt=\"\">\n <span class=\"org-chart-name\">Marisol Okafor</span>\n </summary>\n <ul>\n <li>\n <div class=\"org-chart-node\">\n <span class=\"org-chart-role\">R&amp;D</span>\n <span class=\"avatar large\" aria-hidden=\"true\">IH</span>\n <span class=\"org-chart-name\">Ingrid Halvorsen</span>\n </div>\n </li>\n </ul>\n </details>\n </li>\n</ul>\n```\n\n#### Also documented\n\nThe Organization chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `org-chart-is-not-a-tree-widget`: A .org-chart takes no ARIA tree roles. It is lists of disclosures, which keep their own roles and keyboard behavior.\n- `org-chart-manager-holds-no-link`: A manager's card is the summary that opens the team, so a link or button inside it competes with that. Link the profile elsewhere; only a card without reports can be an <a class=\"org-chart-node\">.\n"
181
245
  },
182
246
  {
183
247
  "file": "pagination.md",
184
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Pagination\nA list of pages. The HTML is the component.\n\nComponent ID: `pagination`\n\n[Component documentation](https://www.expressivecss.com/pagination.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/pagination.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A long article with Introduction/Methods/Results needs section links, optionally with scrollspy, not numbered result pages.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-09-13); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. Full visual parity and spoken output unverified.\n\nSupport (2026-09-13, `llm.md#pagination`): Page links, active page, and unavailable previous/next items.\n\nWeb adaptation: CSS-only navigation; the application supplies destinations.\n\nKnown boundary: aria-disabled and pointer-events alone cannot disable a link for keyboard users.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#pagination).\n\n#### Contract\n\nSplit long content into shorter, easier blocks.\n\nAdd pagination links to split long content into shorter blocks. The component is CSS-only: a `pagination` list of links. There is no JavaScript plugin.\n\nThe list lives in a `<nav aria-label=\"Pagination\">` — a page's links are navigation, and the label distinguishes it from every other `<nav>` on the page.\n\n#### Syntax\n\n```html\n<nav class=\"pagination\" aria-label=\"Pagination\">\n <ol>\n <li class=\"disabled\">\n <span aria-hidden=\"true\"><span class=\"material-symbols\" aria-hidden=\"true\">chevron_left</span></span>\n </li>\n <li class=\"active\"><a href=\"?page=1\" aria-current=\"page\">1</a></li>\n <li><a href=\"?page=2\">2</a></li>\n <li><a href=\"?page=3\">3</a></li>\n <li><a href=\"?page=4\">4</a></li>\n <li><a href=\"?page=5\">5</a></li>\n <li>\n <a href=\"?page=2\" aria-label=\"Next page\"><span class=\"material-symbols\" aria-hidden=\"true\">chevron_right</span></a>\n </li>\n </ol>\n</nav>\n```\n\n#### Also documented\n\nThe Pagination section of the [API reference](https://www.expressivecss.com/llm.md) also covers Responsive.\n\n#### Rules\n\n- `pagination-in-nav`: Pagination is navigation and belongs in a labelled <nav>, or it is just a list of numbers.\n- `pagination-marks-current`: The current page needs aria-current=\"page\". A class only colours it.\n- `pagination-disabled-not-a-link`: A disabled control that is still an <a href> stays in the tab order and still navigates. Use a <span>, or drop the href.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
248
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Pagination\nA list of pages. The HTML is the component.\n\nComponent ID: `pagination`\n\n[Component documentation](https://www.expressivecss.com/pagination.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/pagination.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A long article with Introduction/Methods/Results needs section links, optionally with scrollspy, not numbered result pages.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-09-13); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. Full visual parity and spoken output unverified.\n\nSupport (2026-09-13, `llm.md#pagination`): Page links, active page, and unavailable previous/next items.\n\nWeb adaptation: CSS-only navigation; the application supplies destinations.\n\nKnown boundary: aria-disabled and pointer-events alone cannot disable a link for keyboard users.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#pagination).\n\n#### Contract\n\nSplit long content into shorter, easier blocks.\n\nAdd pagination links to split long content into shorter blocks. The component is CSS-only: a `pagination` list of links. There is no JavaScript plugin.\n\nThe list lives in a `<nav aria-label=\"Pagination\">` — a page's links are navigation, and the label distinguishes it from every other `<nav>` on the page.\n\n#### Syntax\n\n```html\n<nav class=\"pagination\" aria-label=\"Pagination\">\n <ol>\n <li class=\"disabled\">\n <span aria-hidden=\"true\"><span class=\"material-symbols\" aria-hidden=\"true\">chevron_left</span></span>\n </li>\n <li class=\"active\"><a href=\"?page=1\" aria-current=\"page\">1</a></li>\n <li><a href=\"?page=2\">2</a></li>\n <li><a href=\"?page=3\">3</a></li>\n <li><a href=\"?page=4\">4</a></li>\n <li><a href=\"?page=5\">5</a></li>\n <li>\n <a href=\"?page=2\" aria-label=\"Next page\"><span class=\"material-symbols\" aria-hidden=\"true\">chevron_right</span></a>\n </li>\n </ol>\n</nav>\n```\n\n#### Also documented\n\nThe Pagination section of the [API reference](https://www.expressivecss.com/llm.md) also covers Responsive.\n\n#### Rules\n\n- `pagination-in-nav`: Pagination is navigation and belongs in a labelled <nav>, or it is just a list of numbers.\n- `pagination-marks-current`: The current page needs aria-current=\"page\". A class only colours it.\n- `pagination-disabled-not-a-link`: A disabled control that is still an <a href> stays in the tab order and still navigates. Use a <span>, or drop the href.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
185
249
  },
186
250
  {
187
251
  "file": "panes.md",
188
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Panes\nMaterial 3 canonical layouts, from the HTML.\n\nComponent ID: `panes`\n\n[Component documentation](https://www.expressivecss.com/panes.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/panes.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/foundations/layout/canonical-examples/overview)\n\nExample: A contact directory with no detail view needs a list. Use list-detail panes when selecting a contact reveals a separate detail region.\n\n- medium container: variant. Stack or collapse panes when their container cannot preserve useful pane widths.\n\n#### Material mapping\n\nRelationship: pattern. Canonical layout pattern, not a standalone component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/foundations/layout/canonical-examples/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\nSupport (2026-09-17, `llm.md#panes`): List-detail, supporting pane, and equal panes.\n\nWeb adaptation: CSS viewport and container queries; the application chooses the active compact pane.\n\nKnown boundary: Compact supporting layouts retain one-active-pane selection rather than automatic stacking. Native zoom, assistive technology and physical-device review remain pending.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#panes).\n\n#### Contract\n\nMaterial 3 canonical layouts — list-detail, supporting pane, and equal panes.\n\nPanes are CSS-only. A container (`panes`, or one of the named aliases `list-detail`, `supporting-pane-layout`, `pane-layout`) holds two or three `pane` children. Layout decisions use the nearest ancestor with inline-size or size containment, falling back to viewport media rules when none exists. Set `container-type: inline-size` on the containing column for nested layouts. The pane layout cannot query its own size. Compact widths (`< 600px`) use 16px inline margins; wider layouts use 24px margins and spacers. Below 840px only one pane shows at a time; at 840px and up the panes sit side by side. Google recommends stacking supporting content below primary content on compact and medium layouts; the current one-active-pane behavior is a framework adaptation. Footer actions wrap to keep translated labels reachable.\n\nAny of `pane`, `list-pane`, `primary-pane`, `detail-pane`, and `supporting-pane` counts as a pane child — the specific names are for readability.\n\n#### Syntax\n\n```html\n<div class=\"panes list-detail\">\n <div class=\"list-pane\">\n <header>\n <h2>Inbox</h2>\n </header>\n <ul class=\"list\">\n <li><a href=\"/inbox/brunch\">Brunch this weekend?</a></li>\n <li><a href=\"/inbox/design-review\">Design review</a></li>\n </ul>\n </div>\n <div class=\"detail-pane\">\n <header>\n <button aria-label=\"Back\"><span class=\"material-symbols\" aria-hidden=\"true\">arrow_back</span></button>\n <h2>Brunch this weekend?</h2>\n </header>\n <section>\n <p>Detail content.</p>\n </section>\n </div>\n</div>\n```\n\n#### Also documented\n\nThe Panes section of the [API reference](https://www.expressivecss.com/llm.md) also covers List-detail, Compact, Supporting pane, Equal, Three-pane, Appearance, Pane anatomy, Tokens.\n\n#### Rules\n\n- `pane-is-not-main`: A document has one <main>, and a pane is a region inside it, not another one. Use <section>.\n"
252
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Panes\nMaterial 3 canonical layouts, from the HTML.\n\nComponent ID: `panes`\n\n[Component documentation](https://www.expressivecss.com/panes.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/panes.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/foundations/layout/canonical-examples/overview)\n\nExample: A contact directory with no detail view needs a list. Use list-detail panes when selecting a contact reveals a separate detail region.\n\n- medium container: variant. Stack or collapse panes when their container cannot preserve useful pane widths.\n\n#### Material mapping\n\nRelationship: pattern. Canonical layout pattern, not a standalone component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/foundations/layout/canonical-examples/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\nSupport (2026-09-17, `llm.md#panes`): List-detail, supporting pane, and equal panes.\n\nWeb adaptation: CSS viewport and container queries; the application chooses the active compact pane.\n\nKnown boundary: Compact supporting layouts retain one-active-pane selection rather than automatic stacking. Native zoom, assistive technology and physical-device review remain pending.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#panes).\n\n#### Contract\n\nMaterial 3 canonical layouts — list-detail, supporting pane, and equal panes.\n\nPanes are CSS-only. A container (`panes`, or one of the named aliases `list-detail`, `supporting-pane-layout`, `pane-layout`) holds two or three `pane` children. Layout decisions use the nearest ancestor with inline-size or size containment, falling back to viewport media rules when none exists. Set `container-type: inline-size` on the containing column for nested layouts. The pane layout cannot query its own size. Compact widths (`< 600px`) use 16px inline margins; wider layouts use 24px margins and spacers. Below 840px only one pane shows at a time; at 840px and up the panes sit side by side. Google recommends stacking supporting content below primary content on compact and medium layouts; the current one-active-pane behavior is a framework adaptation. Footer actions wrap to keep translated labels reachable.\n\nAny of `pane`, `list-pane`, `primary-pane`, `detail-pane`, and `supporting-pane` counts as a pane child — the specific names are for readability.\n\n#### Syntax\n\n```html\n<div class=\"panes list-detail\">\n <div class=\"list-pane\">\n <header>\n <h2>Inbox</h2>\n </header>\n <ul class=\"list\">\n <li><a href=\"/inbox/brunch\">Brunch this weekend?</a></li>\n <li><a href=\"/inbox/design-review\">Design review</a></li>\n </ul>\n </div>\n <div class=\"detail-pane\">\n <header>\n <button aria-label=\"Back\"><span class=\"material-symbols\" aria-hidden=\"true\">arrow_back</span></button>\n <h2>Brunch this weekend?</h2>\n </header>\n <section>\n <p>Detail content.</p>\n </section>\n </div>\n</div>\n```\n\n#### Also documented\n\nThe Panes section of the [API reference](https://www.expressivecss.com/llm.md) also covers List-detail, Compact, Supporting pane, Equal, Three-pane, Resizing, Appearance, Pane anatomy, Tokens.\n\n#### Rules\n\n- `pane-is-not-main`: A document has one <main>, and a pane is a region inside it, not another one. Use <section>.\n"
189
253
  },
190
254
  {
191
255
  "file": "particles.md",
192
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Particles\nDrifting dots behind a section that lean toward the pointer.\n\nComponent ID: `particles`\n\n[Component documentation](https://www.expressivecss.com/particles.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/particles.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A product landing page can put particles behind its headline and sign-up button. Its pricing table and checkout form sit on a plain surface.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-09); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-09, `llm.md#particles`): A canvas that covers its positioned ancestor with drifting dots that fade near the edges and lean toward the pointer, with quantity, staticity, ease, size, color and drift options and the theme on-surface color by default.\n\nWeb adaptation: A canvas drawn with requestAnimationFrame, aria-hidden with pointer events off. The dot color comes from the canvas's CSS color, read each frame so theme changes apply. Reduced motion draws the dots once without motion.\n\nKnown boundary: The canvas covering its box, the on-surface default and the color option, following the pointer only while it is over the canvas, drift, refitting on resize, destroy and the still drawing under reduced motion are checked in a browser; frame rate on low-end devices, forced colors and contrast of text over the dots remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#particles).\n\n#### Contract\n\nDrifting dots behind a section, such as a hero or a sign-in panel, that lean toward the pointer.\n\nA `<canvas class=\"particles\" aria-hidden=\"true\">` covers its nearest positioned ancestor and lets clicks through. Each dot drifts slowly, fades out near the edges and moves toward the pointer while it is over the canvas. A dot that leaves the canvas is replaced inside it. `AutoInit()` starts every `canvas.particles`.\n\n#### Syntax\n\n```html\n<div style=\"position: relative; height: 320px\">\n <h2>Particles</h2>\n <canvas class=\"particles\" aria-hidden=\"true\"></canvas>\n</div>\n\n<script>\nExpressive.Particles.init(document.querySelector('canvas.particles'), { quantity: 150, vy: -0.3, staticity: 20 });\n</script>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `quantity` | Number | `100` | Number of dots. |\n| `staticity` | Number | `50` | How firmly dots hold their place against the pointer; lower follows further. |\n| `ease` | Number | `50` | Frames a dot takes to catch up with the pointer; higher is slower. |\n| `size` | Number | `0.4` | Base radius in pixels. Each dot adds 0 or 1. |\n| `color` | String | `''` | A CSS color. Empty uses the canvas's CSS `color`. |\n| `vx` | Number | `0` | Horizontal drift in pixels per frame; negative drifts left. |\n| `vy` | Number | `0` | Vertical drift in pixels per frame; negative drifts up. |\n\n#### Methods\n\n- `.destroy()`: Stops drawing, removes the pointer listener and clears the canvas.\n\n#### Rules\n\n- `particles-canvas-is-hidden`: Particles are decoration. Mark the canvas aria-hidden=\"true\".\n"
256
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Particles\nDrifting dots behind a section that lean toward the pointer.\n\nComponent ID: `particles`\n\n[Component documentation](https://www.expressivecss.com/particles.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/particles.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A product landing page can put particles behind its headline and sign-up button. Its pricing table and checkout form sit on a plain surface.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-09); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-09, `llm.md#particles`): A canvas that covers its positioned ancestor with drifting dots that fade near the edges and lean toward the pointer, with quantity, staticity, ease, size, color and drift options and the theme on-surface color by default.\n\nWeb adaptation: A canvas drawn with requestAnimationFrame, aria-hidden with pointer events off. The dot color comes from the canvas's CSS color, read each frame so theme changes apply. Reduced motion draws the dots once without motion.\n\nKnown boundary: The canvas covering its box, the on-surface default and the color option, following the pointer only while it is over the canvas, drift, refitting on resize, destroy and the still drawing under reduced motion are checked in a browser; frame rate on low-end devices, forced colors and contrast of text over the dots remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#particles).\n\n#### Contract\n\nDrifting dots behind a section, such as a hero or a sign-in panel, that lean toward the pointer.\n\nA `<canvas class=\"particles\" aria-hidden=\"true\">` covers its nearest positioned ancestor and lets clicks through. Each dot drifts slowly, fades out near the edges and moves toward the pointer while it is over the canvas. A dot that leaves the canvas is replaced inside it. `AutoInit()` starts every `canvas.particles`.\n\n#### Syntax\n\n```html\n<div style=\"position: relative; height: 320px\">\n <h2>Particles</h2>\n <canvas class=\"particles\" aria-hidden=\"true\"></canvas>\n</div>\n\n<script>\nExpressive.Particles.init(document.querySelector('canvas.particles'), { quantity: 150, vy: -0.3, staticity: 20 });\n</script>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `quantity` | Number | `100` | Number of dots. |\n| `staticity` | Number | `50` | How firmly dots hold their place against the pointer; lower follows further. |\n| `ease` | Number | `50` | Frames a dot takes to catch up with the pointer; higher is slower. |\n| `size` | Number | `0.4` | Base radius in pixels. Each dot adds 0 or 1. |\n| `color` | String | `''` | A CSS color. Empty uses the canvas's CSS `color`. |\n| `vx` | Number | `0` | Horizontal drift in pixels per frame; negative drifts left. |\n| `vy` | Number | `0` | Vertical drift in pixels per frame; negative drifts up. |\n\n#### Methods\n\n- `.destroy()`: Stops drawing, removes the pointer listener and clears the canvas.\n\n#### Rules\n\n- `particles-canvas-is-hidden`: Particles are decoration. Mark the canvas aria-hidden=\"true\".\n"
193
257
  },
194
258
  {
195
259
  "file": "pie-chart.md",
196
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Pie chart\nParts of a whole drawn as slices from a data table, as a pie or a donut with its total.\n\nComponent ID: `pie-chart`\n\n[Component documentation](https://www.expressivecss.com/pie-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/pie-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Revenue split across three plans, with the total in the middle, is a donut pie chart. Revenue from twelve sources is a bar chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-07); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-07, `llm.md#pie-chart`): Slices drawn clockwise from the top from a figure's data table, with a legend of each row's cell and share, donut, values and sparkline modifiers, a <tfoot> total in the donut hole, further columns in the tooltip, and a pointer and keyboard tooltip that highlights the slice.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the slices, values, total and legend are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, slice geometry and colors, the legend and shares, the donut hole and total, values, zero and missing values, RTL, the tooltip, pointer angle hit testing, keyboard movement and destroy are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. Zero and negative values have no slice; there are no slice labels outside the pie.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#pie-chart).\n\n#### Contract\n\nA `<figure class=\"pie-chart\">` takes the same `<figcaption>` and `<table>` as a column chart. `AutoInit()` draws the first value column as slices of the total, clockwise from the top in table order, with a legend of every row, its cell and its share, and a tooltip that follows the pointer round the pie. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"pie-chart donut\">\n <figcaption>Lead sources, last six months</figcaption>\n <table>\n <thead>\n <tr><th>Source</th><th>Leads</th></tr>\n </thead>\n <tbody>\n <tr><th>Referrals</th><td>558</td></tr>\n <tr><th>Organic search</th><td>434</td></tr>\n <tr><th>Paid campaigns</th><td>248</td></tr>\n </tbody>\n <tfoot>\n <tr><th>Total leads</th><td>1,240</td></tr>\n </tfoot>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Pie chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `pie-chart-is-a-figure-with-a-table`: Write .pie-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `pie-chart-has-a-caption`: Name a .pie-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
260
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Pie chart\nParts of a whole drawn as slices from a data table, as a pie or a donut with its total.\n\nComponent ID: `pie-chart`\n\n[Component documentation](https://www.expressivecss.com/pie-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/pie-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Revenue split across three plans, with the total in the middle, is a donut pie chart. Revenue from twelve sources is a bar chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-07); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-07, `llm.md#pie-chart`): Slices drawn clockwise from the top from a figure's data table, with a legend of each row's cell and share, donut, values and sparkline modifiers, a <tfoot> total in the donut hole, labels with each slice's name outside it, further columns in the tooltip, and a pointer and keyboard tooltip that highlights the slice.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the slices, values, total and legend are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, slice geometry and colors, the legend and shares, the donut hole and total, values, outside names and their leader lines, data-other grouping, zero and missing values, RTL, the tooltip, pointer angle hit testing, keyboard movement and destroy are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. Zero and negative values have no slice. The .labels names only move up or down their side, so many thin slices near the top or bottom can run into the caption or legend unless data-other groups them.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#pie-chart).\n\n#### Contract\n\nA `<figure class=\"pie-chart\">` takes the same `<figcaption>` and `<table>` as a column chart. `AutoInit()` draws the first value column as slices of the total, clockwise from the top in table order, with a legend of every row, its cell and its share, and a tooltip that follows the pointer round the pie. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"pie-chart donut\">\n <figcaption>Lead sources, last six months</figcaption>\n <table>\n <thead>\n <tr><th>Source</th><th>Leads</th></tr>\n </thead>\n <tbody>\n <tr><th>Referrals</th><td>558</td></tr>\n <tr><th>Organic search</th><td>434</td></tr>\n <tr><th>Paid campaigns</th><td>248</td></tr>\n </tbody>\n <tfoot>\n <tr><th>Total leads</th><td>1,240</td></tr>\n </tfoot>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Pie chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `pie-chart-is-a-figure-with-a-table`: Write .pie-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `pie-chart-has-a-caption`: Name a .pie-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
197
261
  },
198
262
  {
199
263
  "file": "popover.md",
200
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Popover\nA panel that opens from a button and stays until dismissed, on the native popover attribute.\n\nComponent ID: `popover`\n\n[Component documentation](https://www.expressivecss.com/popover.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/popover.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `native`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A label for an icon button that shows on hover is a tooltip. A popover would make the user press the button just to learn what it does.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#popover`): Native popover panels anchored below, above or beside their button with automatic flipping, rich-tooltip styling, an actions row and frosted glass.\n\nWeb adaptation: The browser owns opening, light dismiss, Escape, focus return and the button's expanded state; CSS anchor positioning places the panel.\n\nKnown boundary: Anchoring and flipping in LTR and RTL, light dismiss, Escape and focus return are checked in a browser, and the expanded state in Chromium; spoken output and engines without anchor positioning remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#popover).\n\n#### Contract\n\nA `.popover` is a `[popover]` element, and the button that opens it names it with `popovertarget` (or `commandfor` with `command=\"toggle-popover\"`). The browser owns the behavior: pressing the button toggles the panel, Escape or a click outside closes it, focus returns to the button, and the button reports the panel as expanded. CSS anchor positioning places the panel 4px below the button with their start edges aligned, and flips it to the other side when it would not fit. CSS only; there is no JavaScript.\n\nIt looks like a rich tooltip but behaves as a disclosure: it opens on press, stays open while the user reads or acts, and can hold links and buttons. A small one behind an info icon button is a toggletip. Add `top` to open above, or `start` or `end` to open beside the button, centered on it.\n\n#### Syntax\n\n```html\n<button class=\"icon-button\" type=\"button\" popovertarget=\"storage-help\" aria-label=\"About storage\">\n <span class=\"material-symbols\" aria-hidden=\"true\">info</span>\n</button>\n<div id=\"storage-help\" class=\"popover\" popover>\n <h3>Storage</h3>\n <p>Files in the trash count toward your quota until you empty it.</p>\n <div class=\"popover-actions\">\n <button type=\"button\" class=\"text\">Empty trash</button>\n </div>\n</div>\n```\n\n#### Also documented\n\nThe Popover section of the [API reference](https://www.expressivecss.com/llm.md) also covers Accessibility, Tokens.\n\n#### Rules\n\n- `popover-is-native`: Give a .popover the popover attribute. Without it the panel is never in the top layer, never light-dismisses and its button reports no expanded state.\n- `popover-target-exists`: popovertarget must name the id of a [popover] element in the page. A dangling id leaves a button that does nothing.\n- `popover-is-not-a-tooltip`: A popover opens on press and can hold links and buttons, which a tooltip never can. Leave the role off; the browser relates the panel to its button.\n"
264
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Popover\nA panel that opens from a button and stays until dismissed, on the native popover attribute.\n\nComponent ID: `popover`\n\n[Component documentation](https://www.expressivecss.com/popover.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/popover.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `native`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A label for an icon button that shows on hover is a tooltip. A popover would make the user press the button just to learn what it does.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#popover`): Native popover panels anchored below, above or beside their button with automatic flipping, rich-tooltip styling, an actions row and frosted glass.\n\nWeb adaptation: The browser owns opening, light dismiss, Escape, focus return and the button's expanded state; CSS anchor positioning places the panel.\n\nKnown boundary: Anchoring and flipping in LTR and RTL, light dismiss, Escape and focus return are checked in a browser, and the expanded state in Chromium; spoken output and engines without anchor positioning remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#popover).\n\n#### Contract\n\nA `.popover` is a `[popover]` element, and the button that opens it names it with `popovertarget` (or `commandfor` with `command=\"toggle-popover\"`). The browser owns the behavior: pressing the button toggles the panel, Escape or a click outside closes it, focus returns to the button, and the button reports the panel as expanded. CSS anchor positioning places the panel 4px below the button with their start edges aligned, and flips it to the other side when it would not fit. A browser without anchor positioning centers the panel in the viewport instead. CSS only; there is no JavaScript.\n\nIt looks like a rich tooltip but behaves as a disclosure: it opens on press, stays open while the user reads or acts, and can hold links and buttons. A small one behind an info icon button is a toggletip. Add `top` to open above, or `start` or `end` to open beside the button, centered on it.\n\n#### Syntax\n\n```html\n<button class=\"icon-button\" type=\"button\" popovertarget=\"storage-help\" aria-label=\"About storage\">\n <span class=\"material-symbols\" aria-hidden=\"true\">info</span>\n</button>\n<div id=\"storage-help\" class=\"popover\" popover>\n <h3>Storage</h3>\n <p>Files in the trash count toward your quota until you empty it.</p>\n <div class=\"popover-actions\">\n <button type=\"button\" class=\"text\">Empty trash</button>\n </div>\n</div>\n```\n\n#### Also documented\n\nThe Popover section of the [API reference](https://www.expressivecss.com/llm.md) also covers Accessibility, Tokens.\n\n#### Rules\n\n- `popover-is-native`: Give a .popover the popover attribute. Without it the panel is never in the top layer, never light-dismisses and its button reports no expanded state.\n- `popover-target-exists`: popovertarget must name the id of a [popover] element in the page. A dangling id leaves a button that does nothing.\n- `popover-is-not-a-tooltip`: A popover opens on press and can hold links and buttons, which a tooltip never can. Leave the role off; the browser relates the panel to its button.\n"
201
265
  },
202
266
  {
203
267
  "file": "progress.md",
204
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Progress indicators\nActivity and progress. The HTML is the indicator.\n\nComponent ID: `progress`\n\n[Component documentation](https://www.expressivecss.com/preloader.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/preloader.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/progress-indicators/overview)\n\nExample: A brief unknown refresh fits a loading indicator. An upload needs accurate determinate progress, even if short. Keep circular progress when an unknown process becomes determinate or runs inside a button; indeterminate linear bars remain supported. Never invent a percentage.\n\n#### Material mapping\n\nRelationship: component. Google replaces most short indeterminate circular waits with a loading indicator. Keep circular progress in buttons and for processes that become determinate; keep determinate and indeterminate linear progress. Deprecated baseline token sets do not deprecate these families.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/progress-indicators/overview).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/progress-indicators/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/progress-indicators/guidelines).\n\nSupport (2026-09-13, `llm.md#progress-indicators`): Linear and circular progress, determinate and indeterminate.\n\nWeb adaptation: Prefer native progress where applicable; supply accessible state for custom hosts.\n\nKnown boundary: Forced-colors rendering is checked in browser emulation; Windows high-contrast and spoken output still require manual review.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#progress).\n\n#### Contract\n\nNative and custom progress use system colors in forced-colors mode, outline empty linear tracks, and preserve circular value segments. Reduced motion retains static feedback, including the legacy preloader. Browser emulation checks do not replace Windows high-contrast or screen-reader review.\n\nActivity and progress indicators for content that takes time to load.\n\nIf content will take a while to load, give the user feedback. Expressive ships linear progress bars and circular spinners. Both are CSS-only — there is no JavaScript plugin.\n\n#### Syntax\n\n```html\n<progress class=\"progress\" value=\"70\" max=\"100\"></progress>\n\n<div class=\"progress\" role=\"progressbar\" aria-valuenow=\"70\" aria-valuemin=\"0\" aria-valuemax=\"100\">\n <div class=\"determinate\" style=\"width: 70%\"></div>\n</div>\n\n<div class=\"progress\" role=\"progressbar\" aria-valuenow=\"70\" aria-valuemin=\"0\" aria-valuemax=\"100\" style=\"--md-comp-progress-value: 70%\"></div>\n```\n\n#### Also documented\n\nThe Progress indicators section of the [API reference](https://www.expressivecss.com/llm.md) also covers Linear, Circular.\n\n#### Rules\n\n- `div-progress-reports-progress`: <progress> reports itself. A <div class=\"progress\"> is a bar drawn with CSS and reports nothing - it needs role=\"progressbar\", plus aria-valuenow/min/max when it is determinate.\n- `determinate-progress-reports-its-value`: role=\"progressbar\" with no aria-valuenow is an *indeterminate* bar. A determinate one draws a width the user can see and must report the same number: aria-valuenow, plus valuemin/valuemax when they are not 0 and 100.\n"
268
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Progress indicators\nActivity and progress. The HTML is the indicator.\n\nComponent ID: `progress`\n\n[Component documentation](https://www.expressivecss.com/preloader.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/preloader.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/progress-indicators/overview)\n\nExample: A brief unknown refresh fits a loading indicator. An upload needs accurate determinate progress, even if short. Keep circular progress when an unknown process becomes determinate or runs inside a button; indeterminate linear bars remain supported. Never invent a percentage.\n\n#### Material mapping\n\nRelationship: component. Google replaces most short indeterminate circular waits with a loading indicator. Keep circular progress in buttons and for processes that become determinate; keep determinate and indeterminate linear progress. Deprecated baseline token sets do not deprecate these families.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/progress-indicators/overview).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/progress-indicators/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/progress-indicators/guidelines).\n\nSupport (2026-09-13, `llm.md#progress-indicators`): Linear and circular progress, determinate and indeterminate.\n\nWeb adaptation: Prefer native progress where applicable; supply accessible state for custom hosts.\n\nKnown boundary: Forced-colors rendering is checked in browser emulation; Windows high-contrast and spoken output still require manual review.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#progress).\n\n#### Contract\n\nNative and custom progress use system colors in forced-colors mode, outline empty linear tracks, and preserve circular value segments. Reduced motion retains static feedback. Browser emulation checks do not replace Windows high-contrast or screen-reader review.\n\nActivity and progress indicators for content that takes time to load.\n\nIf content will take a while to load, give the user feedback. Expressive ships linear progress bars and circular spinners. Both are CSS-only — there is no JavaScript plugin.\n\n#### Syntax\n\n```html\n<progress class=\"progress\" value=\"70\" max=\"100\"></progress>\n\n<div class=\"progress\" role=\"progressbar\" aria-valuenow=\"70\" aria-valuemin=\"0\" aria-valuemax=\"100\">\n <div class=\"determinate\" style=\"width: 70%\"></div>\n</div>\n\n<div class=\"progress\" role=\"progressbar\" aria-valuenow=\"70\" aria-valuemin=\"0\" aria-valuemax=\"100\" style=\"--md-comp-progress-value: 70%\"></div>\n```\n\n#### Also documented\n\nThe Progress indicators section of the [API reference](https://www.expressivecss.com/llm.md) also covers Linear, Circular, Meter.\n\n#### Rules\n\n- `div-progress-reports-progress`: <progress> reports itself. A <div class=\"progress\"> is a bar drawn with CSS and reports nothing - it needs role=\"progressbar\", plus aria-valuenow/min/max when it is determinate.\n- `determinate-progress-reports-its-value`: role=\"progressbar\" with no aria-valuenow is an *indeterminate* bar. A determinate one draws a width the user can see and must report the same number: aria-valuenow, plus valuemin/valuemax when they are not 0 and 100.\n"
269
+ },
270
+ {
271
+ "file": "pull-to-refresh.md",
272
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Pull-to-refresh\nRefreshes a list or page when a person pulls down at its top, or presses its refresh button.\n\nComponent ID: `pull-to-refresh`\n\n[Component documentation](https://www.expressivecss.com/pull-to-refresh.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/pull-to-refresh.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: An inbox that fetches new mail on demand takes pull-to-refresh with a Refresh button; a live chat that receives messages on its own needs a message scroller instead.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-24); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-24, `llm.md#pull-to-refresh`): A touch pull from a scroll position of 0 past a threshold, a refresh button and refresh() method that start the same refresh, an expressive:refresh event and onRefresh callback whose detail takes waitUntil(), complete(), aria-busy while refreshing, a status region that announces the start and the finish with i18n strings, contained overscroll on the container and the page, and containers that scroll themselves or pull the page.\n\nWeb adaptation: Touch events with a non-passive touchmove, because a pointer drag the browser takes for a pan ends in pointercancel. The author's loading indicator carries the role and name; the script sets a class, two custom properties and aria-busy, and adds a visually hidden role=\"status\" region because the indicator is hidden once the refresh ends.\n\nKnown boundary: The pull threshold, waitUntil() and complete(), drags that do nothing, inner and surrounding scrollers, a second finger, the busy state, the refresh button, page scrolling, reduced motion and destroy() are checked in a browser, with real touch in Chromium and dispatched touch events in Firefox and WebKit; real phones, pen input, spoken output from the status region and forced colors remain unverified. There is no pull from the bottom and no nesting.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#pull-to-refresh).\n\n#### Contract\n\nA `.pull-to-refresh` container holds a `.loading-indicator` as a direct child, a `<button data-refresh>` and the content. `AutoInit()` starts every `.pull-to-refresh`. The container can scroll itself, with `overflow-y: auto` or `scroll`, or sit in a page that scrolls; either way a pull counts only at a scroll position of 0.\n\nA touch pull from the top brings the indicator down with the finger and fades it in. Releasing past the threshold starts a refresh. Releasing short of it, a cancelled touch, a drag that moves up or sideways first, and a second finger do nothing, and a mouse drag never pulls. A drag that a scrolled-down scroller can take, whether inside the container or around it, scrolls that scroller instead. The refresh button starts the same refresh, so keyboard, mouse and switch users can refresh without a touch screen.\n\nA refresh fires `expressive:refresh` from the container and runs `onRefresh`. Both get a `detail` with `waitUntil(promise)`. The indicator stays, and the container has `aria-busy=\"true\"`, until every promise passed to `waitUntil()` settles, fulfilled or rejected, or the page calls `complete()`. Without either, the refresh keeps running. Pulls, button presses and `refresh()` calls during a refresh do nothing. A rejected promise still ends the refresh, and a rejection the page does not catch reaches `unhandledrejection` as usual. An `onRefresh` that throws ends the refresh it started, and the error still reaches the page.\n\n#### Syntax\n\n```html\n<div class=\"pull-to-refresh\" style=\"height: 24rem; overflow-y: auto\">\n <span class=\"loading-indicator contained\" role=\"status\" aria-label=\"Refreshing\"></span>\n <button type=\"button\" class=\"text\" data-refresh>Refresh</button>\n <ul class=\"list\">\n <li>Inbox</li>\n </ul>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `threshold` | Number | `64` | How far a finger must pull down, in pixels, for the release to refresh. A value that is not a positive number falls back to `64`. |\n| `onRefresh` | Function | `null` | Called when a refresh starts, with the event's `detail`. |\n| `i18n` | Object | `{ refreshing: 'Refreshing', refreshed: 'Refreshed' }` | What the status region announces when a refresh starts and when it finishes. |\n\n#### Methods\n\n- `.refresh()`: Starts a refresh, as a pull or the refresh button does. Does nothing while one runs.\n- `.complete()`: Ends the running refresh: the indicator goes back up and `aria-busy` is removed.\n- `.destroy()`: Removes the listeners and the status region, and ends a running refresh without an event.\n\n#### Also documented\n\nThe Pull-to-refresh section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `pull-to-refresh-has-a-refresh-button`: Put a <button data-refresh> inside each .pull-to-refresh. Pulling needs a touch screen, and the button is the way to refresh for a keyboard, mouse or switch user.\n- `pull-to-refresh-has-an-indicator`: Give a .pull-to-refresh a .loading-indicator as a direct child. It is what comes down with a pull and shows while the refresh runs.\n"
205
273
  },
206
274
  {
207
275
  "file": "questionnaire.md",
208
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Questionnaire\nA form that asks one question at a time, with single and multiple choice, free text and skippable questions.\n\nComponent ID: `questionnaire`\n\n[Component documentation](https://www.expressivecss.com/questionnaire.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/questionnaire.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A five-question product survey with an optional comment at the end is a questionnaire. A shipping, payment and review checkout is a stepper.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-08); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-08, `llm.md#questionnaire`): A native form of fieldsets shown one at a time, with single choice, multiple choice and text answers, choice descriptions, required and data-required questions, Skip for optional ones, authored or generated errors, a progress row, Enter to advance, disabled questions for branching and normal form submission.\n\nWeb adaptation: Each question is a native fieldset named by its legend; the browser's constraint validation checks answers, hidden questions use hidden, and without JavaScript the form shows every question and still submits.\n\nKnown boundary: Showing one question, required radios, data-required checkboxes that ignore disabled ones, generated and authored errors, aria-invalid and aria-describedby, Enter, Skip clearing an answer, Back focus, a disabled question left out, submit and requestSubmit() opening the first unanswered question, FormData and destroy are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. There are no answer keyboard shortcuts, no summary step and no saved progress; the page owns persistence and branching.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#questionnaire).\n\n#### Contract\n\nA `<form class=\"questionnaire\">` holds one `<fieldset>` per question and a `<footer>` of buttons. The `<legend>` is the prompt and a `<p>` under it describes the question. Answers are ordinary radio buttons, checkboxes or text fields; a `<small>` inside a choice's label describes that choice, and choices draw as outlined rows that fill with `secondary-container` when selected. A `<p class=\"questionnaire-error\">` in a question holds its error text. The footer buttons are marked by `value`: `previous`, `skip` and `next`, each `type=\"button\"`, plus a submit button. `AutoInit()` starts every `form.questionnaire`.\n\nThe component adds a progress row (\"Question 1 of 3\" and a `<progress>` named by that text), hides every question but the current one with `hidden`, and shows only the buttons that apply: Back after the first question, Skip on an optional question, Next before the last one and Submit on the last. A question is required when one of its controls is `required`; `data-required` on a fieldset of checkboxes requires at least one. Next checks the shown question with the browser's validation. When it fails, the fieldset gets `invalid`, its controls get `aria-invalid=\"true\"`, the error shows and joins the description in the fieldset's `aria-describedby`, and focus moves to the control to fix. Answering clears it. A question without its own error shows the browser's validation message, or `requiredMessage`. Skip clears the shown answer, firing `change`, and moves on. Enter in a field moves to the next question, and on the last one submits. Submitting, with the button, Enter or `requestSubmit()`, checks every question and opens the first that still needs an answer. Only enabled checkboxes count toward `data-required`, since a disabled one is not sent. After Next, Back or Skip, focus moves to the selected answer or the first control.\n\nA disabled fieldset is skipped and left out of the count, so a page branches by enabling or disabling questions from earlier answers. The form submits normally: read the answers with `FormData`, using `getAll()` for checkboxes. Without JavaScript every question shows at once, the navigation buttons stay hidden and the submit button sends the form; a `data-required` group is not checked then. Name the form with `aria-label` or `aria-labelledby`.\n\n#### Syntax\n\n```html\n<form class=\"questionnaire\" aria-label=\"Product survey\">\n <fieldset>\n <legend>What should we prototype next?</legend>\n <p>Pick the direction you want to explore first.</p>\n <label><input type=\"radio\" name=\"direction\" value=\"search\" required>Search<small>Find any page in two keystrokes</small></label>\n <label><input type=\"radio\" name=\"direction\" value=\"sharing\" required>Sharing</label>\n <p class=\"questionnaire-error\">Choose an answer to continue.</p>\n </fieldset>\n <fieldset data-required>\n <legend>Where do you use the app?</legend>\n <label><input type=\"checkbox\" name=\"platforms\" value=\"web\">Web</label>\n <label><input type=\"checkbox\" name=\"platforms\" value=\"mobile\">Mobile</label>\n <p class=\"questionnaire-error\">Choose at least one platform.</p>\n </fieldset>\n <fieldset>\n <legend>Anything else we should know?</legend>\n <div class=\"field\">\n <textarea id=\"notes\" name=\"notes\" class=\"expressive-textarea\" placeholder=\" \"></textarea>\n <label for=\"notes\">Notes</label>\n </div>\n </fieldset>\n <footer>\n <button type=\"button\" class=\"text\" value=\"previous\">Back</button>\n <button type=\"button\" class=\"text\" value=\"skip\">Skip</button>\n <button type=\"button\" value=\"next\">Next</button>\n <button type=\"submit\">Submit</button>\n </footer>\n</form>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `progressLabel` | String | `'Question {current} of {total}'` | Text of the progress row. `{current}` and `{total}` become the question number and count. `data-progress-label` on the form sets it. |\n| `requiredMessage` | String | `'Choose an answer to continue.'` | Error for a question with no `.questionnaire-error` when the browser has no message, as for a `data-required` group. |\n\n#### Methods\n\n- `.next()`: Checks the shown question and moves to the next one if it is answered.\n- `.previous()`: Moves to the previous question.\n- `.skip()`: Clears the answer to the shown question and moves to the next one.\n- `.goTo(index)`: Shows the question at `index` without checking the current one, for example to resume a saved questionnaire.\n- `.destroy()`: Shows every question and removes the progress row, ids, states and listeners the component added.\n\n#### Also documented\n\nThe Questionnaire section of the [API reference](https://www.expressivecss.com/llm.md) also covers Properties, Tokens.\n\n#### Rules\n\n- `questionnaire-is-a-form`: Write the questionnaire as <form class=\"questionnaire\">. The form sends the answers, runs the browser's validation and still works without JavaScript.\n- `questionnaire-is-named`: Name the questionnaire with aria-label or aria-labelledby so it is announced as a form.\n- `questionnaire-navigation-does-not-submit`: Give the Back, Skip and Next buttons type=\"button\". A button without a type submits the form, so pressing one before the component starts would send it early.\n"
276
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Questionnaire\nA form that asks one question at a time, with single and multiple choice, free text and skippable questions.\n\nComponent ID: `questionnaire`\n\n[Component documentation](https://www.expressivecss.com/questionnaire.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/questionnaire.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A five-question product survey with an optional comment at the end is a questionnaire. A shipping, payment and review checkout is a stepper.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-08); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-08, `llm.md#questionnaire`): A native form of fieldsets shown one at a time, with single choice, multiple choice and text answers, choice descriptions, required and data-required questions, Skip for optional ones, authored or generated errors, a progress row, Enter to advance, disabled questions for branching and normal form submission.\n\nWeb adaptation: Each question is a native fieldset named by its legend; the browser's constraint validation checks answers, hidden questions use hidden, and without JavaScript the form shows every question and still submits.\n\nKnown boundary: Showing one question, required radios, data-required checkboxes that ignore disabled ones, generated and authored errors, aria-invalid and aria-describedby, Enter, Skip clearing an answer, Back focus, a disabled question left out, submit and requestSubmit() opening the first unanswered question, answer shortcuts, the review step, saved progress, FormData and destroy are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. There are no answer keyboard shortcuts, no summary step and no saved progress; the page owns persistence and branching.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#questionnaire).\n\n#### Contract\n\nA `<form class=\"questionnaire\">` holds one `<fieldset>` per question and a `<footer>` of buttons. The `<legend>` is the prompt and a `<p>` under it describes the question. Answers are ordinary radio buttons, checkboxes or text fields; a `<small>` inside a choice's label describes that choice, and choices draw as outlined rows that fill with `secondary-container` when selected. A `<p class=\"questionnaire-error\">` in a question holds its error text. The footer buttons are marked by `value`: `previous`, `skip` and `next`, each `type=\"button\"`, plus a submit button. `AutoInit()` starts every `form.questionnaire`.\n\nThe component adds a progress row (\"Question 1 of 3\" and a `<progress>` named by that text), hides every question but the current one with `hidden`, and shows only the buttons that apply: Back after the first question, Skip on an optional question, Next before the last one and Submit on the last. A question is required when one of its controls is `required`; `data-required` on a fieldset of checkboxes requires at least one. Next checks the shown question with the browser's validation. When it fails, the fieldset gets `invalid`, its controls get `aria-invalid=\"true\"`, the error shows and joins the description in the fieldset's `aria-describedby`, and focus moves to the control to fix. Answering clears it. A question without its own error shows the browser's validation message, or `requiredMessage`. Skip clears the shown answer, firing `change`, and moves on. Enter in a field moves to the next question, and on the last one submits. Submitting, with the button, Enter or `requestSubmit()`, checks every question and opens the first that still needs an answer. Only enabled checkboxes count toward `data-required`, since a disabled one is not sent. After Next, Back or Skip, focus moves to the selected answer or the first control.\n\nA disabled fieldset is skipped and left out of the count, so a page branches by enabling or disabling questions from earlier answers. The form submits normally: read the answers with `FormData`, using `getAll()` for checkboxes. Without JavaScript every question shows at once, the navigation buttons stay hidden and the submit button sends the form; a `data-required` group is not checked then. Name the form with `aria-label` or `aria-labelledby`.\n\n#### Syntax\n\n```html\n<form class=\"questionnaire\" aria-label=\"Product survey\">\n <fieldset>\n <legend>What should we prototype next?</legend>\n <p>Pick the direction you want to explore first.</p>\n <label><input type=\"radio\" name=\"direction\" value=\"search\" required>Search<small>Find any page in two keystrokes</small></label>\n <label><input type=\"radio\" name=\"direction\" value=\"sharing\" required>Sharing</label>\n <p class=\"questionnaire-error\">Choose an answer to continue.</p>\n </fieldset>\n <fieldset data-required>\n <legend>Where do you use the app?</legend>\n <label><input type=\"checkbox\" name=\"platforms\" value=\"web\">Web</label>\n <label><input type=\"checkbox\" name=\"platforms\" value=\"mobile\">Mobile</label>\n <p class=\"questionnaire-error\">Choose at least one platform.</p>\n </fieldset>\n <fieldset>\n <legend>Anything else we should know?</legend>\n <div class=\"field\">\n <textarea id=\"notes\" name=\"notes\" class=\"expressive-textarea\" placeholder=\" \"></textarea>\n <label for=\"notes\">Notes</label>\n </div>\n </fieldset>\n <footer>\n <button type=\"button\" class=\"text\" value=\"previous\">Back</button>\n <button type=\"button\" class=\"text\" value=\"skip\">Skip</button>\n <button type=\"button\" value=\"next\">Next</button>\n <button type=\"submit\">Submit</button>\n </footer>\n</form>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `progressLabel` | String | `'Question {current} of {total}'` | Text of the progress row. `{current}` and `{total}` become the question number and count. `data-progress-label` on the form sets it. |\n| `summaryLabel` | String | `'Review your answers'` | With `data-summary`, the review step's name and its progress text. |\n| `editLabel` | String | `'Edit'` | With `data-summary`, the text of each answer's button back to its question. |\n| `skippedLabel` | String | `'Not answered'` | With `data-summary`, the answer shown for a question left blank. |\n| `requiredMessage` | String | `'Choose an answer to continue.'` | Error for a question with no `.questionnaire-error` when the browser has no message, as for a `data-required` group. |\n\n#### Methods\n\n- `.next()`: Checks the shown question and moves to the next one if it is answered.\n- `.previous()`: Moves to the previous question.\n- `.skip()`: Clears the answer to the shown question and moves to the next one.\n- `.review()`: With `data-summary`, shows the review step.\n- `.goTo(index)`: Shows the question at `index` without checking the current one, for example to resume a saved questionnaire.\n- `.destroy()`: Shows every question and removes the progress row, ids, states and listeners the component added.\n\n#### Also documented\n\nThe Questionnaire section of the [API reference](https://www.expressivecss.com/llm.md) also covers Properties, Tokens.\n\n#### Rules\n\n- `questionnaire-is-a-form`: Write the questionnaire as <form class=\"questionnaire\">. The form sends the answers, runs the browser's validation and still works without JavaScript.\n- `questionnaire-is-named`: Name the questionnaire with aria-label or aria-labelledby so it is announced as a form.\n- `questionnaire-navigation-does-not-submit`: Give the Back, Skip and Next buttons type=\"button\". A button without a type submits the form, so pressing one before the component starts would send it early.\n"
209
277
  },
210
278
  {
211
279
  "file": "radar-chart.md",
212
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Radar chart\nSeveral measures on one scale drawn as shapes on spokes from a data table, one shape per series.\n\nComponent ID: `radar-chart`\n\n[Component documentation](https://www.expressivecss.com/radar-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/radar-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Two quarters of client satisfaction scored out of 100 on five measures is a radar chart with area. Monthly revenue for a year is a line chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-07); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-07, `llm.md#radar-chart`): Spokes drawn clockwise from the top from a figure's data table, one shape per series from zero in the middle to the scale's top at the rim, row labels past each spoke, a legend for several series, area, points, dashed and sparkline modifiers, data-min and data-max, and a pointer and keyboard tooltip with a cursor spoke.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the grid, shapes, labels and legend are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, the grid, shape geometry and colors, the scale with data-max and clamping, gaps, labels, the legend, area and points, the sparkline, RTL, the tooltip and cursor spoke, nearest-spoke pointer hit testing, keyboard movement and destroy are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. A gap joins the spokes either side of it; there are no ring value labels.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#radar-chart).\n\n#### Contract\n\nA `<figure class=\"radar-chart\">` takes the same `<figcaption>` and `<table>` as a line chart. `AutoInit()` draws each row as a spoke, clockwise from the top in table order, with its label past the tip, and each value column as a shape joining its values, from zero in the middle to the scale's top at the rim. A legend names the series when there is more than one, and a tooltip follows the pointer round the chart. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"radar-chart area\" data-max=\"100\">\n <figcaption>Client satisfaction</figcaption>\n <table>\n <thead>\n <tr><th>Measure</th><th>This quarter</th><th class=\"dashed\">Last quarter</th></tr>\n </thead>\n <tbody>\n <tr><th>Communication</th><td>92</td><td>80</td></tr>\n <tr><th>Quality</th><td>85</td><td>76</td></tr>\n <tr><th>Timeliness</th><td>78</td><td>82</td></tr>\n <tr><th>Value</th><td>88</td><td>74</td></tr>\n <tr><th>Support</th><td>81</td><td>70</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Radar chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `radar-chart-is-a-figure-with-a-table`: Write .radar-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `radar-chart-has-a-caption`: Name a .radar-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
280
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Radar chart\nSeveral measures on one scale drawn as shapes on spokes from a data table, one shape per series.\n\nComponent ID: `radar-chart`\n\n[Component documentation](https://www.expressivecss.com/radar-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/radar-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Two quarters of client satisfaction scored out of 100 on five measures is a radar chart with area. Monthly revenue for a year is a line chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-07); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-07, `llm.md#radar-chart`): Spokes drawn clockwise from the top from a figure's data table, one shape per series from zero in the middle to the scale's top at the rim, row labels past each spoke, a legend for several series, area, points, dashed and sparkline modifiers, data-min and data-max, round labelled rings with .axis, and a pointer and keyboard tooltip with a cursor spoke.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the grid, shapes, labels and legend are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, the grid, shape geometry and colors, the scale with data-max and clamping, gaps, labels, the legend, area and points, the sparkline, RTL, the tooltip and cursor spoke, nearest-spoke pointer hit testing, keyboard movement and destroy are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. A gap breaks the outline; there are no ring value labels.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#radar-chart).\n\n#### Contract\n\nA `<figure class=\"radar-chart\">` takes the same `<figcaption>` and `<table>` as a line chart. `AutoInit()` draws each row as a spoke, clockwise from the top in table order, with its label past the tip, and each value column as a shape joining its values, from zero in the middle to the scale's top at the rim. A legend names the series when there is more than one, and a tooltip follows the pointer round the chart. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"radar-chart area\" data-max=\"100\">\n <figcaption>Client satisfaction</figcaption>\n <table>\n <thead>\n <tr><th>Measure</th><th>This quarter</th><th class=\"dashed\">Last quarter</th></tr>\n </thead>\n <tbody>\n <tr><th>Communication</th><td>92</td><td>80</td></tr>\n <tr><th>Quality</th><td>85</td><td>76</td></tr>\n <tr><th>Timeliness</th><td>78</td><td>82</td></tr>\n <tr><th>Value</th><td>88</td><td>74</td></tr>\n <tr><th>Support</th><td>81</td><td>70</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Radar chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `radar-chart-is-a-figure-with-a-table`: Write .radar-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `radar-chart-has-a-caption`: Name a .radar-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
213
281
  },
214
282
  {
215
283
  "file": "radial-chart.md",
216
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Radial chart\nProgress toward a goal drawn as rings from a data table, as concentric rings or a half-circle gauge.\n\nComponent ID: `radial-chart`\n\n[Component documentation](https://www.expressivecss.com/radial-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/radial-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Four plan targets as a share of their goals, or seats filled out of 25 as a gauge, are a radial chart. Revenue split across plans is a pie chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-07); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-07, `llm.md#radial-chart`): Rings drawn clockwise from the top from a figure's data table as a share of data-max, with tracks, a legend of each row's cell for more than one row, gauge and sparkline modifiers, a <tfoot> total in the middle, further columns in the tooltip, and a pointer and keyboard tooltip that highlights the ring.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the rings, total and legend are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, ring geometry and colors, tracks, the legend, the <tfoot> total, the gauge's half circles, values past the maximum and missing values, thinning crowded rings, RTL, the tooltip, pointer distance hit testing, keyboard movement and destroy are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. Zero and negative values leave only the track; there are no labels on the rings.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#radial-chart).\n\n#### Contract\n\nA `<figure class=\"radial-chart\">` takes the same `<figcaption>` and `<table>` as a column chart. `AutoInit()` draws each row of the first value column as a ring, outermost first, filled clockwise from the top as a share of `data-max` (100 by default), with a legend of the rows and their cells when there is more than one, and a tooltip that follows the pointer across the rings. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"radial-chart gauge\" data-max=\"25\">\n <figcaption>Team capacity</figcaption>\n <table>\n <thead>\n <tr><th>Seats</th><th>Count</th></tr>\n </thead>\n <tbody>\n <tr><th>Filled</th><td>18</td></tr>\n <tr><th>Offered</th><td>21</td></tr>\n </tbody>\n <tfoot>\n <tr><th>Total seats</th><td>25</td></tr>\n </tfoot>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Radial chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `radial-chart-is-a-figure-with-a-table`: Write .radial-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `radial-chart-has-a-caption`: Name a .radial-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
284
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Radial chart\nProgress toward a goal drawn as rings from a data table, as concentric rings or a half-circle gauge.\n\nComponent ID: `radial-chart`\n\n[Component documentation](https://www.expressivecss.com/radial-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/radial-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Four plan targets as a share of their goals, or seats filled out of 25 as a gauge, are a radial chart. Revenue split across plans is a pie chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-07); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-07, `llm.md#radial-chart`): Rings drawn clockwise from the top from a figure's data table as a share of data-max, with tracks, a legend of each row's cell for more than one row, gauge, labels and sparkline modifiers, a <tfoot> total in the middle, further columns in the tooltip, and a pointer and keyboard tooltip that highlights the ring.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the rings, total and legend are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Drawing, the hidden table, ring geometry and colors, tracks, the legend, the <tfoot> total, the gauge's half circles, ring names, gauge names under the base line, segments, gradient fills, values past the maximum and missing values, thinning crowded rings, RTL, the tooltip, pointer distance hit testing, keyboard movement and destroy are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. Zero and negative values leave only the track. The .labels names can run into a crowded middle total.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#radial-chart).\n\n#### Contract\n\nA `<figure class=\"radial-chart\">` takes the same `<figcaption>` and `<table>` as a column chart. `AutoInit()` draws each row of the first value column as a ring, outermost first, filled clockwise from the top as a share of `data-max` (100 by default), with a legend of the rows and their cells when there is more than one, and a tooltip that follows the pointer across the rings. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"radial-chart gauge\" data-max=\"25\">\n <figcaption>Team capacity</figcaption>\n <table>\n <thead>\n <tr><th>Seats</th><th>Count</th></tr>\n </thead>\n <tbody>\n <tr><th>Filled</th><td>18</td></tr>\n <tr><th>Offered</th><td>21</td></tr>\n </tbody>\n <tfoot>\n <tr><th>Total seats</th><td>25</td></tr>\n </tfoot>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Radial chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `radial-chart-is-a-figure-with-a-table`: Write .radial-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `radial-chart-has-a-caption`: Name a .radial-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
217
285
  },
218
286
  {
219
287
  "file": "radio-buttons.md",
220
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Radio buttons\nMaterial Design 3 radios, from the HTML.\n\nComponent ID: `radio-buttons`\n\n[Component documentation](https://www.expressivecss.com/radio-buttons.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/radio-buttons.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/radio-button/overview)\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/radio-button/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/radio-button/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/radio-button/guidelines).\n\nSupport (2026-09-13, `llm.md#radio-buttons`): Native radio groups and disabled controls.\n\nWeb adaptation: Shared name provides exclusivity; fieldset/legend names the group.\n\nKnown boundary: Native group selection is checked; initially-empty and disabled-option paths remain unmeasured.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#radio-buttons).\n\n#### Contract\n\nA `<label>` wrapping `<input type=\"radio\">` is the control. The label text is a sibling of the input — no extra class, no required `<span>`. They are CSS only. There is no JavaScript component and nothing to AutoInit.\n\nA radio only means anything as one of a set, so the set is a `<fieldset>` and the `<legend>` names the question the radios answer. Without it the options are announced one by one with nothing saying what is being chosen.\n\nTokens follow the [M3 radio spec](https://m3.material.io/components/radio-button/specs). The icon is a 20dp ring with a 2dp stroke. Selected is `primary` with a 10dp inner disc (the M3 gap). The state layer is 40dp at 8% hover and 10% focus. The touch target is 48dp. The label is `body-large` / `on-surface`. Disabled is `on-surface` at 38%.\n\n#### Syntax\n\n```html\n<fieldset>\n <legend>Colour</legend>\n <label>\n <input name=\"group1\" type=\"radio\" checked>\n Red\n </label>\n <label>\n <input name=\"group1\" type=\"radio\">\n Yellow\n </label>\n <label>\n <input name=\"group1\" type=\"radio\" disabled>\n Brown\n </label>\n</fieldset>\n```\n\n#### Also documented\n\nThe Radio Buttons section of the [API reference](https://www.expressivecss.com/llm.md) also covers In a row.\n\n#### Rules\n\n- `radio-in-fieldset`: A radio only means something as one of a group, and the group needs a <fieldset> with a <legend> to name the question being answered.\n- `radio-labelled`: A radio must be inside its <label> or carry an id a label points at.\n- `fieldset-has-legend`: A fieldset without a legend groups controls visually but names nothing.\n"
288
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Radio buttons\nMaterial Design 3 radios, from the HTML.\n\nComponent ID: `radio-buttons`\n\n[Component documentation](https://www.expressivecss.com/radio-buttons.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/radio-buttons.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/radio-button/overview)\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/radio-button/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/radio-button/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/radio-button/guidelines).\n\nSupport (2026-09-13, `llm.md#radio-buttons`): Native radio groups and disabled controls.\n\nWeb adaptation: Shared name provides exclusivity; fieldset/legend names the group.\n\nKnown boundary: Native group selection is checked; initially-empty and disabled-option paths remain unmeasured.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#radio-buttons).\n\n#### Contract\n\nA `<label>` wrapping `<input type=\"radio\">` is the control. The label text is a sibling of the input — no extra class, no required `<span>`. They are CSS only. There is no JavaScript component and nothing to AutoInit.\n\nA radio only means anything as one of a set, so the set is a `<fieldset>` and the `<legend>` names the question the radios answer. Without it the options are announced one by one with nothing saying what is being chosen.\n\nTokens follow the [M3 radio spec](https://m3.material.io/components/radio-button/specs). The icon is a 20dp ring with a 2dp stroke. Selected is `primary` with a 10dp inner disc (the M3 gap). The state layer is 40dp at 8% hover and 10% focus. The touch target is 48dp. The label is `body-large` / `on-surface`. Disabled is `on-surface` at 38%.\n\n#### Syntax\n\n```html\n<fieldset>\n <legend>Colour</legend>\n <label>\n <input name=\"group1\" type=\"radio\" checked>\n Red\n </label>\n <label>\n <input name=\"group1\" type=\"radio\">\n Yellow\n </label>\n <label>\n <input name=\"group1\" type=\"radio\" disabled>\n Brown\n </label>\n</fieldset>\n```\n\n#### Also documented\n\nThe Radio Buttons section of the [API reference](https://www.expressivecss.com/llm.md) also covers In a row.\n\n#### Rules\n\n- `radio-in-fieldset`: A radio only means something as one of a group, and the group needs a <fieldset> with a <legend> to name the question being answered.\n- `radio-labelled`: A radio must be inside its <label> or carry an id a label points at.\n- `fieldset-has-legend`: A fieldset without a legend groups controls visually but names nothing.\n"
221
289
  },
222
290
  {
223
291
  "file": "rating.md",
224
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Rating\nStars for choosing or showing a score, on native radio buttons.\n\nComponent ID: `rating`\n\n[Component documentation](https://www.expressivecss.com/rating.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/rating.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Choosing a volume from 0 to 100 is a slider. Stars suit a short fixed scale where each step has a meaning.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#rating`): A fieldset of radios drawn as stars with fill, hover preview and focus ring, and a read-only display that fills to a fractional value in LTR and RTL.\n\nWeb adaptation: Native radios keep keyboard, form and naming behavior; the display is a named role=\"img\".\n\nKnown boundary: Star fill, hover preview, native arrow keys, form value, the display fill direction and names are checked in a browser; the focus ring after arrow keys is not drawn in Playwright WebKit, and spoken output remains unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#rating).\n\n#### Contract\n\nFor input, a `<fieldset class=\"rating\">` holds a `<legend>` and one radio per star, each in a `<label>` whose visually hidden text (\"3 stars\") comes before the radio. The radio is drawn as the star, so Tab, the arrow keys, `required` and form submission stay native. A star fills when it or a later star is checked, and hovering previews a choice. Put the text before the radio: a span after it is the older `input + span` radio, which hides the input. CSS only; there is no JavaScript.\n\n#### Syntax\n\n```html\n<fieldset class=\"rating\">\n <legend>Rate this recipe</legend>\n <label><span class=\"visually-hidden\">1 star</span><input type=\"radio\" name=\"score\" value=\"1\"></label>\n <label><span class=\"visually-hidden\">2 stars</span><input type=\"radio\" name=\"score\" value=\"2\"></label>\n <label><span class=\"visually-hidden\">3 stars</span><input type=\"radio\" name=\"score\" value=\"3\" checked></label>\n <label><span class=\"visually-hidden\">4 stars</span><input type=\"radio\" name=\"score\" value=\"4\"></label>\n <label><span class=\"visually-hidden\">5 stars</span><input type=\"radio\" name=\"score\" value=\"5\"></label>\n</fieldset>\n```\n\n#### Also documented\n\nThe Rating section of the [API reference](https://www.expressivecss.com/llm.md) also covers Display, Tokens.\n\n#### Rules\n\n- `rating-is-a-fieldset-or-an-image`: A .rating is either a <fieldset> of radios, for input, or an element with role=\"img\" and a name, for display. Anything else draws stars that say nothing.\n- `rating-display-is-named`: Name a rating display with the value, such as aria-label=\"Rated 4.5 out of 5\".\n- `rating-label-text-precedes-the-radio`: Put a star's visually hidden text before its radio. A span after the radio is the older input + span pattern, which hides the radio and with it the star.\n"
292
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Rating\nStars for choosing or showing a score, on native radio buttons.\n\nComponent ID: `rating`\n\n[Component documentation](https://www.expressivecss.com/rating.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/rating.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Choosing a volume from 0 to 100 is a slider. Stars suit a short fixed scale where each step has a meaning.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#rating`): A fieldset of radios drawn as stars with fill, hover preview and focus ring, and a read-only display that fills to a fractional value in LTR and RTL.\n\nWeb adaptation: Native radios keep keyboard, form and naming behavior; the display is a named role=\"img\".\n\nKnown boundary: Star fill, hover preview, native arrow keys, form value, the display fill direction and names are checked in a browser; the focus ring after arrow keys is not drawn in Playwright WebKit, and spoken output remains unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#rating).\n\n#### Contract\n\nFor input, a `<fieldset class=\"rating\">` holds a `<legend>` and one radio per star, each in a `<label>` whose visually hidden text (\"3 stars\") comes before the radio. The radio is drawn as the star, so Tab, the arrow keys, `required` and form submission stay native. A star fills when it or a later star is checked, and hovering previews a choice. Put the text before the radio: a span after it is the older `input + span` radio, which hides the input. CSS only; there is no JavaScript.\n\n#### Syntax\n\n```html\n<fieldset class=\"rating\">\n <legend>Rate this recipe</legend>\n <label><span class=\"visually-hidden\">1 star</span><input type=\"radio\" name=\"score\" value=\"1\"></label>\n <label><span class=\"visually-hidden\">2 stars</span><input type=\"radio\" name=\"score\" value=\"2\"></label>\n <label><span class=\"visually-hidden\">3 stars</span><input type=\"radio\" name=\"score\" value=\"3\" checked></label>\n <label><span class=\"visually-hidden\">4 stars</span><input type=\"radio\" name=\"score\" value=\"4\"></label>\n <label><span class=\"visually-hidden\">5 stars</span><input type=\"radio\" name=\"score\" value=\"5\"></label>\n</fieldset>\n```\n\n#### Also documented\n\nThe Rating section of the [API reference](https://www.expressivecss.com/llm.md) also covers Display, Tokens.\n\n#### Rules\n\n- `rating-is-a-fieldset-or-an-image`: A .rating is either a <fieldset> of radios, for input, or an element with role=\"img\" and a name, for display. Anything else draws stars that say nothing.\n- `rating-display-is-named`: Name a rating display with the value, such as aria-label=\"Rated 4.5 out of 5\".\n- `rating-label-text-precedes-the-radio`: Put a star's visually hidden text before its radio. A span after the radio is the pre-1.0 input + span pattern, which 0.16.0 no longer draws.\n"
225
293
  },
226
294
  {
227
295
  "file": "rich-text-editor.md",
228
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Rich text editor\nA text field with a formatting toolbar for writing headings, lists and styled text, built on Tiptap.\n\nComponent ID: `rich-text-editor`\n\n[Component documentation](https://www.expressivecss.com/rich-text-editor.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/rich-text-editor.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `manual`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A blog post body with headings and lists needs a rich text editor; a one-line title needs a text field.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-08); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-08, `llm.md#rich-text-editor`): A Tiptap editor mounted from a textarea, toolbar buttons that run Tiptap commands with JSON arguments, pressed and disabled button state, form submission through the textarea, label naming and read-only textareas.\n\nWeb adaptation: The page supplies Tiptap's Editor class and extensions; the component mounts it, names the contenteditable textbox from the textarea and keeps the textarea in sync. The toolbar is the docked toolbar.\n\nKnown boundary: Formatting a selection, pressed and disabled toolbar state, textarea sync with input events, label naming and focus, a disabled textarea and destroy are checked in a browser; spoken output, IME composition, paste from other applications and touch selection remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#rich-text-editor).\n\n#### Contract\n\nA `.rich-text-editor` holds a docked `.toolbar` and a `<textarea>` whose value is the starting HTML. The component hides the textarea, mounts the editor in a `.rich-text-editor-content` after it, and writes the editor's HTML back on every change, so the textarea submits with its form and fires `input`. An empty editor submits an empty value. Without JavaScript the textarea stays visible and the toolbar is hidden.\n\nExpressiveCSS does not bundle Tiptap. Install `@tiptap/core`, `@tiptap/pm` and `@tiptap/starter-kit`, and pass the `Editor` class and extensions to `RichTextEditor.init()`. `AutoInit()` does not start the editor.\n\nA button's `data-command` names a Tiptap command and `data-attributes` holds its argument as JSON. A click runs the command and returns focus to the editor. A button is disabled while its command cannot run. Buttons for `toggle` commands set `aria-pressed` and `.active` while the selection has that format: `toggleBold` while it is bold, `toggleHeading` with `{\"level\":2}` while it is in a second-level heading.\n\n#### Syntax\n\n```html\n<label for=\"body\">Release notes</label>\n<div class=\"rich-text-editor\">\n <div class=\"toolbar docked\">\n <button type=\"button\" class=\"circle\" data-command=\"toggleBold\" aria-label=\"Bold\">\n <span class=\"material-symbols\" aria-hidden=\"true\">format_bold</span>\n </button>\n <button type=\"button\" class=\"circle\" data-command=\"toggleHeading\"\n data-attributes='{\"level\":2}' aria-label=\"Heading\">\n <span class=\"material-symbols\" aria-hidden=\"true\">format_h2</span>\n </button>\n <span class=\"max\"></span>\n <button type=\"button\" class=\"circle\" data-command=\"undo\" aria-label=\"Undo\">\n <span class=\"material-symbols\" aria-hidden=\"true\">undo</span>\n </button>\n </div>\n <textarea id=\"body\" name=\"body\"><p>Starting content</p></textarea>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `Editor` | Class | `null` | Tiptap's `Editor` class. Required. |\n| `extensions` | Array | `[]` | Tiptap extensions, usually `[StarterKit]`. |\n\n#### Methods\n\n- `.editor`: The Tiptap editor, for anything the toolbar does not cover.\n- `.destroy()`: Destroys the editor, removes its element and the button state, and shows the textarea again.\n\n#### Also documented\n\nThe Rich text editor section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `rich-text-editor-textarea-is-named`: Name the textarea with a <label for>, aria-label or aria-labelledby. The editor copies that name to its editable area, which has none of its own. Its content is the starting HTML, not a name.\n"
296
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Rich text editor\nA text field with a formatting toolbar for writing headings, lists and styled text, built on Tiptap.\n\nComponent ID: `rich-text-editor`\n\n[Component documentation](https://www.expressivecss.com/rich-text-editor.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/rich-text-editor.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `manual`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A blog post body with headings and lists needs a rich text editor; a one-line title needs a text field.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-08); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-08, `llm.md#rich-text-editor`): A Tiptap editor mounted from a textarea, toolbar buttons that run Tiptap commands with JSON arguments, pressed and disabled button state, form submission through the textarea, label naming and read-only textareas.\n\nWeb adaptation: The page supplies Tiptap's Editor class and extensions; the component mounts it, names the contenteditable textbox from the textarea and keeps the textarea in sync. The toolbar is the docked toolbar.\n\nKnown boundary: Formatting a selection, pressed and disabled toolbar state, textarea sync with input events, label naming and focus, a disabled textarea and destroy are checked in a browser; spoken output, IME composition, paste from other applications and touch selection remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#rich-text-editor).\n\n#### Contract\n\nA `.rich-text-editor` holds a docked `.toolbar` and a `<textarea>` whose value is the starting HTML. The component hides the textarea, mounts the editor in a `.rich-text-editor-content` after it, and writes the editor's HTML back on every change, so the textarea submits with its form and fires `input`. An empty editor submits an empty value. Without JavaScript the textarea stays visible and the toolbar is hidden.\n\nExpressiveCSS does not bundle Tiptap. Install `@tiptap/core`, `@tiptap/pm` and `@tiptap/starter-kit`, and pass the `Editor` class and extensions to `RichTextEditor.init()`. `AutoInit()` does not start the editor.\n\nA button's `data-command` names a Tiptap command and `data-attributes` holds its argument as JSON. A click runs the command and returns focus to the editor. A button is disabled while its command cannot run. Buttons for `toggle` commands set `aria-pressed` and `.active` while the selection has that format: `toggleBold` while it is bold, `toggleHeading` with `{\"level\":2}` while it is in a second-level heading.\n\n#### Syntax\n\n```html\n<label for=\"body\">Release notes</label>\n<div class=\"rich-text-editor\">\n <div class=\"toolbar docked\">\n <button type=\"button\" class=\"circle\" data-command=\"toggleBold\" aria-label=\"Bold\">\n <span class=\"material-symbols\" aria-hidden=\"true\">format_bold</span>\n </button>\n <button type=\"button\" class=\"circle\" data-command=\"toggleHeading\"\n data-attributes='{\"level\":2}' aria-label=\"Heading\">\n <span class=\"material-symbols\" aria-hidden=\"true\">format_h2</span>\n </button>\n <span class=\"max\"></span>\n <button type=\"button\" class=\"circle\" data-command=\"undo\" aria-label=\"Undo\">\n <span class=\"material-symbols\" aria-hidden=\"true\">undo</span>\n </button>\n </div>\n <textarea id=\"body\" name=\"body\"><p>Starting content</p></textarea>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `Editor` | Class | `null` | Tiptap's `Editor` class. Required. |\n| `extensions` | Array | `[]` | Tiptap extensions, usually `[StarterKit]`. |\n\n#### Methods\n\n- `.editor`: The Tiptap editor, for anything the toolbar does not cover.\n- `.destroy()`: Destroys the editor, removes its element and the button state, and shows the textarea again.\n\n#### Also documented\n\nThe Rich text editor section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `rich-text-editor-textarea-is-named`: Name the textarea with a <label for>, aria-label or aria-labelledby. The editor copies that name to its editable area, which has none of its own. Its content is the starting HTML, not a name.\n"
297
+ },
298
+ {
299
+ "file": "sankey-chart.md",
300
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Sankey chart\nFlows between nodes, each link as thick as its value.\n\nComponent ID: `sankey-chart`\n\n[Component documentation](https://www.expressivecss.com/sankey-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/sankey-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Energy from solar, wind and gas through the grid to homes and industry is a sankey chart. Visitors leaving at each step of one checkout is a bar chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-25); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-25, `llm.md#sankey-chart`): Nodes and links drawn from a figure's data table of source, target and value columns, with nodes in columns by their longest path from a source, stacked in the order they first appear, links and nodes on one scale, rows skipped for values that are not positive, missing names, self links and cycles, node names and colors, right-to-left mirroring, update() motion, data-watch, and a pointer and keyboard tooltip that steps through links in table order.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the SVG and node names are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Link and node sizes, columns, skipped rows, names, colors, right-to-left mirroring, keyboard steps, the link under the pointer, update() motion and data-watch are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. Nodes are not reached by keyboard or pointer, and node order follows the table with no crossing reduction.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#sankey-chart).\n\n#### Contract\n\nA `<figure class=\"sankey-chart\">` takes a `<figcaption>` and a `<table>` with one row per flow. The first column names the flow's source node, the second its target node, and the third holds its value. `AutoInit()` draws the nodes in columns and each row as a link from its source to its target, and a tooltip shows the link under the pointer. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"sankey-chart\">\n <figcaption>Energy flow (TWh)</figcaption>\n <table>\n <thead>\n <tr><th>Source</th><th>Target</th><th>TWh</th></tr>\n </thead>\n <tbody>\n <tr><th>Solar</th><td>Grid</td><td>30</td></tr>\n <tr><th>Wind</th><td>Grid</td><td>50</td></tr>\n <tr><th>Grid</th><td>Homes</td><td>60</td></tr>\n <tr><th>Grid</th><td>Industry</td><td>20</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Sankey chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `sankey-chart-is-a-figure-with-a-table`: Write .sankey-chart as a <figure> holding a <table> of the flows. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `sankey-chart-has-a-caption`: Name a .sankey-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
301
+ },
302
+ {
303
+ "file": "scatter-chart.md",
304
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Scatter chart\nOne measure plotted against another, with a point for each row.\n\nComponent ID: `scatter-chart`\n\n[Component documentation](https://www.expressivecss.com/scatter-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/scatter-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Weight against city and highway fuel economy for twenty cars is a scatter chart. Fuel economy by model year is a line chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-24); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-24, `llm.md#scatter-chart`): Points drawn from a figure's data table, with a shared x column and one series per further column, gaps for cells that are not numbers, fixed ranges for both scales, round labels for both scales with .axis, a legend with .toggle, update() motion, data-watch, a pointer and keyboard tooltip that steps through points in table order, and .bubble sizes from the last column with areas proportional to its values.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the SVG is aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Point placement in left-to-right and right-to-left pages, gaps, both axes, the tooltip, keyboard steps, the nearest point by pointer, update() motion, data-watch, the toggle legend and .bubble areas, gaps, tooltip, refit and size motion are checked in a browser; screen reader output, forced colors and enlarged text remain unverified. There are no trend lines, separate x columns per series or sizes per series.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#scatter-chart).\n\n#### Contract\n\nA `<figure class=\"scatter-chart\">` takes a `<figcaption>` and a `<table>`. The first column labels each row, the second holds its x value, and each further column is a series of y values named by its header cell. `AutoInit()` draws one point per series for each row, at the row's x value and the series' y value. A legend names the series when there are two or more, and a tooltip shows the point under the pointer. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"scatter-chart axis\" data-x-min=\"0\">\n <figcaption>Fuel economy by weight</figcaption>\n <table>\n <thead>\n <tr><th>Car</th><th>Weight (kg)</th><th>City mpg</th><th>Highway mpg</th></tr>\n </thead>\n <tbody>\n <tr><th>Civic</th><td>1300</td><td>31</td><td>40</td></tr>\n <tr><th>Camry</th><td>1600</td><td>28</td><td>39</td></tr>\n <tr><th>Tahoe</th><td>2600</td><td>15</td><td>20</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Scatter chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `scatter-chart-is-a-figure-with-a-table`: Write .scatter-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `scatter-chart-has-a-caption`: Name a .scatter-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
229
305
  },
230
306
  {
231
307
  "file": "scrollspy.md",
232
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Scrollspy\nHighlight the table of contents as the page scrolls.\n\nComponent ID: `scrollspy`\n\n[Component documentation](https://www.expressivecss.com/scrollspy.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/scrollspy.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A settings screen with mutually exclusive panels needs tabs. Scrollspy only tracks sections already present in the scrolling document.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-09-13); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. Full visual parity and spoken output unverified.\n\nSupport (2026-09-17, `llm.md#scrollspy`): Section tracking with per-section offsets and independent tables of contents grouped by getActiveElement callback identity.\n\nWeb adaptation: Web document navigation helper.\n\nKnown boundary: Independent TOCs must reuse one getActiveElement function per group. Screen-reader announcements, native zoom and physical-device checks remain pending.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#scrollspy).\n\n#### Contract\n\nScrollspy watches a set of sections and which one is currently in view. The table of contents on the right of every documentation page is the live demo: the matching link gets `active`, and clicking a link scrolls to that section.\n\nPut `scrollspy` and an `id` on each section. The table of contents is a set of destinations within the page, so it lives in a labelled `<nav>`; use `table-of-contents` on its list and point each link at `#that-id`. `AutoInit()` starts every `.scrollspy` except those marked `no-autoinit`.\n\nEach link is at least 48dp tall, the Material touch target. For a denser list, set `--md-comp-toc-item-height` on the `.table-of-contents` list.\n\n#### Syntax\n\n```html\n<div class=\"row\">\n <div class=\"s12 m9\">\n <div id=\"introduction\" class=\"section scrollspy\">\n <p>Content</p>\n </div>\n <div id=\"structure\" class=\"section scrollspy\">\n <p>Content</p>\n </div>\n <div id=\"initialization\" class=\"section scrollspy\">\n <p>Content</p>\n </div>\n </div>\n <div class=\"hide-on-compact-only m3\">\n <nav aria-label=\"On this page\">\n <ul class=\"section table-of-contents\">\n <li><a href=\"#introduction\">Introduction</a></li>\n <li><a href=\"#structure\">Structure</a></li>\n <li><a href=\"#initialization\">Initialization</a></li>\n </ul>\n </nav>\n </div>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `scrollOffset` | Number | `200` | Per-section observer top inset and `--md-comp-scrollspy-offset` for native anchor scroll margins. |\n| `activeClass` | String | `'active'` | Class applied to the active table-of-contents link. |\n| `getActiveElement` | Function | see below | Returns a CSS selector for the element that should receive `activeClass`, given the section’s id. |\n| `keepTopElementActive` | Boolean | `false` | If true, keep the last section above the viewport active when the scrollbar is outside all spy sections. If there is no such section, the first one stays active. |\n\n#### Methods\n\n- `.destroy()`: Disconnect the section's observer, restore its original offset style, and update the current link within its group. Repeated destruction is safe.\n\n#### Also documented\n\nThe Scrollspy section of the [API reference](https://www.expressivecss.com/llm.md) also covers Initialization, Properties.\n\n#### Rules\n\n- `toc-in-nav`: A table of contents is a set of destinations within the page. It belongs in a labelled <nav>.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
308
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Scrollspy\nHighlight the table of contents as the page scrolls.\n\nComponent ID: `scrollspy`\n\n[Component documentation](https://www.expressivecss.com/scrollspy.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/scrollspy.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A settings screen with mutually exclusive panels needs tabs. Scrollspy only tracks sections already present in the scrolling document.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-09-13); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. Full visual parity and spoken output unverified.\n\nSupport (2026-09-17, `llm.md#scrollspy`): Section tracking with per-section offsets and independent tables of contents grouped by getActiveElement callback identity.\n\nWeb adaptation: Web document navigation helper.\n\nKnown boundary: Independent TOCs must reuse one getActiveElement function per group. Screen-reader announcements, native zoom and physical-device checks remain pending.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#scrollspy).\n\n#### Contract\n\nScrollspy watches a set of sections and which one is currently in view. The table of contents on the right of every documentation page is the live demo: the matching link gets `active`, and clicking a link scrolls to that section.\n\nPut `scrollspy` and an `id` on each section. The table of contents is a set of destinations within the page, so it lives in a labelled `<nav>`; use `table-of-contents` on its list and point each link at `#that-id`. `AutoInit()` starts every `.scrollspy` except those marked `no-autoinit`.\n\nEach link is at least 48dp tall, the Material touch target. For a denser list, set `--md-comp-toc-item-height` on the `.table-of-contents` list.\n\n#### Syntax\n\n```html\n<div class=\"row\">\n <div class=\"s12 m9\">\n <div id=\"introduction\" class=\"section scrollspy\">\n <p>Content</p>\n </div>\n <div id=\"structure\" class=\"section scrollspy\">\n <p>Content</p>\n </div>\n <div id=\"initialization\" class=\"section scrollspy\">\n <p>Content</p>\n </div>\n </div>\n <div class=\"hide-on-compact-only m3\">\n <nav aria-label=\"On this page\">\n <ul class=\"section table-of-contents\">\n <li><a href=\"#introduction\">Introduction</a></li>\n <li><a href=\"#structure\">Structure</a></li>\n <li><a href=\"#initialization\">Initialization</a></li>\n </ul>\n </nav>\n </div>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `scrollOffset` | Number | `200` | Per-section observer top inset and `--md-comp-scrollspy-offset` for native anchor scroll margins. |\n| `activeClass` | String | `'active'` | Class applied to the active table-of-contents link. |\n| `getActiveElement` | Function | see below | Returns a CSS selector for the element that should receive `activeClass`, given the section’s id. |\n| `keepTopElementActive` | Boolean | `false` | If true, keep the last section above the viewport active when the scrollbar is outside all spy sections. If there is no such section, the first one stays active. |\n\n#### Methods\n\n- `.destroy()`: Disconnect the section's observer, restore its original offset style, and update the current link within its group. Repeated destruction is safe.\n\n#### Also documented\n\nThe Scrollspy section of the [API reference](https://www.expressivecss.com/llm.md) also covers Initialization, Properties.\n\n#### Rules\n\n- `toc-in-nav`: A table of contents is a set of destinations within the page. It belongs in a labelled <nav>.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
233
309
  },
234
310
  {
235
311
  "file": "search.md",
236
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Search\nA search bar, and the view it expands into.\n\nComponent ID: `search`\n\n[Component documentation](https://www.expressivecss.com/search.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/search.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/search/overview)\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/search/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/search/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/search/guidelines).\n\nSupport (2026-09-13, `llm.md#search`): Search bar and expanded search view.\n\nWeb adaptation: Application toggles hidden for docked views or opens a native dialog for full-screen views; optional Autocomplete owns suggestions.\n\nKnown boundary: CSS does not supply search results announcements or modal focus ownership.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#search).\n\n#### Contract\n\nA `<search>` with `search-bar` on it is the bar: a leading icon or icon button, an `<input type=\"search\">`, and whatever trailing actions the query needs. The element is the landmark, so there is no `role=\"search\"` to add. The input goes in bare — the bar is the container, so none of the text-field chrome (`.field`, the underline, the floating label) applies.\n\nSpacing follows what is at each end: a bare glyph sits 16dp from the edge, an icon button 4dp, because the button already insets its own glyph by 12dp. An `<img>` in the bar is the account avatar — 30dp and circular; put it inside the button when it is tappable, so the 48dp target comes from the button. `.searchbar`, the pre-1.0 name, reaches the same rules.\n\n#### Syntax\n\n```html\n<search class=\"search-bar\" aria-label=\"Search\">\n <span class=\"material-symbols\" aria-hidden=\"true\">search</span>\n <input type=\"search\" aria-label=\"Search recipes\" placeholder=\"Search recipes\">\n <button type=\"button\" class=\"icon-button\" aria-label=\"Filters\">\n <span class=\"material-symbols\" aria-hidden=\"true\">tune</span>\n </button>\n</search>\n```\n\n#### Also documented\n\nThe Search section of the [API reference](https://www.expressivecss.com/llm.md) also covers Docked view, Full-screen view, Suggestions, Tokens.\n\n#### Rules\n\n- `search-view-not-a-composite-widget`: The search view takes no composite role. Its contents are links and buttons reached with Tab; the combobox and listbox belong to the Autocomplete on the bar's input.\n- `search-bar-holds-a-search-input`: The bar's control is a search input. `type=\"search\"` is what tells assistive technology, the UA and the on-screen keyboard what the field is for, and it is static semantics, so the author states it. The selector names `.search-bar` and deliberately not the `.searchbar` alias: pre-1.0 markup of that shape predates the requirement, and holding old markup to a rule it could not have known is how a promise of an additive rename gets broken.\n"
312
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Search\nA search bar, and the view it expands into.\n\nComponent ID: `search`\n\n[Component documentation](https://www.expressivecss.com/search.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/search.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/search/overview)\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/search/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/search/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/search/guidelines).\n\nSupport (2026-09-13, `llm.md#search`): Search bar and expanded search view.\n\nWeb adaptation: Application toggles hidden for docked views or opens a native dialog for full-screen views; optional Autocomplete owns suggestions.\n\nKnown boundary: CSS does not supply search results announcements or modal focus ownership.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#search).\n\n#### Contract\n\nA `<search>` with `search-bar` on it is the bar: a leading icon or icon button, an `<input type=\"search\">`, and whatever trailing actions the query needs. The element is the landmark, so there is no `role=\"search\"` to add. The input goes in bare — the bar is the container, so none of the text-field chrome (`.field`, the underline, the floating label) applies.\n\nSpacing follows what is at each end: a bare glyph sits 16dp from the edge, an icon button 4dp, because the button already insets its own glyph by 12dp. An `<img>` in the bar is the account avatar — 30dp and circular; put it inside the button when it is tappable, so the 48dp target comes from the button.\n\n#### Syntax\n\n```html\n<search class=\"search-bar\" aria-label=\"Search\">\n <span class=\"material-symbols\" aria-hidden=\"true\">search</span>\n <input type=\"search\" aria-label=\"Search recipes\" placeholder=\"Search recipes\">\n <button type=\"button\" class=\"icon-button\" aria-label=\"Filters\">\n <span class=\"material-symbols\" aria-hidden=\"true\">tune</span>\n </button>\n</search>\n```\n\n#### Also documented\n\nThe Search section of the [API reference](https://www.expressivecss.com/llm.md) also covers Docked view, Full-screen view, Suggestions, Tokens.\n\n#### Rules\n\n- `search-view-not-a-composite-widget`: The search view takes no composite role. Its contents are links and buttons reached with Tab; the combobox and listbox belong to the Autocomplete on the bar's input.\n- `search-bar-holds-a-search-input`: The bar's control is a search input. `type=\"search\"` is what tells assistive technology, the UA and the on-screen keyboard what the field is for, and it is static semantics, so the author states it.\n"
237
313
  },
238
314
  {
239
315
  "file": "select.md",
240
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Select\nChoose one option, or several, from a styled menu.\n\nComponent ID: `select`\n\n[Component documentation](https://www.expressivecss.com/select.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/select.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Related Google guidance](https://m3.material.io/components/menus/overview)\n\n#### Material mapping\n\nRelationship: related. Related guidance is not a dedicated component specification.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/menus/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/menus/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/menus/guidelines).\n\nSupport (2026-09-17, `llm.md#select`): Single/multiple selection, optgroups, and browser-default fallback.\n\nWeb adaptation: Native select enhanced by runtime; this is related to menu/text-field patterns.\n\nKnown boundary: Native zoom, screen-reader announcements, Windows contrast themes and physical-device form interaction remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#select).\n\n#### Contract\n\nFilled enhanced selects reserve a label row that grows with wrapping and enlarged text. Leading icons retain their label spacing; supporting text stays outside the filled surface. Native and outlined variants retain their existing layouts.\n\nSelect turns a native `<select>` into a menu. Wrap it in a `.field` and give the label a matching `for`. `AutoInit()` starts every `select` except those marked `no-autoinit`. Add `browser-default` to keep the native control.\n\nAdd `multiple` to select several options. Chosen values appear as a comma-separated list. Distinct options with the same value can be selected and deselected independently.\n\n#### Syntax\n\n```html\n<div class=\"field\">\n <select id=\"form-select-1\">\n <option value=\"\" disabled selected>Choose your option</option>\n <option value=\"1\">Option 1</option>\n <option value=\"2\">Option 2</option>\n <option value=\"3\">Option 3</option>\n </select>\n <label for=\"form-select-1\">Expressive Select</label>\n</div>\n\n<div class=\"field\">\n <select id=\"form-select-2\" multiple>\n <option value=\"\" disabled selected>Choose your option</option>\n <option value=\"1\">Option 1</option>\n <option value=\"2\">Option 2</option>\n <option value=\"3\">Option 3</option>\n </select>\n <label for=\"form-select-2\">Multiple Select</label>\n</div>\n\n<div class=\"field\">\n <select id=\"form-select-3\">\n <optgroup label=\"team 1\">\n <option value=\"1\">Option 1</option>\n <option value=\"2\">Option 2</option>\n </optgroup>\n <optgroup label=\"team 2\">\n <option value=\"3\">Option 3</option>\n <option value=\"4\">Option 4</option>\n </optgroup>\n </select>\n <label for=\"form-select-3\">Optgroups</label>\n</div>\n\n<div class=\"field\">\n <select id=\"form-select-4\">\n <option value=\"\" disabled selected>Choose your option</option>\n <option value=\"1\" data-icon=\"photo.jpg\">example 1</option>\n <option value=\"2\" data-icon=\"photo.jpg\" class=\"left\">example 2</option>\n </select>\n <label for=\"form-select-4\">Images in select</label>\n</div>\n\n<label for=\"form-select-6\">Browser Select</label>\n<select id=\"form-select-6\" class=\"browser-default\">\n <option value=\"\" disabled selected>Choose your option</option>\n <option value=\"1\">Option 1</option>\n</select>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `classes` | String | `''` | Space-separated classes added to the `.field` wrapper, either the parent `.field` or one the plugin creates. |\n| `menuOptions` | Object | `{}` | Options passed to `Menu`. See Menu. `coverTrigger` is forced to `false` and `closeOnClick` is forced to `false`. |\n\n#### Methods\n\n- `.getSelectedValues()`: Selected values as an array of strings.\n- `.refresh()`: Re-read native values, option labels, options and disabled states. The generated rows are rebuilt while the field and an existing Menu instance remain. Refresh preserves focus on a surviving option in an open menu; it does not emit a user `change` event. Call it after programmatic changes, including moving the select to another form or changing its `form` attribute, to rebind reset handling. There is no mutation observer.\n- `.destroy()`: Destroy the plugin instance, remove the menu, and restore the native select. Associated labels keep their original nodes and event listeners; teardown restores their attributes and authored positions.\n\n#### Also documented\n\nThe Select section of the [API reference](https://www.expressivecss.com/llm.md) also covers Initialization, Properties, Disabled Styles.\n\n#### Rules\n\n- `option-has-selected-state`: role=option promises a selection state. Without aria-selected the listbox never says which entry is chosen.\n- `field-container-class`: The field container is `.field`. `.input-field` matches nothing in the sheet - the only `.input-field` rule is `.chips.input-field`.\n- `field-icon-not-i`: <i> means idiomatic text, not icon. Use <span class=\"material-symbols\">.\n- `field-icon-hidden`: The ligature is read aloud. A field icon is decoration; the label names the control.\n- `field-icon-side`: A field icon must name its side with .prefix or .suffix. The positional fallback counts elements of the same *type*, which only ever worked for <i>.\n- `field-label-for`: A label that does not wrap its control has to name it. The floating label sits *after* the control and cannot wrap it, so it always needs `for`; a label that does wrap its control (the file input trigger) does not.\n- `field-control-id`: Without an id the label has nothing to point at.\n- `field-supporting-text-id`: Supporting and error text has to be referenceable to be announced with the control.\n- `field-supporting-text-linked`: A field with supporting text must point at it with aria-describedby, or the text is never read out with the control.\n- `password-toggle-reports-its-state`: Give a .password-toggle aria-pressed=\"false\". The behavior flips it, and its name stays \"Show password\", so the pressed state is how a screen reader user hears that the password is visible.\n- `otp-autocompletes`: Set autocomplete=\"one-time-code\" on a one-time code input so the browser can offer the code from a message.\n- `otp-is-not-a-number`: A code is not a quantity: type=\"number\" drops leading zeros and adds a spinner. Use type=\"text\" with inputmode=\"numeric\".\n"
316
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Select\nChoose one option, or several, from a styled menu.\n\nComponent ID: `select`\n\n[Component documentation](https://www.expressivecss.com/select.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/select.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Related Google guidance](https://m3.material.io/components/menus/overview)\n\n#### Material mapping\n\nRelationship: related. Related guidance is not a dedicated component specification.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/menus/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/menus/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/menus/guidelines).\n\nSupport (2026-10-27, `llm.md#select`): Single/multiple selection, optgroups, browser-default fallback, and opt-in single and multiple select search with a separate popup field and native form values.\n\nWeb adaptation: Native select enhanced by runtime; this is related to menu/text-field patterns.\n\nKnown boundary: Native zoom, screen-reader announcements, Windows contrast themes and physical-device form interaction remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#select).\n\n#### Contract\n\nFilled enhanced selects reserve a label row that grows with wrapping and enlarged text. Leading icons retain their label spacing; supporting text stays outside the filled surface. Native and outlined variants retain their existing layouts.\n\nSelect turns a native `<select>` into a menu. Wrap it in a `.field` and give the label a matching `for`. `AutoInit()` starts every `select` except those marked `no-autoinit`. Add `browser-default` to keep the native control.\n\nAdd `multiple` to select several options. Chosen values appear as a comma-separated list. Distinct options with the same value can be selected and deselected independently.\n\n#### Syntax\n\n```html\n<div class=\"field\">\n <select id=\"form-select-1\">\n <option value=\"\" disabled selected>Choose your option</option>\n <option value=\"1\">Option 1</option>\n <option value=\"2\">Option 2</option>\n <option value=\"3\">Option 3</option>\n </select>\n <label for=\"form-select-1\">Expressive Select</label>\n</div>\n\n<div class=\"field\">\n <select id=\"form-select-2\" multiple>\n <option value=\"\" disabled selected>Choose your option</option>\n <option value=\"1\">Option 1</option>\n <option value=\"2\">Option 2</option>\n <option value=\"3\">Option 3</option>\n </select>\n <label for=\"form-select-2\">Multiple Select</label>\n</div>\n\n<div class=\"field\">\n <select id=\"form-select-3\">\n <optgroup label=\"team 1\">\n <option value=\"1\">Option 1</option>\n <option value=\"2\">Option 2</option>\n </optgroup>\n <optgroup label=\"team 2\">\n <option value=\"3\">Option 3</option>\n <option value=\"4\">Option 4</option>\n </optgroup>\n </select>\n <label for=\"form-select-3\">Optgroups</label>\n</div>\n\n<div class=\"field\">\n <select id=\"form-select-4\">\n <option value=\"\" disabled selected>Choose your option</option>\n <option value=\"1\" data-icon=\"photo.jpg\">example 1</option>\n <option value=\"2\" data-icon=\"photo.jpg\" class=\"left\">example 2</option>\n </select>\n <label for=\"form-select-4\">Images in select</label>\n</div>\n\n<label for=\"form-select-6\">Browser Select</label>\n<select id=\"form-select-6\" class=\"browser-default\">\n <option value=\"\" disabled selected>Choose your option</option>\n <option value=\"1\">Option 1</option>\n</select>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `classes` | String | `''` | Space-separated classes added to the `.field` wrapper, either the parent `.field` or one the plugin creates. |\n| `menuOptions` | Object | `{}` | Options passed to `Menu`. See Menu. `coverTrigger` is forced to `false` and `closeOnClick` is forced to `false`. |\n| `searchable` | Boolean | `false` | Add a popup search field to a plain single/multiple FormSelect or single/multiple TreeSelect. Browser-default selects stay native. |\n| `i18n` | Object | `{ searchLabel: 'Search options', noResults: 'No results' }` | Search input label and no-results status. Override either string; omitted keys retain defaults. |\n\n#### Methods\n\n- `.getSelectedValues()`: Selected values as an array of strings.\n- `.refresh()`: Re-read native values, option labels, options and disabled states. The generated rows are rebuilt while the field and an existing Menu instance remain. Refresh preserves focus on a surviving option in an open menu; it does not emit a user `change` event. Call it after programmatic changes, including moving the select to another form or changing its `form` attribute, to rebind reset handling. There is no mutation observer.\n- `.destroy()`: Destroy the plugin instance, remove the menu, and restore the native select. Associated labels keep their original nodes and event listeners; teardown restores their attributes and authored positions.\n\n#### Also documented\n\nThe Select section of the [API reference](https://www.expressivecss.com/llm.md) also covers Select search, Initialization, Properties, Disabled styles.\n\n#### Rules\n\n- `option-has-selected-state`: role=option promises a selection state. Without aria-selected the listbox never says which entry is chosen.\n- `field-container-class`: The field container is `.field`. `.input-field` matches nothing in the sheet - the only `.input-field` rule is `.chips.input-field`.\n- `field-icon-not-i`: <i> means idiomatic text, not icon. Use <span class=\"material-symbols\">.\n- `field-icon-hidden`: The ligature is read aloud. A field icon is decoration; the label names the control.\n- `field-icon-side`: A field icon must name its side with .prefix or .suffix. Fields have no positional fallback.\n- `field-label-for`: A label that does not wrap its control has to name it. The floating label sits *after* the control and cannot wrap it, so it always needs `for`; a label that does wrap its control (the file input trigger) does not.\n- `field-control-id`: Without an id the label has nothing to point at.\n- `field-supporting-text-id`: Supporting and error text has to be referenceable to be announced with the control.\n- `field-supporting-text-linked`: A field with supporting text must point at it with aria-describedby, or the text is never read out with the control.\n- `password-toggle-reports-its-state`: Give a .password-toggle aria-pressed=\"false\". The behavior flips it, and its name stays \"Show password\", so the pressed state is how a screen reader user hears that the password is visible.\n- `otp-autocompletes`: Set autocomplete=\"one-time-code\" on a one-time code input so the browser can offer the code from a message.\n- `otp-is-not-a-number`: A code is not a quantity: type=\"number\" drops leading zeros and adds a spinner. Use type=\"text\" with inputmode=\"numeric\".\n"
317
+ },
318
+ {
319
+ "file": "show-more.md",
320
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Show more\nClamped text with a button that shows the rest, and hides itself when the text already fits.\n\nComponent ID: `show-more`\n\n[Component documentation](https://www.expressivecss.com/show-more.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/show-more.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A product card clamps its description to three lines with a Show more text button; a terms-of-service page with headed sections uses an accordion instead.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-25); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-25, `llm.md#show-more`): A .show-more holding text with a line-clamp-1, -2 or -3 class and a <button type=\"button\">; the button sets aria-expanded and aria-controls, shows the full text, reads \"Show less\" while it is open (i18n.showLess or data-show-less), and hides while the clamped text fits, checking again on resize, after web fonts load and on blur.\n\nWeb adaptation: The component unclamps with inline -webkit-line-clamp and line-clamp, because the utilities layer and prefixed clamp classes beat any component rule. A ResizeObserver on the text and document.fonts.ready run the fit check, scrollHeight against clientHeight with 1px for Firefox's rounding.\n\nKnown boundary: Toggling, the fit check across widths, fractional line heights and window-size prefixes, the focused-button cases, a web-font swap, labels, events and destroy() are checked in a browser or jsdom; spoken output is unverified. Find-in-page does not open clamped text, a font that starts loading after document.fonts.ready has settled is not caught until the next resize, and an icon-only button keeps its aria-label while open.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#show-more).\n\n#### Contract\n\nA `.show-more` holds the text, with a `line-clamp-1`, `line-clamp-2` or `line-clamp-3` class, and a `<button type=\"button\">` whose text is the collapsed label. `AutoInit()` starts every one. The component points the button at the text with `aria-controls`, adding an id to the text when it has none, and sets `aria-expanded`. Activating the button shows the full text and changes the label to \"Show less\"; activating it again clamps the text and puts the author's label back. Only the button's last text outside an `aria-hidden=\"true\"` element changes, so an icon keeps its glyph. An icon-only button named by `aria-label` has no text to change: it keeps its name, and only `aria-expanded` says the text is open. Prefer a visible label.\n\nWhile the clamped text fits, the button gets `hidden`, so it is neither seen nor reached by Tab. The component checks again when the text's size changes and once web fonts have loaded, so `line-clamp-3 expanded:line-clamp-none` hides the button from Expanded windows up. A focused button stays until focus moves on, and open text keeps its button. Expanding sets `-webkit-line-clamp` and `line-clamp` inline, because a prefixed clamp class would beat `line-clamp-none`. The text opens and closes at once, without a transition.\n\nThe clamp classes set `display`, so the text cannot also be a flex or grid container; put the clamp on a child. The browser's find-in-page does not open clamped text, so a match in the hidden lines is not shown until the person opens it.\n\n#### Syntax\n\n```html\n<div class=\"show-more\">\n <p class=\"line-clamp-3\">A long product description that runs well past three lines...</p>\n <button type=\"button\" class=\"text\">Show more</button>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `onOpen` | Function | `null` | Called after the full text shows. |\n| `onClose` | Function | `null` | Called after the text is clamped again. |\n| `i18n` | Object | `{ showLess: 'Show less' }` | The button's label while the text is open. `data-show-less` on the button sets it for one control. |\n\n#### Methods\n\n- `.open()`: Shows the full text.\n- `.close()`: Clamps the text again.\n- `.destroy()`: Removes the listeners, clamps the text without an event, and restores the button's label, `aria-expanded`, `aria-controls` and `hidden`, and the text's id.\n\n#### Rules\n\n- `show-more-needs-a-button`: Give a .show-more a <button type=\"button\"> child. A <span> or <div> with role=\"button\" is not operable from the keyboard without script, and a <button> without type=\"button\" submits the form it sits in.\n- `show-more-needs-clamped-text`: Give a .show-more a child with line-clamp-1, line-clamp-2 or line-clamp-3. That is the text the button expands; without it the component does nothing.\n"
241
321
  },
242
322
  {
243
323
  "file": "side-sheet.md",
244
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Side sheet\nOptional content and actions, without interrupting the main view.\n\nComponent ID: `side-sheet`\n\n[Component documentation](https://www.expressivecss.com/side-sheet.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/side-sheet.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. [Google guidance](https://m3.material.io/components/side-sheets/overview)\n\n- compact window: replace-with [bottom-sheet](./bottom-sheet.md). Secondary content must fit a compact window.\n- compact window: replace-with [dialogs](./dialogs.md). Use a dialog when a compact task must block progress.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/side-sheets/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/side-sheets/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/side-sheets/guidelines).\n\nSupport (2026-09-17, `llm.md#side-sheet`): Standard and modal side sheets.\n\nWeb adaptation: Native dialog and shared drag/dismiss behavior.\n\nKnown boundary: Rendered RTL docking, dragging and enlarged translated text are checked; native zoom, assistive technology and physical-device review remain pending.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#side-sheet).\n\n#### Contract\n\nA recent flick toward the docked edge dismisses the sheet. For short drags, pausing for at least 100ms or reversing direction lets it snap back.\n\nA `dialog.side-sheet` (or `.right` / `.left`) is optional content anchored to the side. `show()` is standard (1dp inner divider, no scrim). `showModal()` is modal (28dp inner corners, scrim). A `<header>` holds an optional back button, a `title-large` headline, and a close control. A last-child `form[method=dialog]` is the action row. Drag the header or the inner 24dp edge toward the docked side to dismiss.\n\nDocking is logical: `.start`, `.left` and `.left-sheet` use the start edge; the default, `.right` and `.right-sheet` use the end edge. Drag direction, inner-edge hit testing, modal corners and opening motion follow computed RTL direction. Headings and actions wrap; the body scrolls independently. Shared sheet dragging ignores secondary-pointer cancellation and resets when a sheet closes, is removed, or a primary press starts elsewhere. Dismissal thresholds are unchanged. Keep an explicit close control; native zoom, assistive technology and physical-device review remain pending.\n\n#### Syntax\n\n```html\n<dialog class=\"side-sheet\" aria-labelledby=\"headline-title\">\n <header>\n <h2 id=\"headline-title\">Headline</h2>\n <form method=\"dialog\">\n <button type=\"submit\" aria-label=\"Close\">\n <span class=\"material-symbols\" aria-hidden=\"true\">close</span>\n </button>\n </form>\n </header>\n <div>…</div>\n</dialog>\n```\n\n#### Rules\n\n- `dialog-is-named`: A <dialog> takes no name from its heading. Point aria-labelledby at that heading, or give it an aria-label - otherwise it opens announced as just \"dialog\".\n"
324
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Side sheet\nOptional content and actions, without interrupting the main view.\n\nComponent ID: `side-sheet`\n\n[Component documentation](https://www.expressivecss.com/side-sheet.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/side-sheet.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. [Google guidance](https://m3.material.io/components/side-sheets/overview)\n\n- compact window: replace-with [bottom-sheet](./bottom-sheet.md). Secondary content must fit a compact window.\n- compact window: replace-with [dialogs](./dialogs.md). Use a dialog when a compact task must block progress.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/side-sheets/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/side-sheets/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/side-sheets/guidelines).\n\nSupport (2026-09-17, `llm.md#side-sheet`): Standard and modal side sheets.\n\nWeb adaptation: Native dialog and shared drag/dismiss behavior.\n\nKnown boundary: Rendered RTL docking, dragging and enlarged translated text are checked; native zoom, assistive technology and physical-device review remain pending.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#side-sheet).\n\n#### Contract\n\nA recent flick toward the docked edge dismisses the sheet. For short drags, pausing for at least 100ms or reversing direction lets it snap back.\n\nA `dialog.side-sheet` is optional content anchored to the side, at the trailing edge unless it has `.start`. `show()` is standard (1dp inner divider, no scrim). `showModal()` is modal (28dp inner corners, scrim). A `<header>` holds an optional back button, a `title-large` headline, and a close control. A last-child `form[method=dialog]` is the action row. Drag the header or the inner 24dp edge toward the docked side to dismiss.\n\nDocking is logical: `.start` uses the start edge, and the default uses the end edge. Drag direction, inner-edge hit testing, modal corners and opening motion follow computed RTL direction. Headings and actions wrap; the body scrolls independently. Shared sheet dragging ignores secondary-pointer cancellation and resets when a sheet closes, is removed, or a primary press starts elsewhere. Dismissal thresholds are unchanged. Keep an explicit close control; native zoom, assistive technology and physical-device review remain pending.\n\n#### Syntax\n\n```html\n<dialog class=\"side-sheet\" aria-labelledby=\"headline-title\">\n <header>\n <h2 id=\"headline-title\">Headline</h2>\n <form method=\"dialog\">\n <button type=\"submit\" aria-label=\"Close\">\n <span class=\"material-symbols\" aria-hidden=\"true\">close</span>\n </button>\n </form>\n </header>\n <div>…</div>\n</dialog>\n```\n\n#### Rules\n\n- `dialog-is-named`: A <dialog> takes no name from its heading. Point aria-labelledby at that heading, or give it an aria-label - otherwise it opens announced as just \"dialog\".\n"
245
325
  },
246
326
  {
247
327
  "file": "skeleton.md",
248
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Skeleton\nPlaceholder shapes that hold the layout while content loads.\n\nComponent ID: `skeleton`\n\n[Component documentation](https://www.expressivecss.com/skeleton.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/skeleton.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A one-second spinner while a form saves is a loading indicator. Skeletons there would draw content shapes for something that is not content.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#skeleton`): Box, text line and circle shapes with a shimmer that reverses in RTL, stops under reduced motion and keeps an outline in forced colors.\n\nWeb adaptation: Empty decorative blocks; the page marks the loading region aria-busy and reports the wait through a status.\n\nKnown boundary: The shimmer, its reduced-motion stop and the absence of skeletons from the accessibility tree are checked in a browser; status announcements and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#skeleton).\n\n#### Contract\n\nA `.skeleton` is an empty block in the shape of the content it stands in for, with a shimmer passing across it. Size the default box with `width` and `height`. `text` is one line of body text, and the last of several lines runs shorter. `circle` is a 40px circle for an avatar. Reduced motion stops the shimmer and keeps the shapes. CSS only; there is no JavaScript.\n\nSkeletons say nothing to assistive technology. Mark the region being filled with `aria-busy=\"true\"` and report the wait in a status, such as a loading indicator or visually hidden `role=\"status\"` text. Swap in the content and remove `aria-busy` in the same update.\n\n#### Syntax\n\n```html\n<p class=\"visually-hidden\" role=\"status\">Loading messages</p>\n<div class=\"flex gap-4\" aria-busy=\"true\">\n <div class=\"skeleton circle\"></div>\n <div class=\"flex-1\">\n <div class=\"skeleton text\"></div>\n <div class=\"skeleton text\"></div>\n </div>\n</div>\n```\n\n#### Also documented\n\nThe Skeleton section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `skeleton-is-empty`: Keep a .skeleton empty. Text inside it is read out as if it were content; report the wait through a status such as a loading indicator, and mark the loading region aria-busy=\"true\".\n"
328
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Skeleton\nPlaceholder shapes that hold the layout while content loads.\n\nComponent ID: `skeleton`\n\n[Component documentation](https://www.expressivecss.com/skeleton.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/skeleton.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A one-second spinner while a form saves is a loading indicator. Skeletons there would draw content shapes for something that is not content.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#skeleton`): Box, text line and circle shapes with a shimmer that reverses in RTL, stops under reduced motion and keeps an outline in forced colors.\n\nWeb adaptation: Empty decorative blocks; the page marks the loading region aria-busy and reports the wait through a status.\n\nKnown boundary: The shimmer, its reduced-motion stop and the absence of skeletons from the accessibility tree are checked in a browser; status announcements and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#skeleton).\n\n#### Contract\n\nA `.skeleton` is an empty block in the shape of the content it stands in for, with a shimmer passing across it. Size the default box with `width` and `height`. `text` is one line of body text, and the last of several lines runs shorter. `circle` is a 40px circle for an avatar. Reduced motion stops the shimmer and keeps the shapes. CSS only; there is no JavaScript.\n\nSkeletons say nothing to assistive technology. Mark the region being filled with `aria-busy=\"true\"` and report the wait in a status, such as a loading indicator or visually hidden `role=\"status\"` text. Swap in the content and remove `aria-busy` in the same update.\n\n#### Syntax\n\n```html\n<p class=\"visually-hidden\" role=\"status\">Loading messages</p>\n<div class=\"flex gap-4\" aria-busy=\"true\">\n <div class=\"skeleton circle\"></div>\n <div class=\"flex-1\">\n <div class=\"skeleton text\"></div>\n <div class=\"skeleton text\"></div>\n </div>\n</div>\n```\n\n#### Also documented\n\nThe Skeleton section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `skeleton-is-empty`: Keep a .skeleton empty. Text inside it is read out as if it were content; report the wait through a status such as a loading indicator, and mark the loading region aria-busy=\"true\".\n"
249
329
  },
250
330
  {
251
331
  "file": "slider.md",
252
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Slider\nSelections from a range of values.\n\nComponent ID: `slider`\n\n[Component documentation](https://www.expressivecss.com/slider.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/slider.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. [Google guidance](https://m3.material.io/components/sliders/overview)\n\nExample: An invoice amount needs a labeled number field with appropriate precision. A slider fits approximate volume or brightness adjustment.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/sliders/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/sliders/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/sliders/guidelines).\n\nSupport (2026-09-17, `llm.md#sliders`): Standard, centered, range, horizontal/vertical, and five sizes.\n\nWeb adaptation: Native range inputs with runtime value-label and range coordination.\n\nKnown boundary: Native-handle alignment, paired interaction and emulated forced colors have browser coverage. Screen readers, physical touch devices and actual Windows contrast themes remain pending.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#slider).\n\n#### Contract\n\nSliders handle a maximum of zero and update value-label placement when the input resizes, including horizontal RTL placement.\n\nMaterial Design 3 sliders, from the HTML.\n\nAn `<input type=\"range\">` is the control. A wrapping `.slider` (or a `<label>`) is the host for the value label; `.range` and `.range-field` are the older names and still work.\n\n#### Syntax\n\n```html\n<label>\n Volume\n <input type=\"range\" min=\"0\" max=\"100\" value=\"40\">\n</label>\n```\n\n#### Methods\n\n- `.destroy()`: Destroy the plugin instance, remove the value label, tear down its event handlers, and restore the inline custom properties it changed.\n\n#### Also documented\n\nThe Sliders section of the [API reference](https://www.expressivecss.com/llm.md) also covers Variants, Sizes, Inset icon, stops, value, Vertical, Disabled, Initialization, Properties.\n\n#### Rules\n\n- `range-labelled`: A range announces only its value unless something names it.\n"
332
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Slider\nSelect a value from a range.\n\nComponent ID: `slider`\n\n[Component documentation](https://www.expressivecss.com/slider.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/slider.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. [Google guidance](https://m3.material.io/components/sliders/overview)\n\nExample: An invoice amount needs a labeled number field with appropriate precision. A slider fits approximate volume or brightness adjustment.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/sliders/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/sliders/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/sliders/guidelines).\n\nSupport (2026-09-17, `llm.md#sliders`): Standard, centered, range, horizontal/vertical, and five sizes.\n\nWeb adaptation: Native range inputs with runtime value-label and range coordination.\n\nKnown boundary: Native-handle alignment, paired interaction and emulated forced colors have browser coverage. Screen readers, physical touch devices and actual Windows contrast themes remain pending.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#slider).\n\n#### Contract\n\nSliders handle a maximum of zero and update value-label placement when the input resizes, including horizontal RTL placement.\n\nMaterial Design 3 sliders, from the HTML.\n\nAn `<input type=\"range\">` is the control. A wrapping `.slider` (or a `<label>`) is the host for the value label.\n\n#### Syntax\n\n```html\n<label>\n Volume\n <input type=\"range\" min=\"0\" max=\"100\" value=\"40\">\n</label>\n```\n\n#### Methods\n\n- `.destroy()`: Destroy the plugin instance, remove the value label, tear down its event handlers, and restore the inline custom properties it changed.\n\n#### Also documented\n\nThe Sliders section of the [API reference](https://www.expressivecss.com/llm.md) also covers Variants, Sizes, Inset icon, stops, value, Vertical, Disabled, Initialization, Properties.\n\n#### Rules\n\n- `range-labelled`: A range announces only its value unless something names it.\n"
253
333
  },
254
334
  {
255
335
  "file": "snackbar.md",
256
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Snackbar\nMaterial Design 3 snackbars, from the HTML.\n\nComponent ID: `snackbar`\n\n[Component documentation](https://www.expressivecss.com/snackbar.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/snackbar.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. [Google guidance](https://m3.material.io/components/snackbar/overview)\n\nExample: A successful save can use a snackbar. Offline status that still allows editing needs a persistent inline message; a required irreversible-delete confirmation needs a dialog before the action.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/snackbar/overview).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/snackbar/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/snackbar/guidelines).\n\nSupport (2026-09-13, `llm.md#snackbar`): Timed text feedback or persistent actionable messages, with optional close control and focused Escape dismissal.\n\nWeb adaptation: Use the documented announcement and timeout behavior.\n\nKnown boundary: Custom template actions require explicit persistence and a close control. Application keyboard reach controls and spoken live-region delivery need verification.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#snackbar).\n\n#### Contract\n\nSnackbars with an action are persistent and dismissible by default. Escape dismisses a focused snackbar; closing restores focus only when it remains inside the bar. Explicit finite timers still pause during focus or hover.\n\nSnackbars show short updates about app processes at the bottom of the screen. They should not interrupt browsing. A `.snackbar` is the bar. A `<p>` is the supporting text. A trailing `<button>` is the optional action; a `.circle` button is the optional close.\n\nTokens follow the [M3 snackbar spec](https://m3.material.io/components/snackbar/specs). The container is `inverse-surface`, 4dp corners, elevation 3, 48dp minimum. Supporting text is `body-medium` / `inverse-on-surface`, two lines max. The action is a `label-large` / `inverse-primary` text button. Close is a 24dp `inverse-on-surface` icon. On Compact viewports the bar is inset 8dp from the edges; from the Medium breakpoint up it hugs content (344–672dp) and sits centered 24dp from the bottom.\n\n#### Syntax\n\n```js\nnew Expressive.Snackbar({ text: 'Photo saved to album' });\n\nnew Expressive.Snackbar({\n text: 'Item archived',\n action: 'Undo',\n onAction: function() { /* restore */ }\n});\n\nnew Expressive.Snackbar({\n text: \"Can't send photo. Retry in 5 seconds.\",\n action: 'Retry',\n dismissible: true\n});\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `text` | String | `''` | Plain-text supporting text, wrapped in a `<p>`. If set, it replaces any HTML from `snackbarId`. |\n| `action` | String | `''` | Optional action label. Rendered as a trailing text button. |\n| `onAction` | Function | `null` | Called when the action button is pressed. The snackbar still dismisses. |\n| `dismissible` | Boolean | `false`, or `true` with an action | Show a trailing close icon button. An explicit false is preserved. |\n| `snackbarId` | String | — | Id of a `<template>` (or another element) used as the snackbar body. Templates are cloned. Other elements are moved into the snackbar and restored on dismissal or replacement, so the same ID can be reused. |\n| `displayLength` | Number | `4000` | Default 4000ms without an action, or Infinity with an action. Explicit finite timers need equivalent persistent feedback in the application. |\n| `inDuration` | Number | `300` | Enter transition duration, in milliseconds. |\n| `outDuration` | Number | `375` | Exit transition duration, in milliseconds. |\n| `classes` | String | `''` | Space-separated classes added to the snackbar. `rounded` is a stadium. `top` moves the bar off the bottom. |\n| `completeCallback` | Function | `null` | Called when the snackbar is dismissed. |\n| `activationPercent` | Number | `0.8` | Fraction of the snackbar’s width a drag must travel to dismiss it. |\n| `root` | Element | `null` | Any element in the tree the snackbar should render into. Pass one when the page lives in a shadow root, so the container is appended there rather than to `document.body`. |\n\n#### Methods\n\n- `.dismiss()`: Dismiss this snackbar with its exit animation. Runs `completeCallback` when the animation finishes.\n- `Snackbar.dismissAll()`: Dismiss every snackbar that is currently showing.\n\n#### Also documented\n\nThe Snackbar section of the [API reference](https://www.expressivecss.com/llm.md) also covers Initialization, Markup, Properties, Custom HTML, Callback, Styling, Dismiss a Snackbar Programmatically.\n\n#### Rules\n\nNo component-specific `semantics.json` rules apply to this guide.\n"
336
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Snackbar\nMaterial Design 3 snackbars, from the HTML.\n\nComponent ID: `snackbar`\n\n[Component documentation](https://www.expressivecss.com/snackbar.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/snackbar.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. [Google guidance](https://m3.material.io/components/snackbar/overview)\n\nExample: A successful save can use a snackbar. Offline status that still allows editing needs a persistent inline message; a required irreversible-delete confirmation needs a dialog before the action.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/snackbar/overview).\n\nRequirements and boundaries: design reference feedback-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/snackbar/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/snackbar/guidelines).\n\nSupport (2026-09-13, `llm.md#snackbar`): Timed text feedback or persistent actionable messages, with optional close control and focused Escape dismissal.\n\nWeb adaptation: Use the documented announcement and timeout behavior.\n\nKnown boundary: Custom template actions require explicit persistence and a close control. Application keyboard reach controls and spoken live-region delivery need verification.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#snackbar).\n\n#### Contract\n\nSnackbars with an action are persistent and dismissible by default. Escape dismisses a focused snackbar; closing restores focus only when it remains inside the bar. Explicit finite timers still pause during focus or hover.\n\nSnackbars show short updates about app processes at the bottom of the screen. They should not interrupt browsing. A `.snackbar` is the bar. A `<p>` is the supporting text. A trailing `<button>` is the optional action; a `.circle` button is the optional close.\n\nTokens follow the [M3 snackbar spec](https://m3.material.io/components/snackbar/specs). The container is `inverse-surface`, 4dp corners, elevation 3, 48dp minimum. Supporting text is `body-medium` / `inverse-on-surface`, two lines max. The action is a `label-large` / `inverse-primary` text button. Close is a 24dp `inverse-on-surface` icon. On Compact viewports the bar is inset 8dp from the edges; from the Medium breakpoint up it hugs content (344–672dp) and sits centered 24dp from the bottom.\n\n#### Syntax\n\n```js\nnew Expressive.Snackbar({ text: 'Photo saved to album' });\n\nnew Expressive.Snackbar({\n text: 'Item archived',\n action: 'Undo',\n onAction: function() { /* restore */ }\n});\n\nnew Expressive.Snackbar({\n text: \"Can't send photo. Retry in 5 seconds.\",\n action: 'Retry',\n dismissible: true\n});\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `text` | String | `''` | Plain-text supporting text, wrapped in a `<p>`. If set, it replaces any HTML from `snackbarId`. |\n| `action` | String | `''` | Optional action label. Rendered as a trailing text button. |\n| `onAction` | Function | `null` | Called when the action button is pressed. The snackbar still dismisses. |\n| `dismissible` | Boolean | `false`, or `true` with an action | Show a trailing close icon button. An explicit false is preserved. |\n| `snackbarId` | String | — | Id of a `<template>` (or another element) used as the snackbar body. Templates are cloned. Other elements are moved into the snackbar and restored on dismissal or replacement, so the same ID can be reused. |\n| `displayLength` | Number | `4000` | Default 4000ms without an action, or Infinity with an action. Explicit finite timers need equivalent persistent feedback in the application. |\n| `inDuration` | Number | `300` | Enter transition duration, in milliseconds. |\n| `outDuration` | Number | `375` | Exit transition duration, in milliseconds. |\n| `classes` | String | `''` | Space-separated classes added to the snackbar. `rounded` is a stadium. `top` moves the bar off the bottom. |\n| `completeCallback` | Function | `null` | Called when the snackbar is dismissed. |\n| `activationPercent` | Number | `0.8` | Fraction of the snackbar’s width a drag must travel to dismiss it. |\n| `root` | Element | `null` | Any element in the tree the snackbar should render into. Pass one when the page lives in a shadow root, so the container is appended there rather than to `document.body`. |\n| `replace` | Boolean | `false` | Show at once, cutting the snackbar on screen short, instead of waiting behind it. Copy buttons use this so a second copy answers right away. |\n\n#### Methods\n\n- `.dismiss()`: Dismiss this snackbar with its exit animation. Runs `completeCallback` when the animation finishes.\n- `Snackbar.dismissAll()`: Dismiss the snackbar on screen and every one waiting behind it.\n\n#### Also documented\n\nThe Snackbar section of the [API reference](https://www.expressivecss.com/llm.md) also covers Initialization, Markup, Properties, Custom HTML, Callback, Styling, Dismiss a Snackbar Programmatically.\n\n#### Rules\n\nNo component-specific `semantics.json` rules apply to this guide.\n"
257
337
  },
258
338
  {
259
339
  "file": "split-button.md",
260
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Split button\nA lead action and a trailing half that opens a menu of related ones, in five sizes.\n\nComponent ID: `split-button`\n\n[Component documentation](https://www.expressivecss.com/split-button.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/split-button.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/split-button)\n\nExample: PDF/CSV format selection for a later Export action belongs in a select or native inputs. A split button fits Export now plus alternate export commands.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/split-button/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/split-button/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/split-button/guidelines).\n\nSupport (2026-09-18, `llm.md#split-button`): Leading action and trailing menu trigger with matching styles.\n\nWeb adaptation: Reuses Menu; no separate split-button runtime.\n\nKnown boundary: Separate 48dp effective targets, native zoom, spoken output and physical-device behavior remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#split-button).\n\n#### Contract\n\nOne action the user will take most of the time, next to a menu of the ones they might take instead — Save and \"save as\", Reply and \"reply all\", Export and every other format. The root is a `<div class=\"split-button\">` holding two buttons: a leading one that does the work, and a trailing `.menu-trigger` that opens a `<menu>`. The pair sits 2dp apart, the outside is round, and the seam between them is square-ish.\n\nThe trailing half is an ordinary Menu trigger, so everything Menu does it does here — `Expressive.AutoInit()` starts it, `data-target` names the surface, and the menu's keyboard model, alignment and options are unchanged. This component adds no script of its own. The one thing Menu does differently is width: a trigger narrower than the surface's own 112dp minimum is not a constraint it can honour, and this trigger is a chevron in a 48dp box, so the menu is sized to its own content instead.\n\n#### Syntax\n\n```html\n<div class=\"split-button\">\n <button class=\"button\">Save</button>\n <button class=\"button menu-trigger\" data-target=\"save-menu\" aria-label=\"More save options\">\n <span class=\"material-symbols\" aria-hidden=\"true\">arrow_drop_down</span>\n </button>\n <menu id=\"save-menu\">\n <li><button type=\"button\">Save a copy</button></li>\n <li><button type=\"button\">Save as template</button></li>\n </menu>\n</div>\n```\n\n#### Rules\n\n- `split-button-is-not-an-authored-composite-widget`: A split button takes no composite role; its two halves are reached with Tab, not arrow keys. The <menu> the trailing half opens is the composite widget here, and it carries its own role.\n- `split-button-halves-are-controls`: A split button is exactly two controls and the menu one of them opens: a <button>, or an <a class=\"button\" href> when it navigates. A wrapper element is refused too - the seam, the insets and the larger trailing icon are all written against direct children, so a nested <div> loses every one of them.\n- `split-button-half-is-not-an-icon-button`: An icon button is its own component: its height, insets, colours, icon sizing and transitions are all set on the element itself, so it reaches none of the split button's geometry and fights the seam it is handed. The trailing half is <button class=\"button menu-trigger\">, which the container sizes like the leading one.\n- `split-button-trigger-is-the-trailing-half`: The menu trigger is the *trailing* half, and only the <menu> may follow it. Order is the whole of the geometry here: the halves are told apart by role, not by position, so a trigger written first still takes the trailing side's round outer corners while flex puts it on the leading side - a split button with its seam and its round ends swapped.\n- `split-button-is-two-controls`: A split button is exactly two controls: one lead action and one menu trigger. A third gets the leading half's rules as well, so two buttons draw round start corners and a seam against each other. Several peer actions are a button group; several related ones belong in the menu.\n- `split-button-has-a-leading-action`: A split button is a lead action *and* a menu of related ones; without the lead it is a menu trigger wearing half a pill. The trailing half's shape is written for a seam, so the only control ends up squared off on the edge it should be joined along, with nothing to join to. One control that opens a menu is a menu trigger - drop the container. Not fragmentSafe: what it detects is an omission, and a fragment omits by nature.\n- `split-button-has-a-trailing-menu-trigger`: The trailing half is the whole point: it opens a menu of actions related to the leading one. Without `.menu-trigger` on it there is no menu, and the pair is a lone button with a chevron glued to it - write two buttons, or one. Not fragmentSafe: what it detects is an omission, and a fragment omits by nature.\n- `split-button-trigger-names-its-menu`: Menu finds its surface through `data-target` on the trigger, falling back to an `href` fragment. A `.menu-trigger` with neither initializes against no element: it gets aria-haspopup and aria-expanded it will never change, which is a promise of a menu that does not exist.\n- `split-button-trailing-icon-hidden`: The ligature is real text and is read out verbatim, so an unhidden chevron names the trailing half \"arrow_drop_down\". It is decoration - the control carries the name - so it is aria-hidden=\"true\", and `icon-only-control-is-named` then requires the aria-label.\n- `split-button-expanded-is-not-authored`: Expanded is dynamic state, so the framework owns it. Menu's constructor stamps aria-expanded on the trigger and every open() and close() rewrites it; authoring it states a value that is about to be overwritten - and the expanded shape is drawn from that attribute, so an authored \"true\" draws an open split button over a closed menu.\n- `menu-children-are-list-items`: <menu> is a list: its content model permits only <li>. A bare <hr> between entries is invalid - put the separator inside an <li>, or use role=separator there.\n- `context-menu-region-is-not-a-control`: data-context-menu goes on a region of content. A button or link already has an action for Enter and click; give it its own menu trigger instead.\n"
340
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Split button\nA lead action and a trailing half that opens a menu of related ones, in five sizes.\n\nComponent ID: `split-button`\n\n[Component documentation](https://www.expressivecss.com/split-button.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/split-button.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/split-button)\n\nExample: PDF/CSV format selection for a later Export action belongs in a select or native inputs. A split button fits Export now plus alternate export commands.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/split-button/overview).\n\nRequirements and boundaries: design reference layout-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/split-button/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/split-button/guidelines).\n\nSupport (2026-09-18, `llm.md#split-button`): Leading action and trailing menu trigger with matching styles.\n\nWeb adaptation: Reuses Menu; no separate split-button runtime.\n\nKnown boundary: Separate 48dp effective targets, native zoom, spoken output and physical-device behavior remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#split-button).\n\n#### Contract\n\nOne action the user will take most of the time, next to a menu of the ones they might take instead — Save and \"save as\", Reply and \"reply all\", Export and every other format. The root is a `<div class=\"split-button\">` holding two buttons: a leading one that does the work, and a trailing `.menu-trigger` that opens a `<menu>`. The pair sits 2dp apart, the outside is round, and the seam between them is square-ish.\n\nThe trailing half is an ordinary Menu trigger, so everything Menu does it does here — `Expressive.AutoInit()` starts it, `data-target` names the surface, and the menu's keyboard model, alignment and options are unchanged. This component adds no script of its own. The one thing Menu does differently is width: a trigger narrower than the surface's own 112dp minimum is not a constraint it can honour, and this trigger is a chevron in a 48dp box, so the menu is sized to its own content instead.\n\n#### Syntax\n\n```html\n<div class=\"split-button\">\n <button class=\"button\">Save</button>\n <button class=\"button menu-trigger\" data-target=\"save-menu\" aria-label=\"More save options\">\n <span class=\"material-symbols\" aria-hidden=\"true\">arrow_drop_down</span>\n </button>\n <menu id=\"save-menu\">\n <li><button type=\"button\">Save a copy</button></li>\n <li><button type=\"button\">Save as template</button></li>\n </menu>\n</div>\n```\n\n#### Rules\n\n- `split-button-is-not-an-authored-composite-widget`: A split button takes no composite role; its two halves are reached with Tab, not arrow keys. The <menu> the trailing half opens is the composite widget here, and it carries its own role.\n- `split-button-halves-are-controls`: A split button is exactly two controls and the menu one of them opens: a <button>, or an <a class=\"button\" href> when it navigates. A wrapper element is refused too - the seam, the insets and the larger trailing icon are all written against direct children, so a nested <div> loses every one of them.\n- `split-button-half-is-not-an-icon-button`: An icon button is its own component: its height, insets, colours, icon sizing and transitions are all set on the element itself, so it reaches none of the split button's geometry and fights the seam it is handed. The trailing half is <button class=\"button menu-trigger\">, which the container sizes like the leading one.\n- `split-button-trigger-is-the-trailing-half`: The menu trigger is the *trailing* half, and only the <menu> may follow it. Order is the whole of the geometry here: the halves are told apart by role, not by position, so a trigger written first still takes the trailing side's round outer corners while flex puts it on the leading side - a split button with its seam and its round ends swapped.\n- `split-button-is-two-controls`: A split button is exactly two controls: one lead action and one menu trigger. A third gets the leading half's rules as well, so two buttons draw round start corners and a seam against each other. Several peer actions are a button group; several related ones belong in the menu.\n- `split-button-has-a-leading-action`: A split button is a lead action *and* a menu of related ones; without the lead it is a menu trigger wearing half a pill. The trailing half's shape is written for a seam, so the only control ends up squared off on the edge it should be joined along, with nothing to join to. One control that opens a menu is a menu trigger - drop the container. Not fragmentSafe: what it detects is an omission, and a fragment omits by nature.\n- `split-button-has-a-trailing-menu-trigger`: The trailing half is the whole point: it opens a menu of actions related to the leading one. Without `.menu-trigger` on it there is no menu, and the pair is a lone button with a chevron glued to it - write two buttons, or one. Not fragmentSafe: what it detects is an omission, and a fragment omits by nature.\n- `split-button-trigger-names-its-menu`: Menu finds its surface through `data-target` on the trigger, falling back to an `href` fragment. A `.menu-trigger` with neither initializes against no element: it gets aria-haspopup and aria-expanded it will never change, which is a promise of a menu that does not exist.\n- `split-button-trailing-icon-hidden`: The ligature is real text and is read out verbatim, so an unhidden chevron names the trailing half \"arrow_drop_down\". It is decoration - the control carries the name - so it is aria-hidden=\"true\", and `icon-only-control-is-named` then requires the aria-label.\n- `split-button-expanded-is-not-authored`: Expanded is dynamic state, so the framework owns it. Menu's constructor stamps aria-expanded on the trigger and every open() and close() rewrites it; authoring it states a value that is about to be overwritten - and the expanded shape is drawn from that attribute, so an authored \"true\" draws an open split button over a closed menu.\n- `menu-children-are-list-items`: <menu> is a list: its content model permits only <li>. A bare <hr> between entries is invalid - put the separator inside an <li>, or use role=separator there.\n- `menu-group-is-named`: A <ul role=group> in a menu is a set of choices, and its name is what a screen reader announces on entering it. Give it aria-label or aria-labelledby.\n- `context-menu-region-is-not-a-control`: data-context-menu goes on a region of content. A button or link already has an action for Enter and click; give it its own menu trigger instead.\n"
261
341
  },
262
342
  {
263
343
  "file": "stat.md",
264
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Stat\nKey figures in tiles, each with a label, a value and an optional change.\n\nComponent ID: `stat`\n\n[Component documentation](https://www.expressivecss.com/stat.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/stat.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Monthly revenue for every region and product is a data table. Stat tiles would turn a comparison into a wall of numbers without columns.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#stat`): Wrapping tiles in a description list with headline values, and a change line with up, down and negative states.\n\nWeb adaptation: A <dl> pairs each value with its label; the arrow is decoration and the text carries the direction.\n\nKnown boundary: Tiling, wrapping, the value type role, arrow direction, the negative color and term-definition pairing are checked in a browser; enlarged text and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#stat).\n\n#### Contract\n\nA `<dl class=\"stats\">` holds one `<div>` tile per figure: a `<dt>` label, a `<dd>` value and an optional `<dd class=\"stat-change\">` comparison. The description list keeps each value paired with its label for assistive technology. Tiles share a row and wrap below 160px each. CSS only; there is no JavaScript.\n\nAdd `up` or `down` to `.stat-change` for an arrow, and `negative` when the change is bad news, which turns it to the error color. The arrow is decoration, so say the direction in the text.\n\n#### Syntax\n\n```html\n<dl class=\"stats\">\n <div>\n <dt>Revenue</dt>\n <dd>$48.2k</dd>\n <dd class=\"stat-change up\">+12% from last month</dd>\n </div>\n <div>\n <dt>Churn</dt>\n <dd>3.1%</dd>\n <dd class=\"stat-change up negative\">+0.4 points from last month</dd>\n </div>\n</dl>\n```\n\n#### Also documented\n\nThe Stat section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `stats-are-a-description-list`: Write .stats as a <dl> with a <dt> label and <dd> value per tile. Without the pairing a screen reader reads a run of numbers with nothing to say what each one is.\n"
344
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Stat\nKey figures in tiles, each with a label, a value and an optional change.\n\nComponent ID: `stat`\n\n[Component documentation](https://www.expressivecss.com/stat.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/stat.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Monthly revenue for every region and product is a data table. Stat tiles would turn a comparison into a wall of numbers without columns.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#stat`): Wrapping tiles in a description list with headline values, and a change line with up, down and negative states.\n\nWeb adaptation: A <dl> pairs each value with its label; the arrow is decoration and the text carries the direction.\n\nKnown boundary: Tiling, wrapping, the value type role, arrow direction, the negative color and term-definition pairing are checked in a browser; enlarged text and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#stat).\n\n#### Contract\n\nA `<dl class=\"stats\">` holds one `<div>` tile per figure: a `<dt>` label, a `<dd>` value and an optional `<dd class=\"stat-change\">` comparison. The description list keeps each value paired with its label for assistive technology. Tiles share a row and wrap below 160px each. CSS only; there is no JavaScript.\n\nAdd `up` or `down` to `.stat-change` for an arrow, and `negative` when the change is bad news, which turns it to the error color. The arrow is decoration, so say the direction in the text.\n\n#### Syntax\n\n```html\n<dl class=\"stats\">\n <div>\n <dt>Revenue</dt>\n <dd>$48.2k</dd>\n <dd class=\"stat-change up\">+12% from last month</dd>\n </div>\n <div>\n <dt>Churn</dt>\n <dd>3.1%</dd>\n <dd class=\"stat-change up negative\">+0.4 points from last month</dd>\n </div>\n</dl>\n```\n\n#### Also documented\n\nThe Stat section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `stats-are-a-description-list`: Write .stats as a <dl> with a <dt> label and <dd> value per tile. Without the pairing a screen reader reads a run of numbers with nothing to say what each one is.\n"
265
345
  },
266
346
  {
267
347
  "file": "stepper.md",
268
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Stepper\nProgress through a fixed sequence of steps, such as a checkout, with each step's state.\n\nComponent ID: `stepper`\n\n[Component documentation](https://www.expressivecss.com/stepper.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/stepper.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Account, Notifications and Privacy settings that people open in any order are tabs. A stepper would number them as if they had to be done in sequence.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#stepper`): Horizontal and vertical ordered lists with current, complete and invalid steps, supporting text, links back to finished steps and a content panel in vertical steps.\n\nWeb adaptation: An <ol> with aria-current=\"step\"; CSS alt text speaks the complete and invalid states from translatable tokens.\n\nKnown boundary: Layout in LTR and RTL, indicator fills and glyphs, and the spoken state text are checked in a browser; screen reader output and enlarged text remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#stepper).\n\n#### Contract\n\nA `.stepper` is an `<ol>`, and each `<li>` is a step whose text is its label. The list numbers the steps. Mark the step the user is on with `aria-current=\"step\"`, finished steps with `complete`, and a step that needs attention with `invalid`; the page keeps these current as the user moves. A complete step shows a check and an invalid one shows \"!\", and both states are spoken before the label through the indicator's alt text. CSS only; there is no JavaScript.\n\nA `<small>` after the label is supporting text, such as \"Optional\". A finished step can link back with an `<a>`. Steps never shrink below their label: a row too narrow for all of them wraps. Add `vertical` to stack the steps, which suits compact windows, and put a `.stepper-content` block in a vertical step to show its form or summary under the label.\n\n#### Syntax\n\n```html\n<ol class=\"stepper\">\n <li class=\"complete\"><a href=\"/checkout/cart\">Cart</a></li>\n <li aria-current=\"step\">Shipping<small>Address and speed</small></li>\n <li>Payment</li>\n <li>Review</li>\n</ol>\n```\n\n#### Also documented\n\nThe Stepper section of the [API reference](https://www.expressivecss.com/llm.md) also covers Vertical, Translation, Tokens.\n\n#### Rules\n\n- `stepper-is-an-ordered-list`: Write a stepper as <ol class=\"stepper\">. The order is the content: an ordered list announces how many steps there are and which one each is.\n- `stepper-current-is-a-step`: Mark the current step with aria-current=\"step\". \"page\" or \"true\" tells assistive technology something else.\n- `stepper-has-one-current-step`: Only one step is current. Mark finished steps with .complete instead.\n- `stepper-step-is-not-error-colored`: Mark a step that needs attention with .invalid. .error is the error color utility: it paints the whole step red and is not spoken.\n"
348
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Stepper\nProgress through a fixed sequence of steps, such as a checkout, with each step's state.\n\nComponent ID: `stepper`\n\n[Component documentation](https://www.expressivecss.com/stepper.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/stepper.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Account, Notifications and Privacy settings that people open in any order are tabs. A stepper would number them as if they had to be done in sequence.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#stepper`): Horizontal and vertical ordered lists with current, complete and invalid steps, supporting text, links back to finished steps and a content panel in vertical steps.\n\nWeb adaptation: An <ol> with aria-current=\"step\"; CSS alt text speaks the complete and invalid states from translatable tokens.\n\nKnown boundary: Layout in LTR and RTL, indicator fills and glyphs, and the spoken state text are checked in a browser; screen reader output and enlarged text remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#stepper).\n\n#### Contract\n\nA `.stepper` is an `<ol>`, and each `<li>` is a step whose text is its label. The list numbers the steps. Mark the step the user is on with `aria-current=\"step\"`, finished steps with `complete`, and a step that needs attention with `invalid`; the page keeps these current as the user moves. A complete step shows a check and an invalid one shows \"!\", and both states are spoken before the label through the indicator's alt text. CSS only; there is no JavaScript.\n\nA `<small>` after the label is supporting text, such as \"Optional\". A finished step can link back with an `<a>`. Steps never shrink below their label: a row too narrow for all of them wraps. Add `vertical` to stack the steps, which suits compact windows, and put a `.stepper-content` block in a vertical step to show its form or summary under the label.\n\n#### Syntax\n\n```html\n<ol class=\"stepper\">\n <li class=\"complete\"><a href=\"/checkout/cart\">Cart</a></li>\n <li aria-current=\"step\">Shipping<small>Address and speed</small></li>\n <li>Payment</li>\n <li>Review</li>\n</ol>\n```\n\n#### Also documented\n\nThe Stepper section of the [API reference](https://www.expressivecss.com/llm.md) also covers Vertical, Translation, Tokens.\n\n#### Rules\n\n- `stepper-is-an-ordered-list`: Write a stepper as <ol class=\"stepper\">. The order is the content: an ordered list announces how many steps there are and which one each is.\n- `stepper-current-is-a-step`: Mark the current step with aria-current=\"step\". \"page\" or \"true\" tells assistive technology something else.\n- `stepper-has-one-current-step`: Only one step is current. Mark finished steps with .complete instead.\n- `stepper-step-is-not-error-colored`: Mark a step that needs attention with .invalid. .error is the error color utility: it paints the whole step red and is not spoken.\n"
349
+ },
350
+ {
351
+ "file": "swipe-actions.md",
352
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Swipe actions\nReveals a list row's actions when a person swipes it sideways, or presses its actions button.\n\nComponent ID: `swipe-actions`\n\n[Component documentation](https://www.expressivecss.com/swipe-actions.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/swipe-actions.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: An inbox where each message can be archived or deleted takes swipe actions with an actions button per row; a row with one obvious action shows that button in the row instead.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-24); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-24, `llm.md#swipe-actions`): A touch swipe toward a row's inline start that reveals its .swipe-actions group, opening past half the group's width or on a flick and closing short of it, a [data-swipe-toggle] button with aria-expanded, focus on an action opening its row, Escape, an outside tap or an action closing it, one open row per list, an expressive:swipetoggle event and onToggle callback with { row, open }, open(row) and close(), right-to-left pages and reduced motion.\n\nWeb adaptation: Touch events with a non-passive touchmove, because a pointer drag the browser takes for a pan ends in pointercancel. The row slides by a translate on one custom property, and the actions wait past its inline end in a list clipped on the inline axis, so focus on a closed row's action scrolls nothing. The author names the toggle and the actions; the script keeps aria-expanded.\n\nKnown boundary: Swipe reveal, the slide limit, short, vertical, mouse, two-finger and cancelled drags, the toggle, focus on an action, Escape, an outside tap, closing after an action, one open row, the event and onToggle, right-to-left pages, reduced motion and destroy() are checked in a browser, with real touch in Chromium and dispatched touch events in Firefox and WebKit; real phones, flick velocity, pen input, spoken output and forced colors remain unverified. Actions only on the inline-end side, and no full swipe that runs an action.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#swipe-actions).\n\n#### Contract\n\nAdd `swipeable` to a `.list`. A row with actions holds a `<button data-swipe-toggle>` as its trailing action, then a `.swipe-actions` group of buttons as its last child. `AutoInit()` starts every `.list.swipeable`.\n\nA touch swipe toward the row's inline start, which is left in a left-to-right page, slides the row with the finger and brings the actions in from the other edge. Released past half the actions' width, or with a flick, the row stays open; released short of that, it slides back. A swipe only reveals the actions, and none of them runs until a person presses it. A swipe the other way closes an open row. A drag that moves up or down first scrolls as usual, a second finger or a cancelled touch puts the row back, and a mouse drag never swipes.\n\nThe toggle opens and closes the row for keyboard, mouse and switch users, and the script keeps its `aria-expanded` in step. The actions stay in the tab order and the accessibility tree while the row is closed, and focus on one opens its row. One row in a list is open at a time. A tap or focus outside the open row closes it, and so does Escape inside it. Escape on an action, or pressing one, closes the row and moves focus from the action to the toggle. The page handles what each action does.\n\n#### Syntax\n\n```html\n<ul class=\"list swipeable\" aria-label=\"Inbox\">\n <li>\n <span class=\"material-symbols\" aria-hidden=\"true\">mail</span>\n Weekly report\n <button type=\"button\" class=\"icon-button\" data-swipe-toggle aria-label=\"Actions for Weekly report\">\n <span class=\"material-symbols\" aria-hidden=\"true\">more_horiz</span>\n </button>\n <span class=\"swipe-actions\">\n <button type=\"button\" class=\"icon-button\" aria-label=\"Archive Weekly report\">\n <span class=\"material-symbols\" aria-hidden=\"true\">archive</span>\n </button>\n <button type=\"button\" class=\"icon-button\" aria-label=\"Delete Weekly report\">\n <span class=\"material-symbols\" aria-hidden=\"true\">delete</span>\n </button>\n </span>\n </li>\n</ul>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `onToggle` | Function | `null` | Called when a row opens or closes, with `{ row, open }`. |\n\n#### Methods\n\n- `.open(row)`: Opens a row of the list, closing the one that was open.\n- `.close()`: Closes the open row, if there is one.\n- `.destroy()`: Removes the listeners, closes the open row without an event and removes the toggles' `aria-expanded`.\n\n#### Also documented\n\nThe Swipe actions section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `swipeable-rows-open-without-swiping`: Give each .swipeable row with .swipe-actions a [data-swipe-toggle] button. Swiping is a path-based gesture; the toggle is the single-pointer alternative WCAG 2.5.1 requires, and focus on an action opens the row for keyboard users.\n"
269
353
  },
270
354
  {
271
355
  "file": "switches.md",
272
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Switches\nMaterial Design 3 switches, from the HTML.\n\nComponent ID: `switches`\n\n[Component documentation](https://www.expressivecss.com/switches.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/switches.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/switch/overview)\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/switch/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/switch/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/switch/guidelines).\n\nSupport (2026-09-13, `llm.md#switches`): Native checkbox switches and disabled controls.\n\nWeb adaptation: Input state owns selection and form value.\n\nKnown boundary: The styled native checkbox does not perform or announce a persisted setting change.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#switches).\n\n#### Contract\n\nA `<label class=\"switch\">` wrapping `<input type=\"checkbox\">` is the control. The label text is a sibling of the input — no `.lever` required. They are CSS only. There is no JavaScript component and nothing to AutoInit.\n\nTokens follow the [M3 switch spec](https://m3.material.io/components/switch/specs). The track is 52×32dp. Unselected is `surface-variant` with a 2dp `outline` and a 16dp handle. Selected is a `primary` track and a 24dp `on-primary` handle. The state layer is 40dp at 8% hover and 10% focus. The touch target is 48dp. The label is `body-large` / `on-surface`. Disabled is 38%.\n\n#### Syntax\n\n```html\n<label class=\"switch\">\n <input type=\"checkbox\">\n Wi-Fi\n</label>\n\n<label class=\"switch\">\n <input type=\"checkbox\" checked>\n Bluetooth\n</label>\n\n<label class=\"switch\">\n <input type=\"checkbox\" disabled>\n Airplane mode\n</label>\n```\n\n#### Rules\n\n- `switch-is-label`: A switch is a <label> wrapping its checkbox - that is what makes the text its accessible name.\n- `switch-decorative-text-hidden`: On/off captions inside the label are folded into the accessible name, which then reads \"Off On\". Hide them and let the label text name the switch.\n"
356
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Switches\nMaterial Design 3 switches, from the HTML.\n\nComponent ID: `switches`\n\n[Component documentation](https://www.expressivecss.com/switches.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/switches.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/switch/overview)\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/switch/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/switch/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/switch/guidelines).\n\nSupport (2026-09-13, `llm.md#switches`): Native checkbox switches and disabled controls.\n\nWeb adaptation: Input state owns selection and form value.\n\nKnown boundary: The styled native checkbox does not perform or announce a persisted setting change.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#switches).\n\n#### Contract\n\nA `<label class=\"switch\">` wrapping `<input type=\"checkbox\">` is the control. The label text is a sibling of the input — no `.lever` required. They are CSS only. There is no JavaScript component and nothing to AutoInit.\n\nTokens follow the [M3 switch spec](https://m3.material.io/components/switch/specs). The track is 52×32dp. Unselected is `surface-container-highest` with a 2dp `outline` and a 16dp handle. Selected is a `primary` track and a 24dp `on-primary` handle. The state layer is 40dp at 8% hover and 10% focus. The touch target is 48dp. The label is `body-large` / `on-surface`. Disabled is 38%.\n\n#### Syntax\n\n```html\n<label class=\"switch\">\n <input type=\"checkbox\">\n Wi-Fi\n</label>\n\n<label class=\"switch\">\n <input type=\"checkbox\" checked>\n Bluetooth\n</label>\n\n<label class=\"switch\">\n <input type=\"checkbox\" disabled>\n Airplane mode\n</label>\n```\n\n#### Rules\n\n- `switch-is-label`: A switch is a <label> wrapping its checkbox - that is what makes the text its accessible name.\n- `switch-decorative-text-hidden`: On/off captions inside the label are folded into the accessible name, which then reads \"Off On\". Hide them and let the label text name the switch.\n"
273
357
  },
274
358
  {
275
359
  "file": "tabs.md",
276
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Tabs\nMaterial Design 3 tabs, from the HTML.\n\nComponent ID: `tabs`\n\n[Component documentation](https://www.expressivecss.com/tabs.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/tabs.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/tabs/overview)\n\nExample: Home/Search/Profile are app destinations, so use a navigation bar or rail. Overview/Activity panels within one account page can use tabs; Day/Week/Month as a form value can use native radio inputs.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/tabs/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/tabs/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/tabs/guidelines).\n\nSupport (2026-09-13, `llm.md#tabs`): Primary and secondary tabs; stacked and horizontal icon layouts.\n\nWeb adaptation: Links target panels; use the shipped runtime keyboard contract.\n\nKnown boundary: Native links use Tab and aria-current; runtime does not implement composite tab arrow keys.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#tabs).\n\n#### Contract\n\nA `<nav class=\"tabs\" aria-label=\"Sections\">` of `<a href=\"#panel\">` is the bar. A `<span>` (or the link text) is the label; a leading `<span class=\"material-symbols\">` is the icon. Nest a `<span class=\"badge\">` in the icon for a count, and put that count in the tab's `aria-label` so a hidden icon does not swallow it. `.active` is the selected tab. There is no `li.tab` required — `ul.tabs > li.tab > a` stays as an alias. `AutoInit()` starts every `.tabs` except those marked `no-autoinit`.\n\nTokens follow the [M3 tabs spec](https://m3.material.io/components/tabs/specs). Primary tabs sit on `surface`, 48dp (64dp with a stacked icon). The label is `title-small` / `on-surface-variant`; selected is `primary`. The indicator is 3dp `primary` with 3dp top corners. The icon is 24dp. A 1dp `outline-variant` divider runs under the bar. Hover is 8%; focus is 10%. Disabled is 38%.\n\n#### Syntax\n\n```html\n<nav class=\"tabs\" aria-label=\"Travel\">\n <a href=\"#flight\">\n <span class=\"material-symbols\" aria-hidden=\"true\">flight</span>\n <span>Flight</span>\n </a>\n <a class=\"active\" aria-current=\"page\" href=\"#luggage\" aria-label=\"Luggage, 3 unread\">\n <span class=\"material-symbols\" aria-hidden=\"true\">luggage<span class=\"badge\">3</span></span>\n <span>Luggage</span>\n </a>\n <a href=\"#explore\">\n <span class=\"material-symbols\" aria-hidden=\"true\">explore</span>\n <span>Explore</span>\n </a>\n</nav>\n<div id=\"flight\">Flight</div>\n<div id=\"luggage\">Luggage</div>\n<div id=\"explore\">Explore</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `duration` | Number | `300` | Indicator transition duration, in milliseconds. |\n| `onShow` | Function | `null` | Called when a new tab panel is shown. Receives the panel element. |\n| `swipeable` | Boolean | `false` | Enable swipeable tabs. Uses `responsiveThreshold`. Wraps the panels in a carousel. |\n| `responsiveThreshold` | Number | `Infinity` | Maximum viewport width, in pixels, at which swipeable mode starts. Wider viewports stay non-swipeable. |\n\n#### Methods\n\n- `.select()`: Show the panel that belongs to the tab with this id. Panel IDs are matched literally, including numeric IDs and IDs containing dots, colons or brackets, in normal and swipeable modes.\n- `.updateTabIndicator()`: Recalculate the indicator position. Useful if the bar was hidden or resized.\n- `.destroy()`: Destroy the plugin instance and tear down its event handlers.\n\n#### Also documented\n\nThe Tabs section of the [API reference](https://www.expressivecss.com/llm.md) also covers Secondary, Initialization, Properties, Preselecting a tab, Linking to an External Page, Swipeable Tabs, Fixed width, Inline icons.\n\n#### Rules\n\n- `tabs-not-a-tablist`: A tab strip takes no ARIA role here; these are links to in-page sections.\n- `tabs-marks-current`: The active tab is the section you are on; aria-current says so where a class cannot.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
360
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Tabs\nMaterial Design 3 tabs, from the HTML.\n\nComponent ID: `tabs`\n\n[Component documentation](https://www.expressivecss.com/tabs.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/tabs.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/tabs/overview)\n\nExample: Home/Search/Profile are app destinations, so use a navigation bar or rail. Overview/Activity panels within one account page can use tabs; Day/Week/Month as a form value can use native radio inputs.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/tabs/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/tabs/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/tabs/guidelines).\n\nSupport (2026-09-13, `llm.md#tabs`): Primary and secondary tabs; stacked and horizontal icon layouts.\n\nWeb adaptation: Links target panels; use the shipped runtime keyboard contract.\n\nKnown boundary: Native links keep Tab and aria-current; Left, Right, Home and End move focus between them as a shortcut, without the selection model a tablist would announce.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#tabs).\n\n#### Contract\n\nA `<nav class=\"tabs\" aria-label=\"Sections\">` of `<a href=\"#panel\">` is the bar. A `<span>` (or the link text) is the label; a leading `<span class=\"material-symbols\">` is the icon. Nest a `<span class=\"badge\">` in the icon for a count, and put that count in the tab's `aria-label` so a hidden icon does not swallow it. `.active` is the selected tab. There is no `li.tab` required — `ul.tabs > li.tab > a` stays as an alias. `AutoInit()` starts every `.tabs` except those marked `no-autoinit`.\n\nTokens follow the [M3 tabs spec](https://m3.material.io/components/tabs/specs). Primary tabs sit on `surface`, 48dp (64dp with a stacked icon). The label is `title-small` / `on-surface-variant`; selected is `primary`. The indicator is 3dp `primary` with 3dp top corners. The icon is 24dp. A 1dp `outline-variant` divider runs under the bar. Hover is 8%; focus is 10%. Disabled is 38%.\n\n#### Syntax\n\n```html\n<nav class=\"tabs\" aria-label=\"Travel\">\n <a href=\"#flight\">\n <span class=\"material-symbols\" aria-hidden=\"true\">flight</span>\n <span>Flight</span>\n </a>\n <a class=\"active\" aria-current=\"page\" href=\"#luggage\" aria-label=\"Luggage, 3 unread\">\n <span class=\"material-symbols\" aria-hidden=\"true\">luggage<span class=\"badge\">3</span></span>\n <span>Luggage</span>\n </a>\n <a href=\"#explore\">\n <span class=\"material-symbols\" aria-hidden=\"true\">explore</span>\n <span>Explore</span>\n </a>\n</nav>\n<div id=\"flight\">Flight</div>\n<div id=\"luggage\">Luggage</div>\n<div id=\"explore\">Explore</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `duration` | Number | `null` | Indicator transition duration in milliseconds, on an ease-out curve. The default, `null`, moves the indicator on the default spatial spring (`--md-comp-tabs-indicator-easing` and `--md-comp-tabs-indicator-duration`), which follows `motion=\"expressive\"` and stops under reduced motion. |\n| `onShow` | Function | `null` | Called when a new tab panel is shown. Receives the panel element. |\n| `swipeable` | Boolean | `false` | Enable swipeable tabs. Uses `responsiveThreshold`. Moves the panels into a scroll-snap track. |\n| `responsiveThreshold` | Number | `Infinity` | Maximum viewport width, in pixels, at which swipeable mode starts. Wider viewports stay non-swipeable. |\n\n#### Methods\n\n- `.select()`: Show the panel that belongs to the tab with this id. Panel IDs are matched literally, including numeric IDs and IDs containing dots, colons or brackets, in normal and swipeable modes.\n- `.updateTabIndicator()`: Recalculate the indicator position. Useful if the bar was hidden or resized.\n- `.destroy()`: Destroy the plugin instance and tear down its event handlers.\n\n#### Also documented\n\nThe Tabs section of the [API reference](https://www.expressivecss.com/llm.md) also covers Secondary, Initialization, Properties, Preselecting a tab, Linking to an External Page, Swipeable tabs, Fixed width, Inline icons.\n\n#### Rules\n\n- `tabs-role-is-tablist`: A tab strip takes no role, or role=\"tablist\" for the tabs pattern. Any other role promises a keyboard model it does not have.\n- `tabs-tab-needs-tablist`: role=\"tab\" belongs in a strip marked role=\"tablist\". Link tabs take no role.\n- `tablist-on-a-div`: Write a tablist on a <div>. A tablist is not navigation, so <nav> stays for link tabs.\n- `tablist-has-a-name`: A tablist needs a name: aria-label or aria-labelledby.\n- `tablist-tabs-are-tabs`: In a tablist every tab takes role=\"tab\".\n- `tablist-tab-names-panel`: A tab names the panel it shows in aria-controls.\n- `tablist-no-current`: A tablist marks its tab with aria-selected, which the component keeps; aria-current belongs to link tabs.\n- `tabs-marks-current`: The active tab is the section you are on; aria-current says so where a class cannot.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
277
361
  },
278
362
  {
279
363
  "file": "text-fields.md",
280
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Text fields\nMaterial Design 3 text fields, from the HTML.\n\nComponent ID: `text-fields`\n\n[Component documentation](https://www.expressivecss.com/text-inputs.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/text-inputs.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/text-fields/overview)\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: overview-specs-guidelines-accessibility-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/text-fields/overview).\n\nRendered prose reviewed; collapsed token tables, image-only measurements, runtime and full parity unverified. See the design priority component review for requirement-level findings.\n\n[Specification link](https://m3.material.io/components/text-fields/specs); full specs unreviewed.\n\nSupport (2026-10-06, `llm.md#text-inputs`): Filled and outlined fields, supporting text, error states, trailing icon buttons, a password visibility toggle and one-time code cells.\n\nWeb adaptation: Native input/textarea with explicit label and description relationships.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#text-fields).\n\n#### Contract\n\nA `.field` is the container. The `<label>` after the control is the floating label, so it must carry `for` — it sits after the control and cannot wrap it. An icon names its side with `prefix` or `suffix` and is `aria-hidden`, because the ligature is real text. `<small>` is supporting text: give it an `id` and point the control at it with `aria-describedby`, or it is never read out with the field. The default is the M3 filled field; add `outlined` (or `border`) for the outlined variant.\n\n`.input-field` is **not** the container and never was — the only rule matching it in the sheet is `.chips.input-field`.\n\nTokens follow the [M3 text field spec](https://m3.material.io/components/text-fields/specs). Height is 56dp. Filled is a `surface-variant` well with 4dp top corners and a 1dp / 2dp bottom indicator. Outlined is a 1dp / 2dp `outline` at 4dp. The label is `body-large` at rest and `body-small` floated. Input text is `body-large` / `on-surface`. Icons are 24dp, 12dp from the edge.\n\n#### Syntax\n\n```html\n<div class=\"field\">\n <input id=\"first_name\" type=\"text\" placeholder=\" \" aria-describedby=\"first_name_help\">\n <label for=\"first_name\">First name</label>\n <small id=\"first_name_help\">Supporting text</small>\n</div>\n\n<div class=\"field outlined\">\n <input id=\"last_name\" type=\"text\" placeholder=\" \">\n <label for=\"last_name\">Last name</label>\n</div>\n\n<div class=\"field\">\n <span class=\"material-symbols prefix\" aria-hidden=\"true\">place</span>\n <span class=\"material-symbols suffix\" aria-hidden=\"true\">gps_fixed</span>\n <input id=\"loc\" type=\"text\" placeholder=\" \">\n <label for=\"loc\">Location</label>\n</div>\n```\n\n#### Also documented\n\nThe Text Inputs section of the [API reference](https://www.expressivecss.com/llm.md) also covers Input types, Inline, Textarea, Trailing button, Password visibility, One-time code, File input, Character counter.\n\n#### Rules\n\n- `field-container-class`: The field container is `.field`. `.input-field` matches nothing in the sheet - the only `.input-field` rule is `.chips.input-field`.\n- `field-icon-not-i`: <i> means idiomatic text, not icon. Use <span class=\"material-symbols\">.\n- `field-icon-hidden`: The ligature is read aloud. A field icon is decoration; the label names the control.\n- `field-icon-side`: A field icon must name its side with .prefix or .suffix. The positional fallback counts elements of the same *type*, which only ever worked for <i>.\n- `field-label-for`: A label that does not wrap its control has to name it. The floating label sits *after* the control and cannot wrap it, so it always needs `for`; a label that does wrap its control (the file input trigger) does not.\n- `field-control-id`: Without an id the label has nothing to point at.\n- `field-supporting-text-id`: Supporting and error text has to be referenceable to be announced with the control.\n- `field-supporting-text-linked`: A field with supporting text must point at it with aria-describedby, or the text is never read out with the control.\n- `password-toggle-reports-its-state`: Give a .password-toggle aria-pressed=\"false\". The behavior flips it, and its name stays \"Show password\", so the pressed state is how a screen reader user hears that the password is visible.\n- `otp-autocompletes`: Set autocomplete=\"one-time-code\" on a one-time code input so the browser can offer the code from a message.\n- `otp-is-not-a-number`: A code is not a quantity: type=\"number\" drops leading zeros and adds a spinner. Use type=\"text\" with inputmode=\"numeric\".\n- `file-input-not-nested`: A control inside a control is not valid HTML and gives two overlapping hit targets. Use a <label> styled as a button.\n- `file-path-readonly`: The path field only ever displays what the file input holds; leaving it writable offers an edit that is discarded.\n- `counter-is-announced`: The count changes as the user types, so it has to be a live region or the remaining characters are never announced.\n"
364
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Text fields\nMaterial Design 3 text fields, from the HTML.\n\nComponent ID: `text-fields`\n\n[Component documentation](https://www.expressivecss.com/text-inputs.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/text-inputs.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/text-fields/overview)\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: overview-specs-guidelines-accessibility-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/text-fields/overview).\n\nRendered prose reviewed; collapsed token tables, image-only measurements, runtime and full parity unverified. See the design priority component review for requirement-level findings.\n\n[Specification link](https://m3.material.io/components/text-fields/specs); full specs unreviewed.\n\nSupport (2026-10-06, `llm.md#text-inputs`): Filled and outlined fields, supporting text, error states, trailing icon buttons, a password visibility toggle and one-time code cells.\n\nWeb adaptation: Native input/textarea with explicit label and description relationships.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#text-fields).\n\n#### Contract\n\nA `.field` is the container. The `<label>` after the control is the floating label, so it must carry `for` — it sits after the control and cannot wrap it. An icon names its side with `prefix` or `suffix` and is `aria-hidden`, because the ligature is real text. `<small>` is supporting text: give it an `id` and point the control at it with `aria-describedby`, or it is never read out with the field. The default is the M3 filled field; add `outlined` (or `border`) for the outlined variant.\n\n`.input-field` is **not** the container and never was — the only rule matching it in the sheet is `.chips.input-field`.\n\nTokens follow the [M3 text field spec](https://m3.material.io/components/text-fields/specs). Height is 56dp. Filled is a `surface-container-highest` well with 4dp top corners and a 1dp / 2dp bottom indicator. Outlined is a 1dp / 2dp `outline` at 4dp. The label is `body-large` at rest and `body-small` floated. Input text is `body-large` / `on-surface`. Icons are 24dp, 12dp from the edge.\n\n#### Syntax\n\n```html\n<div class=\"field\">\n <input id=\"first_name\" type=\"text\" placeholder=\" \" aria-describedby=\"first_name_help\">\n <label for=\"first_name\">First name</label>\n <small id=\"first_name_help\">Supporting text</small>\n</div>\n\n<div class=\"field outlined\">\n <input id=\"last_name\" type=\"text\" placeholder=\" \">\n <label for=\"last_name\">Last name</label>\n</div>\n\n<div class=\"field\">\n <span class=\"material-symbols prefix\" aria-hidden=\"true\">place</span>\n <span class=\"material-symbols suffix\" aria-hidden=\"true\">gps_fixed</span>\n <input id=\"loc\" type=\"text\" placeholder=\" \">\n <label for=\"loc\">Location</label>\n</div>\n```\n\n#### Also documented\n\nThe Text Inputs section of the [API reference](https://www.expressivecss.com/llm.md) also covers Input types, Inline, Textarea, Trailing button, Password visibility, Number stepper, One-time code, File input, Color, Character counter.\n\n#### Rules\n\n- `field-container-class`: The field container is `.field`. `.input-field` matches nothing in the sheet - the only `.input-field` rule is `.chips.input-field`.\n- `field-icon-not-i`: <i> means idiomatic text, not icon. Use <span class=\"material-symbols\">.\n- `field-icon-hidden`: The ligature is read aloud. A field icon is decoration; the label names the control.\n- `field-icon-side`: A field icon must name its side with .prefix or .suffix. Fields have no positional fallback.\n- `field-label-for`: A label that does not wrap its control has to name it. The floating label sits *after* the control and cannot wrap it, so it always needs `for`; a label that does wrap its control (the file input trigger) does not.\n- `field-control-id`: Without an id the label has nothing to point at.\n- `field-supporting-text-id`: Supporting and error text has to be referenceable to be announced with the control.\n- `field-supporting-text-linked`: A field with supporting text must point at it with aria-describedby, or the text is never read out with the control.\n- `password-toggle-reports-its-state`: Give a .password-toggle aria-pressed=\"false\". The behavior flips it, and its name stays \"Show password\", so the pressed state is how a screen reader user hears that the password is visible.\n- `otp-autocompletes`: Set autocomplete=\"one-time-code\" on a one-time code input so the browser can offer the code from a message.\n- `otp-is-not-a-number`: A code is not a quantity: type=\"number\" drops leading zeros and adds a spinner. Use type=\"text\" with inputmode=\"numeric\".\n- `file-input-not-nested`: A control inside a control is not valid HTML and gives two overlapping hit targets. Use a <label> styled as a button.\n- `file-path-readonly`: The path field only ever displays what the file input holds; leaving it writable offers an edit that is discarded.\n- `counter-is-announced`: The count changes as the user types, so it has to be a live region or the remaining characters are never announced.\n"
281
365
  },
282
366
  {
283
367
  "file": "time-picker.md",
284
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Time picker\nPick a time from a clock face, in 12-hour or 24-hour form.\n\nComponent ID: `time-picker`\n\n[Component documentation](https://www.expressivecss.com/timepicker.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/timepicker.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/time-pickers/overview)\n\nExample: An appointment time can use a labeled native time input when platform entry is enough. Do not choose this picker expecting a popup: its clock stays inline.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/time-pickers/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/time-pickers/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/time-pickers/guidelines).\n\nSupport (2026-09-18, `llm.md#time-picker`): Inline clock and digital input; 12-hour and 24-hour formats.\n\nWeb adaptation: Default inline presentation with optional docked display plugin; no open()/close() methods or modal implementation.\n\nKnown boundary: Modal picker parity is not provided by the documented inline API.\n\nKnown boundary: Named digital inputs, AM/PM state and input-event synchronization have browser coverage. Modal and clock-dial keyboard behavior remain incomplete.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#time-picker).\n\n#### Contract\n\nAdd `time-picker` to a text input. `AutoInit()` starts every `.time-picker` except those marked `no-autoinit`.\n\nThe clock is inline, not a modal. It is appended to the input’s parent and stays visible. There is no `openByDefault` flag. `open()` and `close()` are not provided.\n\n#### Syntax\n\n```html\n<div class=\"field\">\n <input type=\"text\" class=\"time-picker\" id=\"lunchtime\">\n <label for=\"lunchtime\">Lunchtime</label>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `duration` | Number | `350` | Transition between the hours and minutes views, in milliseconds. |\n| `container` | Element or String | `null` | Element or selector to render the clock into. When omitted, it is appended to the input’s parent. |\n| `showClearBtn` | Boolean | `false` | Show a Clear button in the footer. |\n| `autoSubmit` | Boolean | `true` | If true, choosing a minute writes the input. If false, Ok and Cancel buttons are added. |\n| `defaultTime` | String | `'now'` | Initial time. `'now'` or a `'HH:MM'` string (optionally with `AM`/`PM`). |\n| `fromNow` | Number | `0` | Millisecond offset added to `'now'`. |\n| `i18n` | Object | See below | Labels for Cancel, Clear, Ok, Hours, and Minutes. |\n| `twelveHour` | Boolean | `true` | If true, use a 12-hour clock with AM/PM. If false, use 24-hour hours on two rings. |\n| `vibrate` | Boolean | `true` | Vibrate the device when the clock hand changes value. |\n| `onSelect` | Function | `null` | Called when a time is chosen on the dial. Receives `(hour, minute)`. |\n| `onInputInteraction` | Function | `null` | Called when the input is clicked or confirmed with Enter. |\n| `onDone` | Function | `null` | Called once when Ok is activated by click, Enter or Space. The button is created when `autoSubmit` is false. |\n| `onCancel` | Function | `null` | Called once when Cancel is activated by click, Enter or Space. The button is created when `autoSubmit` is false. |\n| `displayPlugin` | String | `null` | Set to `'docked'` for a click-to-open popover. |\n| `displayPluginOptions` | Object | `null` | Options for the docked plugin: `margin`, `transition`, `duration`, `align`. |\n\n#### Methods\n\n- `.showView()`: Show the hours or minutes face.\n- `.done()`: Write the current hours and minutes to the input. Pass a truthy argument to clear the input instead.\n- `.clear()`: Clear the input. Same as `done(true)`.\n- `.destroy()`: Destroy the plugin instance, remove the clock, and tear down its event handlers. Pending automatic submission and clock updates are cancelled, so the destroyed instance cannot write to the input.\n- `.open(); / .close()`: These methods are not provided. The clock defaults to visible inline presentation; `displayPlugin: 'docked'` enables docking. No modal implementation is available.\n\n#### Also documented\n\nThe Time Picker section of the [API reference](https://www.expressivecss.com/llm.md) also covers Initialization, Display, Internationalization, 12-hour and 24-hour, Properties.\n\n#### Rules\n\nNo component-specific `semantics.json` rules apply to this guide.\n"
368
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Time picker\nPick a time from a clock face, in 12-hour or 24-hour form.\n\nComponent ID: `time-picker`\n\n[Component documentation](https://www.expressivecss.com/timepicker.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/timepicker.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/time-pickers/overview)\n\nExample: An appointment time can use a labeled native time input when platform entry is enough. Do not choose this picker expecting a popup: its clock stays inline.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: requirements-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/time-pickers/overview).\n\nRequirements and boundaries: design reference inputs-material-review. Full visual parity and spoken output unverified.\n\n[Specification link](https://m3.material.io/components/time-pickers/specs); full specs unreviewed.\n\n[Google guidelines](https://m3.material.io/components/time-pickers/guidelines).\n\nSupport (2026-09-18, `llm.md#time-picker`): Inline clock and digital input; 12-hour and 24-hour formats, with an optional modal dialog.\n\nWeb adaptation: Default inline presentation with optional docked and modal display plugins; displayPlugin: 'modal' opens it in a dialog from the input. No open()/close() methods.\n\nKnown boundary: Modal picker parity is not provided by the documented inline API.\n\nKnown boundary: Named digital inputs as spin buttons, AM/PM state, input-event synchronization and the modal dialog have browser coverage. The dial itself takes pointer input only.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#time-picker).\n\n#### Contract\n\nAdd `time-picker` to a text input. `AutoInit()` starts every `.time-picker` except those marked `no-autoinit`.\n\nThe clock is inline, not a modal. It is appended to the input’s parent and stays visible. There is no `openByDefault` flag. `open()` and `close()` are not provided.\n\n#### Syntax\n\n```html\n<div class=\"field\">\n <input type=\"text\" class=\"time-picker\" id=\"lunchtime\">\n <label for=\"lunchtime\">Lunchtime</label>\n</div>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `duration` | Number | `350` | Transition between the hours and minutes views, in milliseconds. |\n| `container` | Element or String | `null` | Element or selector to render the clock into. When omitted, it is appended to the input’s parent. |\n| `showClearBtn` | Boolean | `false` | Show a Clear button in the footer. |\n| `autoSubmit` | Boolean | `true` | If true, choosing a minute writes the input. If false, Ok and Cancel buttons are added. |\n| `defaultTime` | String | `'now'` | Initial time. `'now'` or a `'HH:MM'` or `'HH:MM:SS'` string (optionally with `AM`/`PM`). |\n| `fromNow` | Number | `0` | Millisecond offset added to `'now'`. |\n| `i18n` | Object | See below | Labels for Cancel, Clear, Ok, Hours, Minutes and Seconds. |\n| `twelveHour` | Boolean | `true`, or the language's | If true, use a 12-hour clock with AM/PM. If false, use 24-hour hours on two rings. With a `lang` on or around the input, defaults to the clock that language uses. |\n| `showSeconds` | Boolean | `false` | Add a seconds field after the minutes and write `HH:MM:SS`. The dial still sets hours and minutes only. |\n| `minTime` | String | `null` | The earliest time a person can choose, as 24-hour `HH:MM` or `HH:MM:SS`. Without `showSeconds` it rounds up to a whole minute. |\n| `maxTime` | String | `null` | The latest time a person can choose, as 24-hour `HH:MM` or `HH:MM:SS`. Without `showSeconds` it rounds down to a whole minute. |\n| `vibrate` | Boolean | `true` | Vibrate the device when the clock hand changes value. |\n| `onSelect` | Function | `null` | Called when a time is chosen on the dial. Receives `(hour, minute)`. |\n| `onInputInteraction` | Function | `null` | Called when the input is clicked or confirmed with Enter. |\n| `onDone` | Function | `null` | Called once when Ok is activated by click, Enter or Space. The button is created when `autoSubmit` is false. |\n| `onCancel` | Function | `null` | Called once when Cancel is activated by click, Enter or Space. The button is created when `autoSubmit` is false. |\n| `displayPlugin` | String | `null` | Set to `'docked'` for a click-to-open popover, or `'modal'` for a dialog with Cancel and Ok. |\n| `displayPluginOptions` | Object | `null` | Options for the docked plugin: `margin`, `transition`, `duration`, `align`. The modal plugin takes none. |\n\n#### Methods\n\n- `.showView()`: Show the hours or minutes face.\n- `.done()`: Write the current time to the input, with seconds when `showSeconds` is on. Pass a truthy argument to clear the input instead.\n- `.clear()`: Clear the input. Same as `done(true)`.\n- `.destroy()`: Destroy the plugin instance, remove the clock, and tear down its event handlers. Pending automatic submission and clock updates are cancelled, so the destroyed instance cannot write to the input.\n- `.open(); / .close()`: These methods are not provided. The clock defaults to visible inline presentation; `displayPlugin: 'docked'` enables docking and `displayPlugin: 'modal'` opens it in a dialog from the input.\n\n#### Also documented\n\nThe Time Picker section of the [API reference](https://www.expressivecss.com/llm.md) also covers Initialization, Display, Internationalization, 12-hour and 24-hour, Seconds, Time range, Properties.\n\n#### Rules\n\nNo component-specific `semantics.json` rules apply to this guide.\n"
285
369
  },
286
370
  {
287
371
  "file": "timeline.md",
288
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Timeline\nEvents in order, each with a time, a title and details, joined by a line.\n\nComponent ID: `timeline`\n\n[Component documentation](https://www.expressivecss.com/timeline.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/timeline.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Cart, Shipping and Payment in a checkout are steps the user still has to finish, so they belong in a stepper. A timeline would present them as things that already happened.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#timeline`): Ordered events with times, headings and text, dots joined by a line and a leading icon variant.\n\nWeb adaptation: An <ol> of events with <time datetime>; the dots and line are decoration.\n\nKnown boundary: Marker and line geometry in LTR and RTL and the icon variant are checked in a browser; long feeds, enlarged text and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#timeline).\n\n#### Contract\n\nAn `<ol class=\"timeline\">` lists events in the order the page chooses, newest or oldest first. Each `<li>` is an event: an optional `<time>` with a `datetime`, a heading and supporting `<p>` text. A dot marks each event and a line joins it to the next. A leading icon as the first child replaces the dot with a tonal circle; hide it from assistive technology. CSS only; there is no JavaScript.\n\n#### Syntax\n\n```html\n<ol class=\"timeline\">\n <li>\n <time datetime=\"2026-10-06T09:12\">9:12 AM</time>\n <h3>Order placed</h3>\n <p>Two items, paid by card.</p>\n </li>\n <li>\n <time datetime=\"2026-10-06T11:40\">11:40 AM</time>\n <h3>Shipped</h3>\n <p>Tracking number sent by email.</p>\n </li>\n</ol>\n```\n\n#### Also documented\n\nThe Timeline section of the [API reference](https://www.expressivecss.com/llm.md) also covers Leading icon, Tokens.\n\n#### Rules\n\n- `timeline-is-an-ordered-list`: Write a timeline as <ol class=\"timeline\">. The events have an order, and an ordered list says so to assistive technology.\n- `timeline-time-is-machine-readable`: Give each <time> a datetime value. \"9:12 AM\" or \"Yesterday\" has no date a script, a translation tool or a reader in another time zone can use.\n"
372
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Timeline\nEvents in order, each with a time, a title and details, joined by a line.\n\nComponent ID: `timeline`\n\n[Component documentation](https://www.expressivecss.com/timeline.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/timeline.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Cart, Shipping and Payment in a checkout are steps the user still has to finish, so they belong in a stepper. A timeline would present them as things that already happened.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#timeline`): Ordered events with times, headings and text, dots joined by a line and a leading icon variant.\n\nWeb adaptation: An <ol> of events with <time datetime>; the dots and line are decoration.\n\nKnown boundary: Marker and line geometry in LTR and RTL and the icon variant are checked in a browser; long feeds, enlarged text and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#timeline).\n\n#### Contract\n\nAn `<ol class=\"timeline\">` lists events in the order the page chooses, newest or oldest first. Each `<li>` is an event: an optional `<time>` with a `datetime`, a heading and supporting `<p>` text. A dot marks each event and a line joins it to the next. A leading icon as the first child replaces the dot with a tonal circle; hide it from assistive technology. CSS only; there is no JavaScript.\n\n#### Syntax\n\n```html\n<ol class=\"timeline\">\n <li>\n <time datetime=\"2026-10-06T09:12\">9:12 AM</time>\n <h3>Order placed</h3>\n <p>Two items, paid by card.</p>\n </li>\n <li>\n <time datetime=\"2026-10-06T11:40\">11:40 AM</time>\n <h3>Shipped</h3>\n <p>Tracking number sent by email.</p>\n </li>\n</ol>\n```\n\n#### Also documented\n\nThe Timeline section of the [API reference](https://www.expressivecss.com/llm.md) also covers Leading icon, Tokens.\n\n#### Rules\n\n- `timeline-is-an-ordered-list`: Write a timeline as <ol class=\"timeline\">. The events have an order, and an ordered list says so to assistive technology.\n- `timeline-time-is-machine-readable`: Give each <time> a datetime value. \"9:12 AM\" or \"Yesterday\" has no date a script, a translation tool or a reader in another time zone can use.\n"
289
373
  },
290
374
  {
291
375
  "file": "toolbars.md",
292
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Toolbars\nFrequently used actions for the current page.\n\nComponent ID: `toolbars`\n\n[Component documentation](https://www.expressivecss.com/toolbars.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/toolbars.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/toolbars/overview)\n\nExample: Home/Search/Profile belong in navigation. A toolbar near a document can hold edit, share, and formatting actions.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: overview-specs-guidelines-accessibility-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/toolbars/overview).\n\nRendered prose reviewed; collapsed token tables, image-only measurements, runtime and full parity unverified. See the design priority component review for requirement-level findings.\n\n[Specification link](https://m3.material.io/components/toolbars/specs); full specs unreviewed.\n\nSupport (2026-09-07, `llm.md#toolbars`): Floating/docked, standard/vibrant, vertical floating, and companion FAB.\n\nWeb adaptation: CSS-only commands; no automatic composite toolbar keyboard model.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#toolbars).\n\n#### Contract\n\nMaterial Design 3 toolbars, from the HTML.\n\nA `<div class=\"toolbar\">` is the bar — not a `<nav>`, because a toolbar holds commands rather than destinations. Direct `<button>` or `<a>` children are the actions. A `<span class=\"material-symbols\">` is the icon; wrap a label in its own `<span>`. An action with an icon and no label needs an `aria-label`. `.active` marks the selected action. They are CSS only. There is no JavaScript component and nothing to AutoInit.\n\nTokens follow the [M3 toolbar spec](https://m3.material.io/components/toolbars/specs). Actions are 48dp targets with a 24dp icon, transparent at rest, `on-surface-variant`, 4dp apart.\n\n#### Syntax\n\n```html\n<div class=\"toolbar\">\n <button type=\"button\" class=\"circle\" aria-label=\"Undo\">\n <span class=\"material-symbols\" aria-hidden=\"true\">undo</span>\n </button>\n <button type=\"button\" class=\"circle active\" aria-label=\"Bold\">\n <span class=\"material-symbols\" aria-hidden=\"true\">format_bold</span>\n </button>\n <button type=\"button\" class=\"circle\" aria-label=\"Italic\">\n <span class=\"material-symbols\" aria-hidden=\"true\">format_italic</span>\n </button>\n</div>\n```\n\n#### Also documented\n\nThe Toolbars section of the [API reference](https://www.expressivecss.com/llm.md) also covers Variants, Docked, Pairing with a FAB, Fixed.\n\n#### Rules\n\n- `toolbar-not-nav`: A toolbar holds commands (Bold, Italic), not navigation, so it is not a <nav> landmark.\n- `toolbar-not-a-composite-widget`: A toolbar takes no composite role; its buttons are reached with Tab, not arrow keys.\n"
376
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Toolbars\nFrequently used actions for the current page.\n\nComponent ID: `toolbars`\n\n[Component documentation](https://www.expressivecss.com/toolbars.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/toolbars.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `css-only`. [Google guidance](https://m3.material.io/components/toolbars/overview)\n\nExample: Home/Search/Profile belong in navigation. A toolbar near a document can hold edit, share, and formatting actions.\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: overview-specs-guidelines-accessibility-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/toolbars/overview).\n\nRendered prose reviewed; collapsed token tables, image-only measurements, runtime and full parity unverified. See the design priority component review for requirement-level findings.\n\n[Specification link](https://m3.material.io/components/toolbars/specs); full specs unreviewed.\n\nSupport (2026-09-07, `llm.md#toolbars`): Floating/docked, standard/vibrant, vertical floating, and companion FAB.\n\nWeb adaptation: CSS-only commands; no automatic composite toolbar keyboard model.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#toolbars).\n\n#### Contract\n\nMaterial Design 3 toolbars, from the HTML.\n\nA `<div class=\"toolbar\">` is the bar — not a `<nav>`, because a toolbar holds commands rather than destinations. Direct `<button>` or `<a>` children are the actions. A `<span class=\"material-symbols\">` is the icon; wrap a label in its own `<span>`. An action with an icon and no label needs an `aria-label`. `.active` marks the selected action. They are CSS only. There is no JavaScript component and nothing to AutoInit.\n\nTokens follow the [M3 toolbar spec](https://m3.material.io/components/toolbars/specs). Actions are 48dp targets with a 24dp icon, transparent at rest, `on-surface-variant`, 4dp apart.\n\n#### Syntax\n\n```html\n<div class=\"toolbar\">\n <button type=\"button\" class=\"circle\" aria-label=\"Undo\">\n <span class=\"material-symbols\" aria-hidden=\"true\">undo</span>\n </button>\n <button type=\"button\" class=\"circle active\" aria-label=\"Bold\">\n <span class=\"material-symbols\" aria-hidden=\"true\">format_bold</span>\n </button>\n <button type=\"button\" class=\"circle\" aria-label=\"Italic\">\n <span class=\"material-symbols\" aria-hidden=\"true\">format_italic</span>\n </button>\n</div>\n```\n\n#### Also documented\n\nThe Toolbars section of the [API reference](https://www.expressivecss.com/llm.md) also covers Variants, Docked, Pairing with a FAB, Fixed.\n\n#### Rules\n\n- `toolbar-not-nav`: A toolbar holds commands (Bold, Italic), not navigation, so it is not a <nav> landmark.\n- `toolbar-not-a-composite-widget`: A toolbar takes no composite role; its buttons are reached with Tab, not arrow keys.\n"
293
377
  },
294
378
  {
295
379
  "file": "tooltips.md",
296
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Tooltips\nMaterial Design 3 tooltips, from the HTML.\n\nComponent ID: `tooltips`\n\n[Component documentation](https://www.expressivecss.com/tooltips.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/tooltips.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/tooltips/overview)\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: overview-specs-guidelines-accessibility-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/tooltips/overview).\n\nRendered prose reviewed; collapsed token tables, image-only measurements, runtime and full parity unverified. See the design priority component review for requirement-level findings.\n\n[Specification link](https://m3.material.io/components/tooltips/specs); full specs unreviewed.\n\nSupport (2026-09-07, `llm.md#tooltips`): Plain and rich tooltips; CSS child and runtime-generated forms.\n\nWeb adaptation: The selected form determines ownership; CSS-only hover/focus is not runtime initialization.\n\nKnown boundary: For hover/focus tooltips, verify dismissibility, hoverability, and persistence under WCAG SC 1.4.13, including its exceptions. Plain CSS bubbles disable pointer events and have no own Escape handler; aria-describedby alone does not verify these behaviors. Read the [focused web checks](../expressivecss-accessibility/references/web-checks.md#content-on-hover-or-focus).\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#tooltips).\n\n#### Contract\n\nA child `.tooltip` is the bubble. No `tooltipped` class is required. They are CSS only on hover and keyboard focus. Helpers are only for placement and the rich variant: `top` (the default), `bottom`, `left`, `right`, `rich` / `max`.\n\nTokens follow the [M3 tooltip spec](https://m3.material.io/components/tooltips/specs). A plain tooltip is `inverse-surface` / `inverse-on-surface`, 4dp corners, `body-small`, 24dp minimum, 4/8dp padding, 200dp max, no elevation and no caret. It sits 4dp from the activator. M3 prefers above.\n\nInside a `<button>` the bubble has to be a `<span>` — a `<div>` is not phrasing content and the parser will hoist it. Icon-only buttons still need `.circle` so the span is not treated as a label.\n\n#### Syntax\n\n```html\n<button type=\"button\" class=\"circle\" aria-label=\"Add\" aria-describedby=\"tip-add-to-album\">\n <span class=\"material-symbols\" aria-hidden=\"true\">add</span>\n <span class=\"tooltip\" id=\"tip-add-to-album\">Add to album</span>\n</button>\n\n<button type=\"button\" class=\"circle\" aria-label=\"Below\" aria-describedby=\"tip-below\">\n <span class=\"material-symbols\" aria-hidden=\"true\">arrow_downward</span>\n <span class=\"tooltip bottom\" id=\"tip-below\">Below</span>\n</button>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `exitDelay` | Number | `200` | Delay before the tooltip disappears, in milliseconds. |\n| `enterDelay` | Number | `0` | Delay before the tooltip appears, in milliseconds. |\n| `tooltipId` | String | — | Id of an element used as the tooltip body. Set by `data-tooltip-id`. Marks the bubble `rich`. |\n| `text` | String | `''` | Plain-text content. Set by `data-tooltip`. Ignored when `tooltipId` is set. |\n| `margin` | Number | `4` | Distance from the activator, in pixels, not counting `transitionMovement`. M3 is 4dp. |\n| `inDuration` | Number | `250` | Enter transition duration, in milliseconds. |\n| `opacity` | Number | `1` | Opacity of the tooltip when shown. |\n| `outDuration` | Number | `200` | Exit transition duration, in milliseconds. |\n| `position` | String | `'bottom'` | Direction: `'top'`, `'right'`, `'bottom'`, or `'left'`. Set by `data-position`. The CSS-only default is above. |\n| `transitionMovement` | Number | `10` | How far the tooltip moves during its transition, in pixels. |\n\n#### Methods\n\n- `.open()`: `open()` and `open(true)` show the tooltip after its enter delay without requiring hover or focus. `open(false)` uses the automatic behavior and requires hover or focus when the delay ends.\n- `.close()`: Hide the tooltip.\n- `.destroy()`: Destroy the plugin instance and remove its generated tooltip and event handlers. Rich content returns to its authored position and presentation state. Existing description associations are preserved, so the same content can be initialized again.\n\n#### Also documented\n\nThe Tooltips section of the [API reference](https://www.expressivecss.com/llm.md) also covers Rich, JavaScript, Initialization, Properties.\n\n#### Rules\n\n- `css-tooltip-is-described-by`: A .tooltip inside its control is swallowed: with an aria-label present the label wins and the tooltip is never announced, and without one the two run together. Give the tooltip an id and point aria-describedby at it.\n"
380
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Tooltips\nMaterial Design 3 tooltips, from the HTML.\n\nComponent ID: `tooltips`\n\n[Component documentation](https://www.expressivecss.com/tooltips.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/tooltips.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. [Google guidance](https://m3.material.io/components/tooltips/overview)\n\n#### Material mapping\n\nRelationship: component.\n\nUpstream: overview-specs-guidelines-accessibility-prose-reviewed (2026-09-13); [evidence](https://m3.material.io/components/tooltips/overview).\n\nRendered prose reviewed; collapsed token tables, image-only measurements, runtime and full parity unverified. See the design priority component review for requirement-level findings.\n\n[Specification link](https://m3.material.io/components/tooltips/specs); full specs unreviewed.\n\nSupport (2026-09-07, `llm.md#tooltips`): Plain and rich tooltips; CSS child and runtime-generated forms.\n\nWeb adaptation: The selected form determines ownership; CSS-only hover/focus is not runtime initialization.\n\nKnown boundary: For hover/focus tooltips, verify dismissibility, hoverability, and persistence under WCAG SC 1.4.13, including its exceptions. Plain CSS bubbles disable pointer events and have no own Escape handler; aria-describedby alone does not verify these behaviors. Read the [focused web checks](../expressivecss-accessibility/references/web-checks.md#content-on-hover-or-focus).\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#tooltips).\n\n#### Contract\n\nA child `.tooltip` is the bubble. No `tooltipped` class is required. They are CSS only on hover and keyboard focus. Helpers are only for placement and the rich variant: `top` (the default), `bottom`, `left`, `right`, `rich`.\n\nTokens follow the [M3 tooltip spec](https://m3.material.io/components/tooltips/specs). A plain tooltip is `inverse-surface` / `inverse-on-surface`, 4dp corners, `body-small`, 24dp minimum, 4/8dp padding, 200dp max, no elevation and no caret. It sits 4dp from the activator. M3 prefers above.\n\nInside a `<button>` the bubble has to be a `<span>` — a `<div>` is not phrasing content and the parser will hoist it. Icon-only buttons still need `.circle` so the span is not treated as a label.\n\n#### Syntax\n\n```html\n<button type=\"button\" class=\"circle\" aria-label=\"Add\" aria-describedby=\"tip-add-to-album\">\n <span class=\"material-symbols\" aria-hidden=\"true\">add</span>\n <span class=\"tooltip\" id=\"tip-add-to-album\">Add to album</span>\n</button>\n\n<button type=\"button\" class=\"circle\" aria-label=\"Below\" aria-describedby=\"tip-below\">\n <span class=\"material-symbols\" aria-hidden=\"true\">arrow_downward</span>\n <span class=\"tooltip bottom\" id=\"tip-below\">Below</span>\n</button>\n```\n\n#### Options\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| `exitDelay` | Number | `200` | Delay before the tooltip disappears, in milliseconds. |\n| `enterDelay` | Number | `0` | Delay before the tooltip appears, in milliseconds. Skipped while a tooltip is closing and for 300 ms after its `exitDelay` ends. |\n| `tooltipId` | String | — | Id of an element used as the tooltip body. Set by `data-tooltip-id`. Marks the bubble `rich`. |\n| `text` | String | `''` | Plain-text content. Set by `data-tooltip`. Ignored when `tooltipId` is set. |\n| `margin` | Number | `4` | Distance from the activator, in pixels, not counting `transitionMovement`. M3 is 4dp. |\n| `inDuration` | Number | `250` | Enter transition duration, in milliseconds. |\n| `opacity` | Number | `1` | Opacity of the tooltip when shown. |\n| `outDuration` | Number | `200` | Exit transition duration, in milliseconds. |\n| `position` | String | `'bottom'` | Direction: `'top'`, `'right'`, `'bottom'`, or `'left'`. Set by `data-position`. The CSS-only default is above. |\n| `transitionMovement` | Number | `10` | How far the tooltip moves during its transition, in pixels. An anchored tooltip does not move and adds this to the gap. |\n| `openOnClick` | Boolean | `false` | Open on a click instead of hover and focus, at once, without `enterDelay`. A second click, Escape, a click elsewhere or focus moving to another element closes it at once; a click inside the tooltip does not. Keyboard users open it with Enter or Space, so the trigger should be a `<button>`. |\n\n#### Methods\n\n- `.open()`: `open()` and `open(true)` show the tooltip after its enter delay without requiring hover or focus. `open(false)` uses the automatic behavior and requires hover or focus when the delay ends. With `openOnClick`, a click elsewhere also closes a tooltip opened this way.\n- `.close()`: Hide the tooltip.\n- `.destroy()`: Destroy the plugin instance and remove its generated tooltip and event handlers. Rich content returns to its authored position and presentation state. Existing description associations are preserved, so the same content can be initialized again.\n\n#### Also documented\n\nThe Tooltips section of the [API reference](https://www.expressivecss.com/llm.md) also covers Rich, JavaScript, Initialization, Properties.\n\n#### Rules\n\n- `css-tooltip-is-described-by`: A .tooltip inside its control is swallowed: with an aria-label present the label wins and the tooltip is never announced, and without one the two run together. Give the tooltip an id and point aria-describedby at it.\n"
381
+ },
382
+ {
383
+ "file": "tour.md",
384
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Tour\nA card that walks people through an interface one step at a time, beside each element it explains.\n\nComponent ID: `tour`\n\n[Component documentation](https://www.expressivecss.com/tour.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/tour.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Three new features after an update are a tour. One unexplained icon is a tooltip.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-15); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-15, `llm.md#tour`): One step at a time beside its target, a highlight ring, Back, Next and Done, progress text, skipped missing targets, Escape and focus return.\n\nWeb adaptation: A manual native popover placed with CSS anchor positioning; the Tour behavior moves the anchor and focus.\n\nKnown boundary: Placement beside each target, the card name, progress, button visibility, skipped targets, Escape, focus return and cleanup after a scripted close are checked in a browser; screen reader output remains unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#tour).\n\n#### Contract\n\nA `.tour` is a `popover=\"manual\"` element with `role=\"dialog\"`, holding one `<section>` per step. Each section's `data-tour-target` names the id of the element it explains. Add a `.tour-progress` line, a `.tour-actions` row with `[data-tour-back]`, `[data-tour-next]` and `[data-tour-done]` buttons, and a `[data-tour-close]` button; a `[data-tour-start=\"tour-id\"]` button starts it. The `Tour` behavior shows one step at a time below its target (above when there is no room), rings the target with `.tour-target` and scrolls it into view, and skips steps whose target is missing or hidden. Back hides on the first step, Next on the last, and Done shows only on the last. The card is named by the step heading and takes focus. Escape, Done or close end the tour and return focus; the card's `toggle` event reports it. `Expressive.Tour.start(el, index)`, `go(el, index)` and `end(el, completed)` work from script, and the tour fires `expressive:open`, `expressive:change` (`{ step, index }`) and `expressive:close` (`{ completed }`), and `Expressive.Tour.i18n.progress` holds \"Step {current} of {total}\". The root bundle starts the behavior on load; with the modular entry, call `Tour.Init()`.\n\n#### Syntax\n\n```html\n<button type=\"button\" data-tour-start=\"welcome\">Take the tour</button>\n<div id=\"welcome\" class=\"tour\" popover=\"manual\" role=\"dialog\">\n <section data-tour-target=\"search-button\">\n <h2>Search everything</h2>\n <p>Find files, people and settings from here.</p>\n </section>\n <p class=\"tour-progress\"></p>\n <div class=\"tour-actions\">\n <button type=\"button\" class=\"button text\" data-tour-back>Back</button>\n <button type=\"button\" class=\"button filled\" data-tour-next>Next</button>\n <button type=\"button\" class=\"button filled\" data-tour-done>Done</button>\n </div>\n</div>\n```\n\n#### Rules\n\n- `tour-is-a-manual-popover`: Write the .tour with popover=\"manual\". An auto popover closes on any outside click, which ends the tour while the user looks at the target.\n- `tour-is-a-dialog`: Give the .tour role=\"dialog\". The behavior labels it with the current step's heading and moves focus to it, and the dialog role is what makes that name announced.\n- `tour-steps-name-a-target`: Each step <section> needs data-tour-target with the id of the element it explains. A step without one is never shown.\n"
385
+ },
386
+ {
387
+ "file": "transfer-list.md",
388
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Transfer list\nMoves options between an available list and a chosen list, and submits the chosen ones with the form.\n\nComponent ID: `transfer-list`\n\n[Component documentation](https://www.expressivecss.com/transfer-list.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/transfer-list.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `shared-runtime`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Choosing which of forty report columns to export, in the order they should appear, takes a transfer list; choosing two of five toppings takes checkboxes.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-25); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-25, `llm.md#transfer-list`): Two .list groups of labelled checkbox rows with add, add-all, remove and remove-all buttons between them, chosen rows kept in the order they arrived, rows returned to their original slots, the chosen values submitted under data-name in order, form reset, a polite announcement, an expressive:transfer event, and a layout that follows the page direction and stacks below the medium window size.\n\nWeb adaptation: A document-delegated behavior moves the rows and appends the chosen values in a formdata listener, so the checkboxes carry no name. The rows stay native checkboxes rather than a listbox, so Tab and Space work without an arrow-key contract.\n\nKnown boundary: The add, remove and move-all buttons, original slots on the way back, disabled rows, focus, the announcement, the expressive:transfer event, the form data order, disabled fieldsets, nested lists, form reset, right-to-left pages and the stacked narrow layout are checked in a browser; spoken output and forced colors remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#transfer-list).\n\n#### Contract\n\nA `.transfer-list` holds a `.list.transfer-available`, a `.transfer-actions` group of four `type=\"button\"` buttons, and a `.list.transfer-chosen`. Each row is a `<label>` around a checkbox with a `value` and no `name`. Name each list with `aria-label` or `aria-labelledby`, and give the container a `data-name`. The root bundle starts the behavior on load; with the modular entry, call `TransferList.Init()`. Its listeners are on the document, so a list added later needs no call.\n\n`data-transfer=\"add\"` moves the checked rows of the available list to the end of the chosen list, and `remove` moves the checked rows of the chosen list back. `add-all` and `remove-all` move every row. Moved rows arrive unchecked, and a row whose checkbox is disabled stays where it is. The chosen list keeps the order rows arrived in, and a row moved back returns to the place it had before the first move. The rows are native checkboxes, so Tab reaches each one and Space checks it; there is no listbox role or arrow-key model. A button pressed with nothing to move does nothing, and focus stays on the button after a move.\n\nWhen the form builds its data, each row of the chosen list adds its checkbox value under the container's `data-name`, in the chosen list's order. A form reset puts every row back where it was before the first move. A list inside a `<fieldset disabled>` submits nothing, as the fieldset's own fields do, but a chosen row whose own checkbox is disabled still submits.\n\n#### Syntax\n\n```html\n<form>\n <div class=\"transfer-list\" data-name=\"toppings\">\n <ul class=\"list transfer-available\" aria-label=\"Available toppings\">\n <li><label><input type=\"checkbox\" value=\"olives\"> Olives</label></li>\n <li><label><input type=\"checkbox\" value=\"peppers\"> Peppers</label></li>\n </ul>\n <div class=\"transfer-actions\">\n <button type=\"button\" class=\"icon-button\" data-transfer=\"add\" aria-label=\"Add checked toppings\">\n <span class=\"material-symbols\" aria-hidden=\"true\">chevron_right</span>\n </button>\n <button type=\"button\" class=\"icon-button\" data-transfer=\"add-all\" aria-label=\"Add all toppings\">\n <span class=\"material-symbols\" aria-hidden=\"true\">keyboard_double_arrow_right</span>\n </button>\n <button type=\"button\" class=\"icon-button\" data-transfer=\"remove\" aria-label=\"Remove checked toppings\">\n <span class=\"material-symbols\" aria-hidden=\"true\">chevron_left</span>\n </button>\n <button type=\"button\" class=\"icon-button\" data-transfer=\"remove-all\" aria-label=\"Remove all toppings\">\n <span class=\"material-symbols\" aria-hidden=\"true\">keyboard_double_arrow_left</span>\n </button>\n </div>\n <ul class=\"list transfer-chosen\" aria-label=\"Chosen toppings\">\n <li><label><input type=\"checkbox\" value=\"mushrooms\"> Mushrooms</label></li>\n </ul>\n </div>\n</form>\n```\n\n#### Rules\n\n- `transfer-list-needs-a-name`: Give each .transfer-list a data-name. The chosen rows submit their values under it; without one the form gets nothing.\n- `transfer-list-checkbox-unnamed`: Leave the name off a transfer list's checkboxes. A checkbox's name submits whether it is checked, which only says what is selected to move; the script submits the chosen rows under the container's data-name.\n- `transfer-list-lists-labelled`: Name each transfer list with aria-label or aria-labelledby, such as \"Available toppings\" and \"Chosen toppings\". The name tells people which list a row is in, and the move announcement reads it.\n- `transfer-list-buttons-typed`: Give each [data-transfer] button type=\"button\". A button in a form submits it by default, so an untyped move button would also send the form.\n"
389
+ },
390
+ {
391
+ "file": "tree-select.md",
392
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Tree select\nChoose one or more leaves from a tree using the keyboard or pointer.\n\nComponent ID: `tree-select`\n\n[Component documentation](https://www.expressivecss.com/tree-select.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/tree-select.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A destination field with continents, countries and cities takes a data-tree select whose cities are the leaves; a field of five countries stays a select.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-25); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-27, `llm.md#tree-select`): A native select[data-tree] whose flat options name their parent's value in data-parent; a role=\"tree\" popup with the ARIA tree keyboard contract over visible, enabled rows, mirrored under RTL; single or native multiple independent leaf choices, with branches normalized out of multiple selection; branch options disabled while it runs; optional single/multiple leaf search retaining ancestor paths and restoring pre-query expansion; refresh() preserving the query, expansion baseline and focus.\n\nWeb adaptation: TreeSelect extends FormSelect, so the native select keeps the form value, reset, validity and disabled state. The popup is a ul role=\"tree\" inside the Menu surface, one list below the rows Menu styles, named as the field is. Search reuses the labelled dialog/search/status controls around that tree without a listbox wrapper.\n\nKnown boundary: Keys in both directions, typeahead, pointer toggling, refresh(), validation focus, form data, indentation, chevrons and disabled rows are checked in a browser, and the tree from data-parent in jsdom; spoken output, touch, native zoom and OS IME remain unverified. Leaf filtering, expansion restoration, native submission, reset/refresh, modal/shadow focus and forced-color scrolling are checked in all three engines. No choosable branch; the field shows leaf text, not ancestor paths. Multiple toggling, hidden selections, native two-value submission and distinct selected/focus paint are checked in all three engines.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#tree-select).\n\n#### Contract\n\nChoose leaves from options nested as a tree, expanded with the keyboard or pointer.\n\nA native `<select data-tree>` holds flat `<option>`s. A child names its parent option's value in `data-parent`; an option without one is a root, and an option another option names is a branch. `AutoInit()` starts every `select[data-tree]` as `TreeSelect`, a FormSelect subclass, so the field, labels, validation messages and reset work as in a select. The native select still owns the form value, reset, validity and the disabled state. HTML cannot nest `<optgroup>`s, so nesting comes only from `data-parent`.\n\nThe popup is a `<ul role=\"tree\">` inside the Menu surface, named as the field is: its `aria-labelledby`, then its `aria-label`, then its labels. Branch rows have `aria-expanded` and hold their children in a `ul[role=\"group\"]`; leaf rows have `aria-selected`. Branches start collapsed. In default mode, opening reveals and focuses the first chosen enabled leaf, or the first enabled row; search mode focuses its separate search field.\n\n#### Syntax\n\n```html\n<div class=\"field\">\n <select id=\"place\" name=\"place\" data-tree>\n <option value=\"\" disabled selected>Choose a place</option>\n <option value=\"europe\" disabled>Europe</option>\n <option value=\"france\" data-parent=\"europe\" disabled>France</option>\n <option value=\"paris\" data-parent=\"france\">Paris</option>\n <option value=\"lyon\" data-parent=\"france\">Lyon</option>\n <option value=\"spain\" data-parent=\"europe\">Spain</option>\n <option value=\"asia\" disabled>Asia</option>\n <option value=\"tokyo\" data-parent=\"asia\">Tokyo</option>\n </select>\n <label for=\"place\">Place</label>\n</div>\n```\n\n#### Methods\n\n- `.refresh()`: Rebuilds the tree from the native options after a page adds, removes or changes them. Without search, branches whose value is still there stay open. An active query is reapplied without overwriting its saved expansion baseline. While open, focus stays on a surviving enabled visible row, moves to an enabled visible row, or returns to search when none remains.\n- `.getSelectedValues()`: The selected native option values in option order, as in FormSelect.\n- `.destroy()`: Removes the tree and its listeners, restores the branch options' `disabled` attributes and the native select.\n\n#### Also documented\n\nThe Tree select section of the [API reference](https://www.expressivecss.com/llm.md) also covers Multiple leaves, Tree search, Properties.\n\n#### Rules\n\n- `tree-select-needs-native-options`: Put data-tree on a native <select>, optionally with multiple. Nest flat options only through data-parent naming the parent option value. An <optgroup> cannot nest, and data-parent needs a select with data-tree.\n- `option-has-selected-state`: role=option promises a selection state. Without aria-selected the listbox never says which entry is chosen.\n"
297
393
  },
298
394
  {
299
395
  "file": "tree.md",
300
- "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Tree\nNested lists of folders and links that open in place, for file browsers and deep navigation.\n\nComponent ID: `tree`\n\n[Component documentation](https://www.expressivecss.com/tree.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/tree.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.15.0)\n\nContract: ExpressiveCSS 0.15.0\n\nContract SHA-256: `e1c777a1bcd53d5a6ed4615075a7a500fb6f488050e727487804f8cc421b5902`\n\n#### Selection and adaptation\n\nRuntime ownership: `native`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Three settings pages under one heading are a list or a navigation rail. A tree would add chevrons and indentation for a hierarchy one level deep.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#tree`): Nested lists of native disclosures and links with guide lines, turning chevrons in LTR and RTL, leading icons and an aria-current row.\n\nWeb adaptation: Native <details> branches and links; every row is a Tab stop and no tree role is promised.\n\nKnown boundary: Indentation, chevrons in LTR and RTL, opening a branch and the current row are checked in a browser; long trees for keyboard users, enlarged text and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#tree).\n\n#### Contract\n\nA `<ul class=\"tree\">` nests lists. A branch is an `<li>` holding a `<details>`: its `<summary>` is the row and its nested `<ul>` the children. A leaf is an `<li>` holding a link, or a `<span>` when it is not one. Mark the current row with `aria-current`. Each level draws a guide line from its parent's chevron. Wrap a tree of links in a named `<nav>`. CSS only; there is no JavaScript.\n\nIt is lists of disclosures and links rather than `role=\"tree\"`: every row is a Tab stop and the browser opens a branch with Enter or Space. A tree role would promise arrow-key navigation that native disclosures do not provide.\n\n#### Syntax\n\n```html\n<nav aria-label=\"Files\">\n <ul class=\"tree\">\n <li>\n <details open>\n <summary>src</summary>\n <ul>\n <li><a href=\"/src/index.ts\" aria-current=\"page\">index.ts</a></li>\n <li>\n <details>\n <summary>components</summary>\n <ul>\n <li><a href=\"/src/components/menu.ts\">menu.ts</a></li>\n </ul>\n </details>\n </li>\n </ul>\n </details>\n </li>\n <li><a href=\"/README.md\">README.md</a></li>\n </ul>\n</nav>\n```\n\n#### Also documented\n\nThe Tree section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `tree-is-not-a-tree-widget`: A .tree takes no ARIA tree roles. It is lists of disclosures and links, which keep their own roles and keyboard behavior.\n- `tree-marks-current-not-selected`: Mark the open file or page with aria-current. aria-selected is not valid on a link or a list item.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
396
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Tree\nNested lists of folders and links that open in place, for file browsers and deep navigation.\n\nComponent ID: `tree`\n\n[Component documentation](https://www.expressivecss.com/tree.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/tree.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `native`. No dedicated entry in the reviewed Google component inventory.\n\nExample: Three settings pages under one heading are a list or a navigation rail. A tree would add chevrons and indentation for a hierarchy one level deep.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-06); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-06, `llm.md#tree`): Nested lists of native disclosures and links with guide lines, turning chevrons in LTR and RTL, leading icons and an aria-current row.\n\nWeb adaptation: Native <details> branches and links; every row is a Tab stop and no tree role is promised.\n\nKnown boundary: Indentation, chevrons in LTR and RTL, opening a branch and the current row are checked in a browser; long trees for keyboard users, enlarged text and spoken output remain unverified.\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#tree).\n\n#### Contract\n\nA `<ul class=\"tree\">` nests lists. A branch is an `<li>` holding a `<details>`: its `<summary>` is the row and its nested `<ul>` the children. A leaf is an `<li>` holding a link, or a `<span>` when it is not one. Mark the current row with `aria-current`. Each level draws a guide line from its parent's chevron. Wrap a tree of links in a named `<nav>`. CSS only; there is no JavaScript.\n\nIt is lists of disclosures and links rather than `role=\"tree\"`: every row is a Tab stop and the browser opens a branch with Enter or Space. A tree role would promise arrow-key navigation that native disclosures do not provide.\n\n#### Syntax\n\n```html\n<nav aria-label=\"Files\">\n <ul class=\"tree\">\n <li>\n <details open>\n <summary>src</summary>\n <ul>\n <li><a href=\"/src/index.ts\" aria-current=\"page\">index.ts</a></li>\n <li>\n <details>\n <summary>components</summary>\n <ul>\n <li><a href=\"/src/components/menu.ts\">menu.ts</a></li>\n </ul>\n </details>\n </li>\n </ul>\n </details>\n </li>\n <li><a href=\"/README.md\">README.md</a></li>\n </ul>\n</nav>\n```\n\n#### Also documented\n\nThe Tree section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `tree-is-not-a-tree-widget`: A .tree takes no ARIA tree roles. It is lists of disclosures and links, which keep their own roles and keyboard behavior.\n- `tree-marks-current-not-selected`: Mark the open file or page with aria-current. aria-selected is not valid on a link or a list item.\n\n#### Guide checks\n\n- Label every <nav> landmark with aria-label or aria-labelledby.\n"
397
+ },
398
+ {
399
+ "file": "treemap-chart.md",
400
+ "content": "<!-- Generated by scripts/gen-expressivecss-skill.mjs. Do not edit. -->\n\n### Treemap chart\nParts of a whole drawn as rectangles whose areas follow their values.\n\nComponent ID: `treemap-chart`\n\n[Component documentation](https://www.expressivecss.com/treemap-chart.html.md) · [Repository source](https://github.com/BaezFJ/ExpressiveCSS/blob/master/docs/src/pages/treemap-chart.astro) · [Matching tag](https://github.com/BaezFJ/ExpressiveCSS/tree/v0.16.0)\n\nContract: ExpressiveCSS 0.16.0\n\nContract SHA-256: `d005fbd1fc75538ac5f20a4024adbd044ab3d31d3a3ff9c34b268a74c26b1d5a`\n\n#### Selection and adaptation\n\nRuntime ownership: `auto-init`. No dedicated entry in the reviewed Google component inventory.\n\nExample: A budget split across twelve teams is a treemap chart. Revenue split across three plans is a pie chart.\n\n#### Material mapping\n\nRelationship: none. Web extension; no dedicated entry was observed in the reviewed current M3 component inventory.\n\nUpstream: web-extension-inventory-reviewed (2026-10-25); [evidence](https://m3.material.io/components).\n\nRequirements and boundaries: design reference web-extensions-review. The inventory page renders client-side, so this review relies on the 2026-09-13 inventory review. Full visual parity and spoken output unverified.\n\nSupport (2026-10-25, `llm.md#treemap-chart`): Rectangles drawn from a figure's data table, one per row of the first value column with its area in proportion to the total, in a squarified layout that mirrors in right-to-left pages, with names and cells written in rectangles that hold them, no rectangle for cells that are not positive numbers, six colors, update(), data-watch, and a pointer and keyboard tooltip with each row's share that steps through rows in table order.\n\nWeb adaptation: The <table> stays in the page as the text alternative and the no-JavaScript fallback; the rectangles are aria-hidden and the plot is one Tab stop with a polite live tooltip.\n\nKnown boundary: Areas, the layout in left-to-right and right-to-left pages, gaps, colors, text fitting, keyboard steps, the rectangle under the pointer, the tooltip, update() and data-watch are checked in a browser, and the forced-colors edge is checked in the stylesheet; screen reader output and enlarged text remain unverified. There is one level only: no parent groups, legend, drill-down or animated update().\n\nFull parity remains unassessed. [Capability evidence](../references/capability-roadmap.md#treemap-chart).\n\n#### Contract\n\nParts of a whole drawn as rectangles whose areas follow their values, filling the plot.\n\nA `<figure class=\"treemap-chart\">` takes the same `<figcaption>` and `<table>` as a pie chart. `AutoInit()` draws each row of the first value column as a rectangle whose share of the plot's area is its share of the total, with its name and cell written inside it, and a tooltip that follows the pointer. The table stays in the page, visually hidden, as the chart's text alternative; without JavaScript it shows instead of the chart.\n\n#### Syntax\n\n```html\n<figure class=\"treemap-chart\">\n <figcaption>Budget by team</figcaption>\n <table>\n <thead>\n <tr><th>Team</th><th>Budget</th></tr>\n </thead>\n <tbody>\n <tr><th>Engineering</th><td>$600,000</td></tr>\n <tr><th>Research</th><td>$300,000</td></tr>\n <tr><th>Design</th><td>$120,000</td></tr>\n <tr><th>Sales</th><td>$80,000</td></tr>\n </tbody>\n </table>\n</figure>\n```\n\n#### Also documented\n\nThe Treemap chart section of the [API reference](https://www.expressivecss.com/llm.md) also covers Tokens.\n\n#### Rules\n\n- `treemap-chart-is-a-figure-with-a-table`: Write .treemap-chart as a <figure> holding a <table> of the data. The drawn chart is hidden from assistive technology, so the table is all a screen reader reads, and it is what shows without JavaScript.\n- `treemap-chart-has-a-caption`: Name a .treemap-chart with a <figcaption>. It names the focusable plot as well as the figure; hide it with .visually-hidden when nearby text already names the chart.\n"
301
401
  }
302
402
  ]
303
403
  }