css-is-awesome 1.14.3 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -8,7 +8,7 @@ A token-driven SCSS design system with a **single mixin-router per component**.
8
8
 
9
9
  **Every mixin is a knob-board.** Each look/feel dimension is an *input*, so a consumer can restyle any mixin at any time by changing an argument — row→column is just `@include cia.flex($direction: column)`, never a hand-written `flex-direction`. Customization lives in the mixin's arguments; the consumer stays one line. **If a visual dimension can only be reached by overriding in CSS, that's a missing input — add it to the mixin.** Fewer SCSS lines always wins.
10
10
 
11
- **v1.0 architecture (locked 2026-05-23):** humans-first, AI-second. The 5-pillar priority is **(1) users first, (2) tokens, (3) theme editor on the website, (4) mixin-first speed, (5) AI as composer via recipes book + MCP server**. v1.0 shipped the recipes book + theme editor polish + Tailwind/Bootstrap migration CLI + playground + MCP polish. No separate React component library (Jerry's call — recipes are the deliverable). Full backlog: [`roadmap/epics/v1-0/`](./roadmap/epics/v1-0/).
11
+ **v1.0 architecture (locked 2026-05-23):** humans-first, AI-second. The 5-pillar priority is **(1) users first, (2) tokens, (3) theme editor on the website, (4) mixin-first speed, (5) AI as composer via recipes book + MCP server**. v1.0 shipped the recipes book + theme editor polish + Tailwind/Bootstrap migration CLI + MCP polish (the playground was carried forward to v1.1 EPIC-09, still unbuilt). No separate React component library (Jerry's call — recipes are the deliverable). Full backlog: [`roadmap/epics/v1-0/`](./roadmap/epics/v1-0/).
12
12
 
13
13
  Three authoring tiers, in primary-to-fallback order:
14
14
 
@@ -338,7 +338,7 @@ Or run this in-repo copy directly — needs its SDK peer deps installed manually
338
338
  }
339
339
  ```
340
340
 
341
- Either way it exposes **30 tools** across 8 families:
341
+ Either way it exposes **31 tools** across 8 families:
342
342
 
343
343
  - **Themes** — `list_themes`, `get_theme`, `search_themes`
344
344
  - **Mixins** — `list_mixins`, `get_mixin`, `search_mixins` (real signatures — don't guess)
@@ -348,7 +348,7 @@ Either way it exposes **30 tools** across 8 families:
348
348
  - **Components** — `list_components`, `get_component`, `search_components`
349
349
  - **Recipes** — `list_recipes`, `get_recipe`
350
350
  - **Doc readers** — `read_llm_txt`, `read_changelog`, `read_migration`, `read_theming`, `read_agents`, `read_contract`, `read_three_tiers`, `read_readme`, `read_versioning`
351
- - **Helpers** — `assemble_prompt` (bundle context), `resolve_size` (snap a design px value to cia's 4px grid — call this whenever a design tool hands you a raw px value)
351
+ - **Helpers** — `assemble_prompt` (bundle context), `resolve_size` (snap a design px value to cia's 4px grid — call this whenever a design tool hands you a raw px value), `validate_theme` (run the real theme validator — contract + contrast — on a CSS string before you ship it)
352
352
 
353
353
  ## Other tooling (shipped)
354
354
 
package/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ # [1.16.0](https://github.com/Jerry2d3d/css-is-awesome/compare/v1.15.0...v1.16.0) (2026-09-17)
2
+
3
+
4
+ ### Features
5
+
6
+ * **recipes:** command-palette recipe + live demo ([6cc16b1](https://github.com/Jerry2d3d/css-is-awesome/commit/6cc16b104b625c8f73fda7631f72289b50e892ed))
7
+
8
+ # [1.15.0](https://github.com/Jerry2d3d/css-is-awesome/compare/v1.14.3...v1.15.0) (2026-09-16)
9
+
10
+
11
+ ### Features
12
+
13
+ * **recipes:** breadcrumb recipe + live demo ([c50fdd2](https://github.com/Jerry2d3d/css-is-awesome/commit/c50fdd2c52940f85d53020d095c410f3a0cb84a7))
14
+ * **recipes:** color-picker recipe + live demo ([4cc34f6](https://github.com/Jerry2d3d/css-is-awesome/commit/4cc34f6cf875fdcd3bbf92ed3e4c50aac4d83cb0))
15
+ * **recipes:** combobox-multiselect recipe + live demo ([1b919e0](https://github.com/Jerry2d3d/css-is-awesome/commit/1b919e08a87fceca49d25b6d5d1f084bc2359317))
16
+ * **recipes:** file-upload recipe + live demo ([74043c4](https://github.com/Jerry2d3d/css-is-awesome/commit/74043c4aa3b1301d5ebc49b40151ab91f715804e))
17
+ * **recipes:** pagination recipe + live demo ([d93fa2a](https://github.com/Jerry2d3d/css-is-awesome/commit/d93fa2a37ff6f8927a5736c3110c3af25901b006))
18
+ * **recipes:** sortable-list recipe + live demo ([9ddd71e](https://github.com/Jerry2d3d/css-is-awesome/commit/9ddd71ecab5ab8d2f72717083bcffb8b7df1fe68))
19
+ * **recipes:** toast recipe + live demo ([45ead4d](https://github.com/Jerry2d3d/css-is-awesome/commit/45ead4d3bded5a99228486deabe8136056d1bf4e))
20
+
1
21
  ## [1.14.3](https://github.com/Jerry2d3d/css-is-awesome/compare/v1.14.2...v1.14.3) (2026-09-12)
2
22
 
3
23
 
package/README.md CHANGED
@@ -8,13 +8,13 @@
8
8
 
9
9
  **Docs:** [cssisawesome.com](https://cssisawesome.com/) · **Install:** `npm install css-is-awesome`
10
10
 
11
- > **The recipes book:** build any component in any framework using cia mixins — 16 recipes today, including `dialog`, `combobox`, `datepicker`, `data-table`, `app-shell`, five form-validation patterns (HTML5, react-hook-form, Zod, async, success-states), `rtl-layout`, `print-to-pdf`, `print-spec`, `letterhead`, `mobile-nav` and `bottom-nav` — with `command-palette` and five more (`confirm-dialog`, `auth-flow`, `multi-step-wizard`, `otp-input`, `admin-dashboard-layout`) queued. AI agents read recipes via MCP and generate components in your stack; humans read them at [`/docs/recipes`](https://cssisawesome.com/docs/recipes/).
11
+ > **The recipes book:** build any component in any framework using cia mixins — 32 recipes today, including `dialog`, `command-palette`, `combobox`, `combobox-multiselect`, `datepicker`, `data-table`, `pagination`, `breadcrumb`, `toast`, `file-upload`, `sortable-list`, `color-picker`, `app-shell`, `admin-dashboard-layout`, `auth-flow`, `otp-input`, `multi-step-wizard`, `confirm-dialog`, five form-validation patterns (HTML5, react-hook-form, Zod, async, success-states), three i18n patterns, `rtl-layout`, `print-to-pdf`, `print-spec`, `letterhead`, `mobile-nav` and `bottom-nav`. AI agents read recipes via MCP and generate components in your stack; humans read them at [`/docs/recipes`](https://cssisawesome.com/docs/recipes/).
12
12
 
13
13
  ## For AI agents — start here
14
14
 
15
15
  **Read [`llm.txt`](./llm.txt) first.** One file, the whole system: install path, hard rules, the mixin vocabulary, and the traps that make agents write wrong cia code. It ships in the npm package, so it's at `node_modules/css-is-awesome/llm.txt` in any project that has cia.
16
16
 
17
- **Then connect the MCP server** and stop guessing at signatures. It answers from the real source — 30 tools covering themes, mixins, functions, tokens, recipes and components.
17
+ **Then connect the MCP server** and stop guessing at signatures. It answers from the real source — 31 tools covering themes, mixins, functions, tokens, recipes, components and theme validation.
18
18
 
19
19
  ```json
20
20
  {
@@ -294,7 +294,7 @@ The scope is kept narrow: 8 `!important` declarations, all inside `@media print`
294
294
 
295
295
  ## MCP server (for AI agents)
296
296
 
297
- cia ships a Model Context Protocol stdio server (JSON-RPC over stdio, protocol `2024-11-05`) exposing **30 tools** across 8 families (themes, mixins, functions, tokens · 127 required of them, animations, components, recipes, doc readers) plus `assemble_prompt` (context bundles) and `resolve_size` (snap design px values to cia's 4px grid). Any MCP-aware client (Claude Code, Cursor, Aider, Gemini, Copilot) can then query cia's real design system — mixin signatures, tokens, themes, recipes — instead of guessing, without grep-walking the repo. Full reference: [`/docs/mcp`](https://cssisawesome.com/docs/mcp/).
297
+ cia ships a Model Context Protocol stdio server (JSON-RPC over stdio, protocol `2024-11-05`) exposing **31 tools** across 8 families (themes, mixins, functions, tokens · 127 required of them, animations, components, recipes, doc readers) plus `assemble_prompt` (context bundles) , `resolve_size` (snap design px values to cia's 4px grid) and `validate_theme` (run the real theme validator on CSS you just wrote). Any MCP-aware client (Claude Code, Cursor, Aider, Gemini, Copilot) can then query cia's real design system — mixin signatures, tokens, themes, recipes — instead of guessing, without grep-walking the repo. Full reference: [`/docs/mcp`](https://cssisawesome.com/docs/mcp/).
298
298
 
299
299
  **Recommended — zero install:** use the dedicated [`css-is-awesome-mcp`](https://www.npmjs.com/package/css-is-awesome-mcp) package. It depends on `css-is-awesome` and resolves your installed version's real source, so it's never out of sync — and the MCP SDK ships as a real dependency, not an optional peer you have to remember to add.
300
300
 
@@ -408,7 +408,7 @@ Full detail: [`/docs/testing`](https://cssisawesome.com/docs/testing/).
408
408
 
409
409
  **Stable, [published on npm](https://www.npmjs.com/package/css-is-awesome)** (first published 2026-09-01). The mixin API, functions, token contract, and theme architecture are stable and under strict SemVer — breaking changes require a major bump. See [`VERSIONING.md`](./VERSIONING.md) for the policy.
410
410
 
411
- The 1.0 surface is the v0.8 mixin-first reframe — twelve mixin renames, theme system collapsed to 8 single-file theme families, six zero-JS components, intrinsic-layout vocabulary, opt-in utilities — plus the recipes book, the Tailwind/Bootstrap migration on-ramp, print/PDF support, and the 30-tool MCP server (now also available zero-install via the companion [`css-is-awesome-mcp`](https://www.npmjs.com/package/css-is-awesome-mcp) package). The npm package ships ZERO JavaScript by hard rule.
411
+ The 1.0 surface is the v0.8 mixin-first reframe — twelve mixin renames, theme system collapsed to 8 single-file theme families, six zero-JS components, intrinsic-layout vocabulary, opt-in utilities — plus the recipes book, the Tailwind/Bootstrap migration on-ramp, print/PDF support, and the 31-tool MCP server (now also available zero-install via the companion [`css-is-awesome-mcp`](https://www.npmjs.com/package/css-is-awesome-mcp) package). The npm package ships ZERO JavaScript by hard rule.
412
412
 
413
413
  See [CHANGELOG.md](./CHANGELOG.md) for the full history and [MIGRATION.md](./MIGRATION.md) for the v0.7 → v0.8 upgrade path.
414
414
 
package/ROADMAP.md CHANGED
@@ -390,7 +390,7 @@ Full backlog: [`roadmap/epics/v1-0/README.md`](./roadmap/epics/v1-0/README.md).
390
390
 
391
391
  - ❌ `@cia/react` as a separate npm component library Jerry maintains forever
392
392
  - ❌ shadcn-style component ejection CLI for cia
393
- - ❌ `@cia/a11y` as cia-original JS shims (deferred → `@cia/a11y-recipes` post-v1.0)
393
+ - ❌ `@cia/a11y` as cia-original JS shims (deferred → `@cia/a11y-recipes` post-v1.0 → **retired 2026-09-17**: folded into the core recipe book, WCAG-strict content is a Variants subsection per recipe)
394
394
  - ❌ Component library as the v1.0 selling point — recipes ARE the deliverable
395
395
  - ❌ VS Code extension at v1.0 (deferred to v1.5; playground covers the demo need)
396
396
 
@@ -465,7 +465,7 @@ Open list of ideas that could make cia better, captured in [`WISHLIST.md`](./WIS
465
465
 
466
466
  | Release | Theme | Epic folder | Stories | Effort |
467
467
  |---|---|---|---|---|
468
- | **v1.1** | Recipes momentum (7 more recipes, install wizard, @cia/a11y-recipes add-on, @cia/react codegen POC) | [v1-1](./roadmap/epics/v1-1/README.md) | 43 | ~25-35 days |
468
+ | **v1.1** | Recipes momentum (14 more recipes, install wizard, ~~@cia/a11y-recipes add-on~~ (retired), @cia/react codegen POC, playground) | [v1-1](./roadmap/epics/v1-1/README.md) | 43 | ~25-35 days |
469
469
  | **v1.2** | Coverage (RTL audit, form-validation recipes, i18n recipes, print recipe, MUI + Chakra migration) | [v1-2](./roadmap/epics/v1-2/README.md) | 32 | ~16-22 days |
470
470
  | **v1.3** | Ecosystem (Figma plugin, theme marketplace, DTCG migration CLI, @cia/angular) | [v1-3](./roadmap/epics/v1-3/README.md) | 34 | ~28-35 days |
471
471
  | v1.4 | *Reserved — scoped based on v1.1-v1.3 community feedback* | — | — | — |
package/dist/tokens.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- // Generated from scripts/theme-contract.json on 2026-09-12.
1
+ // Generated from scripts/theme-contract.json on 2026-09-17.
2
2
  // Do not edit by hand. Run `npm run build:token-types` to regenerate.
3
3
 
4
4
  /** Every CSS custom property cia themes are required to declare. */
package/llm.txt CHANGED
@@ -172,7 +172,7 @@ npm install -D @modelcontextprotocol/sdk zod
172
172
  }
173
173
  ```
174
174
 
175
- ## Recipes (shipped in 1.0.0)
175
+ ## Recipes (shipped in 1.0.0, grown since)
176
176
 
177
177
  cia ships a **recipes book**: portable patterns for building accessible components in any framework. Each recipe is a markdown file at `scss/recipes/<name>.md` with:
178
178
 
@@ -183,7 +183,7 @@ cia ships a **recipes book**: portable patterns for building accessible componen
183
183
 
184
184
  AI agents read recipes via MCP `list_recipes` / `get_recipe(name)` and generate consumer components in any framework. Humans read them at `/docs/recipes` and copy patterns directly.
185
185
 
186
- **Shipped today — 7 recipes: dialog, combobox, print-to-pdf, print-spec, letterhead, mobile-nav, bottom-nav.** Queued next: datepicker, data-table, command-palette. **No component library** — recipes are the deliverable. See `scss/recipes/README.md` for the schema.
186
+ **Shipped today — 32 recipes** (as of 2026-09-17): admin-dashboard-layout, app-shell, auth-flow, bottom-nav, breadcrumb, color-picker, combobox, combobox-multiselect, command-palette, confirm-dialog, data-table, datepicker, dialog, file-upload, form-validation-async, form-validation-html5, form-validation-react-hook-form, form-validation-success-states, form-validation-zod, i18n-date-formatting, i18n-number-currency, i18n-pluralization, letterhead, mobile-nav, multi-step-wizard, otp-input, pagination, print-spec, print-to-pdf, rtl-layout, sortable-list, toast. The authoritative list is `list_recipes` (MCP) or `npx cia add --list` — both read `scss/recipes/` directly. WCAG-strict variants live inside the recipes themselves (a `### WCAG-strict` subsection under Variants) — there is no separate a11y package. **No component library** — recipes are the deliverable. See `scss/recipes/README.md` for the schema.
187
187
 
188
188
  ## Priority ladder (the v1.0 pitch order)
189
189
 
package/mcp/server.cjs CHANGED
@@ -21,7 +21,7 @@
21
21
  * Sizing: resolve_size
22
22
  * Prompt: assemble_prompt(intent[, args])
23
23
  *
24
- * 30 tools total.
24
+ * 31 tools total.
25
25
  *
26
26
  * Discovery model: filesystem scan, no database. Parses SCSS files with
27
27
  * focused regex (no full SCSS AST). Tokens come from the authoritative
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "css-is-awesome",
3
- "version": "1.14.3",
3
+ "version": "1.16.0",
4
4
  "description": "A token-driven SCSS design system with light/dark theming, semantic color tokens, and a 800+ LOC mixin API.",
5
5
  "homepage": "https://github.com/Jerry2d3d/css-is-awesome#readme",
6
6
  "bugs": {
@@ -27,7 +27,7 @@ cia-version: ">=1.0.0"
27
27
  |---|---|---|
28
28
  | `name` | string | Must match the filename slug |
29
29
  | `description` | string | One sentence — what this recipe builds, no more |
30
- | `category` | enum | One of: `overlay`, `input`, `data`, `navigation`, `feedback`, `layout`, `auth` |
30
+ | `category` | enum | One of: `overlay`, `input`, `data`, `navigation`, `feedback`, `layout`, `auth`, `forms`, `i18n` |
31
31
  | `complexity` | enum | One of: `simple`, `medium`, `complex` |
32
32
  | `cia-version` | semver range | Minimum cia version this recipe targets |
33
33
 
@@ -0,0 +1,265 @@
1
+ ---
2
+ name: breadcrumb
3
+ description: An accessible "where am I" trail — a labelled <nav> wrapping an ordered list, the current page marked with aria-current, separators drawn by CSS and never read aloud.
4
+ category: navigation
5
+ complexity: simple
6
+ cia-version: ">=1.0.0"
7
+ ---
8
+
9
+ ## Use this when
10
+
11
+ You need to show a page's position in a hierarchy — docs sections, admin screens, category → subcategory → product — and let the user jump back up any level. Build it as `<nav aria-label="Breadcrumb">` around an `<ol>`: the list order **is** the hierarchy, so assistive tech announces "list, 4 items" and the nav landmark is discoverable by name. If your pages have no real hierarchy (a flat marketing site, a wizard's step indicator) this is the wrong pattern — a wizard wants the `multi-step-wizard` stepper, and flat sites want plain links.
12
+
13
+ ## Structure (raw HTML)
14
+
15
+ ```html
16
+ <nav aria-label="Breadcrumb" data-cia-recipe="breadcrumb">
17
+ <ol class="my-breadcrumb" data-slot="list">
18
+ <li><a href="/">Home</a></li>
19
+ <li><a href="/docs">Docs</a></li>
20
+ <li><a href="/docs/recipes">Recipes</a></li>
21
+ <li><span aria-current="page">Breadcrumb</span></li>
22
+ </ol>
23
+ </nav>
24
+ ```
25
+
26
+ Notes on the markup:
27
+
28
+ - The wrapper is a `<nav>` with a **name**. `aria-label="Breadcrumb"` is what lets a screen-reader user jump straight to it from the landmarks list, and distinguishes it from the site's primary `<nav>`.
29
+ - The trail is an **ordered** list — `<ol>`, not `<ul>` — because order carries meaning here (ancestor before descendant).
30
+ - The last item is the page the user is on. It is `aria-current="page"` and **not a link**: a `<span>` (or an `<a>` with no `href`). A self-link is a dead click and a confusing "link, current page" announcement.
31
+ - **No separator elements.** The `›` between items is drawn by CSS (`li + li::before`, provided by the mixin), so it never enters the accessibility tree — no `<span aria-hidden="true">/</span>` between every pair.
32
+
33
+ ## Styling (cia mixins)
34
+
35
+ The whole pattern is one existing mixin. `cia.breadcrumb` resets the list, lays the items out in a row, draws the separator with `::before` on every item after the first, styles links, and mutes the `[aria-current="page"]` item. Customise it through its **inputs** — the separator glyph and the gap — rather than by writing CSS around it.
36
+
37
+ ```scss
38
+ // MyBreadcrumb.module.scss — component stylesheet, so import the zero-emit barrel.
39
+ @use 'css-is-awesome/api' as cia;
40
+
41
+ .my-breadcrumb {
42
+ @include cia.breadcrumb($gap: 1, $separator: "›");
43
+ }
44
+ ```
45
+
46
+ That is the entire recipe for the base case. Two inputs are all you should ever need to touch:
47
+
48
+ | Input | Default | What it does |
49
+ |---|---|---|
50
+ | `$separator` | `"/"` | The glyph rendered in `li + li::before` — `"›"`, `"→"`, `"/"`, `"\\"`. It is CSS `content`, so it is never announced. |
51
+ | `$gap` | `1` | Spacing-scale step on both sides of the separator. |
52
+
53
+ A long trail on a narrow screen should wrap rather than overflow. The mixin is a plain flex row; add `flex-wrap` via `cia.cluster` on the same selector when your trails can get deep:
54
+
55
+ ```scss
56
+ @use 'css-is-awesome/api' as cia;
57
+
58
+ .my-breadcrumb {
59
+ @include cia.breadcrumb($separator: "›");
60
+ @include cia.cluster($gap: 1); // adds flex-wrap so deep trails wrap onto a second line
61
+ }
62
+ ```
63
+
64
+ ## Interactivity
65
+
66
+ **Zero JS.** The base breadcrumb is links and one `aria-current` attribute; the browser and the router do the rest. Two things the *consumer* owns:
67
+
68
+ - **Marking the current item.** Whatever renders the trail must put `aria-current="page"` on the last item and render it without an `href`. In a framework this is a one-line conditional on "is this the last crumb" (every example below does exactly that).
69
+ - **The collapsed variant (optional).** Deep trails (5+ levels) collapse the middle into a single `…` **button**. Pressing it swaps the ellipsis for the hidden crumbs. That is a boolean toggle — one `useState` / `ref` / class field — and the ellipsis must be a real `<button>` with `aria-expanded`, not a clickable `<span>`. See [Variants](#variants).
70
+
71
+ Edge cases:
72
+
73
+ - **SSR / static export:** everything above renders correctly server-side; there is no client-only state in the base pattern. The collapsed variant renders collapsed on the server and stays collapsed until the user expands it.
74
+ - **Single-level pages:** a trail with only "Home › Current" is still valid. A trail with only the current page is noise — render nothing.
75
+ - **Routing frameworks:** swap `<a href>` for your router's link component (`next/link`, `RouterLink`, SvelteKit `<a>` works as-is). The current item stays a `<span>` regardless.
76
+
77
+ ## A11y checklist
78
+
79
+ - [ ] The trail is wrapped in a `<nav>` with an accessible name (`aria-label="Breadcrumb"`), so it is a distinct, findable landmark ([APG Breadcrumb Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/))
80
+ - [ ] Items are inside an `<ol>` — the sequence is exposed as an ordered list, not loose inline links ([WCAG 2.2 SC 1.3.1 Info and Relationships](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html))
81
+ - [ ] The current page carries `aria-current="page"` and is not a link ([APG Breadcrumb Pattern — aria-current](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/))
82
+ - [ ] Separators are CSS `::before` content, so they are not announced between every crumb ([WCAG 2.2 SC 1.3.1 Info and Relationships](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html))
83
+ - [ ] Link colour vs. muted current-item colour is not the only cue — the current item is also non-interactive (no link affordance) ([WCAG 2.2 SC 1.4.1 Use of Color](https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html))
84
+ - [ ] In the collapsed variant, the `…` is a `<button>` with `aria-expanded` and a name ("Show all pages"), and expanding it does not move focus away from the button ([WCAG 2.2 SC 4.1.2 Name, Role, Value](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html))
85
+ - [ ] Every crumb link is keyboard reachable in document order and shows a visible focus ring ([WCAG 2.2 SC 2.4.7 Focus Visible](https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html))
86
+
87
+ ## Framework examples
88
+
89
+ All four examples take a `crumbs` array (`{ label, href }`), render every item but the last as a link, and mark the last as `aria-current="page"`.
90
+
91
+ ### React
92
+
93
+ ```tsx
94
+ import styles from "./Breadcrumb.module.scss";
95
+
96
+ type Crumb = { label: string; href: string };
97
+
98
+ export default function Breadcrumb({ crumbs }: { crumbs: Crumb[] }) {
99
+ if (crumbs.length < 2) return null;
100
+ const last = crumbs.length - 1;
101
+
102
+ return (
103
+ <nav aria-label="Breadcrumb">
104
+ <ol className={styles.myBreadcrumb}>
105
+ {crumbs.map((crumb, i) => (
106
+ <li key={crumb.href}>
107
+ {i === last ? (
108
+ <span aria-current="page">{crumb.label}</span>
109
+ ) : (
110
+ <a href={crumb.href}>{crumb.label}</a>
111
+ )}
112
+ </li>
113
+ ))}
114
+ </ol>
115
+ </nav>
116
+ );
117
+ }
118
+ ```
119
+
120
+ ### Vue
121
+
122
+ ```vue
123
+ <script setup>
124
+ const props = defineProps({ crumbs: { type: Array, required: true } });
125
+ </script>
126
+
127
+ <template>
128
+ <nav v-if="crumbs.length > 1" aria-label="Breadcrumb">
129
+ <ol class="my-breadcrumb">
130
+ <li v-for="(crumb, i) in crumbs" :key="crumb.href">
131
+ <span v-if="i === crumbs.length - 1" aria-current="page">{{ crumb.label }}</span>
132
+ <a v-else :href="crumb.href">{{ crumb.label }}</a>
133
+ </li>
134
+ </ol>
135
+ </nav>
136
+ </template>
137
+ ```
138
+
139
+ ### Svelte
140
+
141
+ ```svelte
142
+ <script>
143
+ export let crumbs = [];
144
+ </script>
145
+
146
+ {#if crumbs.length > 1}
147
+ <nav aria-label="Breadcrumb">
148
+ <ol class="my-breadcrumb">
149
+ {#each crumbs as crumb, i (crumb.href)}
150
+ <li>
151
+ {#if i === crumbs.length - 1}
152
+ <span aria-current="page">{crumb.label}</span>
153
+ {:else}
154
+ <a href={crumb.href}>{crumb.label}</a>
155
+ {/if}
156
+ </li>
157
+ {/each}
158
+ </ol>
159
+ </nav>
160
+ {/if}
161
+ ```
162
+
163
+ ### Vanilla (Web Component)
164
+
165
+ ```js
166
+ // <my-breadcrumb items='[{"label":"Home","href":"/"},{"label":"Docs","href":"/docs"}]'></my-breadcrumb>
167
+ class MyBreadcrumb extends HTMLElement {
168
+ connectedCallback() {
169
+ const crumbs = JSON.parse(this.getAttribute("items") ?? "[]");
170
+ if (crumbs.length < 2) return;
171
+ const last = crumbs.length - 1;
172
+
173
+ this.innerHTML = `<nav aria-label="Breadcrumb"><ol class="my-breadcrumb">${crumbs
174
+ .map((c, i) =>
175
+ i === last
176
+ ? `<li><span aria-current="page"></span></li>`
177
+ : `<li><a></a></li>`,
178
+ )
179
+ .join("")}</ol></nav>`;
180
+
181
+ // Set text via textContent / href via setAttribute so labels are never
182
+ // interpreted as HTML.
183
+ this.querySelectorAll("li").forEach((li, i) => {
184
+ const el = li.firstElementChild;
185
+ el.textContent = crumbs[i].label;
186
+ if (i !== last) el.setAttribute("href", crumbs[i].href);
187
+ });
188
+ }
189
+ }
190
+ customElements.define("my-breadcrumb", MyBreadcrumb);
191
+ ```
192
+
193
+ ## Variants
194
+
195
+ ### Collapsed middle (deep trails)
196
+
197
+ For trails deeper than ~5 levels, keep the first crumb and the last two, and fold everything between into a single `…` button. Pressing it reveals the hidden crumbs in place. The button is a normal list item, so the mixin's separator logic still applies to it.
198
+
199
+ ```html
200
+ <nav aria-label="Breadcrumb" data-cia-recipe="breadcrumb">
201
+ <ol class="my-breadcrumb">
202
+ <li><a href="/">Home</a></li>
203
+ <li>
204
+ <button type="button" data-slot="expand" aria-expanded="false" aria-label="Show all pages">…</button>
205
+ </li>
206
+ <li><a href="/docs/recipes">Recipes</a></li>
207
+ <li><span aria-current="page">Breadcrumb</span></li>
208
+ </ol>
209
+ </nav>
210
+ ```
211
+
212
+ ```scss
213
+ @use 'css-is-awesome/api' as cia;
214
+
215
+ .my-breadcrumb {
216
+ @include cia.breadcrumb($separator: "›");
217
+
218
+ [data-slot="expand"] {
219
+ @include cia.button-reset;
220
+ cursor: pointer;
221
+ color: cia.color(text-link);
222
+ padding-inline: cia.space(2xs);
223
+ border-radius: cia.radius(sm);
224
+ @include cia.focus-ring;
225
+
226
+ &:hover {
227
+ background: cia.color(interactive-hover);
228
+ }
229
+ }
230
+ }
231
+ ```
232
+
233
+ Behaviour: on click, set `aria-expanded="true"` and replace the `…` item with the hidden `<li>`s. Focus stays where it was (on the button, which is now gone — move it to the first revealed link so it isn't lost). There is no "collapse again" — once expanded, the trail is just a trail.
234
+
235
+ ### Truncated labels
236
+
237
+ Long page titles blow up a single-line trail. Cap each crumb at a max width and let `cia.truncate` add the ellipsis; the full title stays available via `title` on the link.
238
+
239
+ ```scss
240
+ @use 'css-is-awesome/api' as cia;
241
+
242
+ .my-breadcrumb {
243
+ @include cia.breadcrumb($separator: "›");
244
+
245
+ a,
246
+ [aria-current="page"] {
247
+ display: inline-block;
248
+ max-inline-size: 12rem;
249
+ @include cia.truncate;
250
+ }
251
+ }
252
+ ```
253
+
254
+ ## Pitfalls
255
+
256
+ - **Don't make the current page a link to itself.** It reads as "link, Breadcrumb, current page" and clicking it does nothing useful. A `<span aria-current="page">` is correct.
257
+ - **Don't put separators in the DOM.** `<li>/</li>` or `<span aria-hidden="true">/</span>` between crumbs doubles the item count and, without `aria-hidden`, gets read aloud. The mixin's `::before` separator costs nothing and is invisible to assistive tech.
258
+ - **Don't use `<ul>`.** The hierarchy is ordered; `<ol>` says so.
259
+ - **Don't nest the breadcrumb inside the site's primary `<nav>`.** Two nav landmarks with distinct labels are the expected shape; one nav containing another confuses the landmark list.
260
+ - **Don't drop `aria-label` when there's a visible heading.** If you *do* have a visible "You are here:" heading, point at it with `aria-labelledby` instead — but there must always be a name.
261
+
262
+ ## Related recipes
263
+
264
+ - [`app-shell`](./app-shell.md) — the page frame this trail typically sits at the top of
265
+ - [`admin-dashboard-layout`](./admin-dashboard-layout.md) — deep admin hierarchies are the usual home of the collapsed variant