**Minor release.** Five surface areas touched: form-input accessibility (`multi-select`, `range-slider`), shared-prop-vocabulary aliases (`feature`, `card`, `button`, `heading`, `reveal`), icon-system completeness (8 new base aliases per preset + bare-name fallthrough resolution), new `main` max-width default + `button` `forceLoading`, two new artisan-command capabilities (`wirekit:show <parent>.<child>`, `wirekit:verify --fix`), plus a new static-analysis a11y linter `wirekit:doctor:a11y`. Documentation site grows the unified `/blueprints/` route with `/blueprints/partials/*` (8 marketing-section building blocks) and `/blueprints/recipes/*` (10 worked-example compositions) under a fresh index page.

### Added

- **`ariaLabel` prop on `<x-wirekit::multi-select>`.** Optional explicit override for the screen-reader-announced label of the component's internal combobox `<input>`. When unset, the component auto-derives a sensible label from (in order) any passthrough `aria-label` attribute, then the `label` prop, then the `placeholder`, then finally the `name` — so the internal input is never unlabeled even if the consuming code does not supply one explicitly.
- **`forceLoading` prop on `<x-wirekit::button>`.** When `true`, renders the spinner unconditionally and disables the button regardless of any `wire:loading` gate. Useful for static documentation demos, non-Livewire contexts, or stories that want to preview the loading state without a backend request in flight. The existing `loading` semantics are unchanged — `loading=true` alone still auto-detects the wire-action context.
- **`max` prop on `<x-wirekit::main>` with default `"2xl"`.** Caps the inner content width to the matching `--size-wk-container-*` token tier. Default `"2xl"` matches the existing `container` component default and prevents dashboards from stretching edge-to-edge on 1900 px+ monitors. Opt out with `max="none"` to preserve the pre-2.3.0 unbounded behavior, or pick another tier (`sm`/`md`/`lg`/`xl`/`full`). The config key `wirekit.components.main.max` overrides the default app-wide.
- **8 new base icon aliases in every preset** — `home`, `moon`, `sun`, `book-open`, `sign-out`, `megaphone`, `map`, `file-text`. The four base presets (Heroicons, Lucide, Phosphor, Tabler) now ship 34 aliases each (was 26). Every common first-install icon (theme toggle, sidebar home, sign-out button, file row) resolves without an additional preset.
- **Icon-alias fallthrough resolution.** When `<x-wirekit::icon name="briefcase">` references an alias that isn't in the active preset, the resolver now tries the underlying blade-icons identifier (`heroicon-m-briefcase`, then `-o-`, then `-s-`) before throwing `Unknown icon alias`. Logs an INFO line so the developer can add the alias to their preset. Catches the bug class where a developer reaches for a heroicons name that ships in `blade-ui-kit/blade-heroicons` but wasn't aliased by WireKit.
- **`php artisan wirekit:show <parent>.<child>` resolves dotted sub-component names.** Pre-fix, `php artisan wirekit:show card` advertised `Sub-components: card.body` but `php artisan wirekit:show card.body` returned "Unknown component" because the component registry tracks only top-level components. The artisan command now resolves sub-components by reading the nested Blade file directly and extracting props via the same parser the top-level command uses. Honors `--as=json` so AI tooling can introspect sub-components the same way as top-level components.
- **`wirekit:verify --fix` self-heals missing public assets.** When the doctor finds `public/vendor/wirekit/wirekit.css` or `wirekit.js` missing AND `--fix` is set, it proactively runs `vendor:publish --tag=wirekit-assets --force` and re-tests the asset paths so the verify report comes back clean. Closes the fresh-clone-fails-the-doctor gap where the `.gitignored` `public/vendor/wirekit/` directory is empty until the publish runs. Without `--fix`, the existing "Run: php artisan vendor:publish ..." hint is augmented with an "Or: php artisan wirekit:verify --fix" alternative.
- **`wirekit:doctor:a11y` — new static-analysis a11y linter.** Scans every `.blade.php` under a given path for three high-value WCAG-AA bug classes: icon-only `<x-wirekit::button>` without `aria-label`, `role="dialog"`/`"alertdialog"` without `aria-label`/`aria-labeledby`, `role="img"` without `aria-label`. CLI: `php artisan wirekit:doctor:a11y [path] [--fail-on=error|warning|none]`. Exit 1 on any finding ≥ threshold. Pair with `wirekit:verify` in CI to gate both integration health AND a11y in one pass.
- **`tag_alias` field in `wirekit:export-json` for class-based components.** Class-based components whose canonical tag uses the single-hyphen form (`<x-wirekit-chart>`) now also emit a `tag_alias` field carrying the double-colon form (`<x-wirekit::chart>`). Lets downstream tool integrators that grep against the historical shape still match. Normal anonymous components have no alias — the field is omitted entirely so the schema stays minimal for the common case.
- **`library` prop on `<x-wirekit-chart>` — per-instance chart-library override.** Accepts the same values as `config('wirekit.charts.library')` — the built-in keys `"chartjs"` / `"apexcharts"`, or a fully qualified class name implementing `ChartAdapter`. When `null` (default), the chart uses whatever the global config resolves to — matching pre-v2.3.0 behavior. When set, this chart instance binds to the named library regardless of the app default, so two charts on the same page can use different libraries. Useful when a specific chart type requires a specific library (`boxplot` / `candlestick` / `heatmap` / `treemap` / `column` are ApexCharts-only and throw `TypeNotSupportedException` against the Chart.js adapter) inside an app whose default is the other library. Backwards compatible — existing charts that don't set the prop continue to read from config exactly as before. The wrapper components `<x-wirekit::chart-mixed>` and `<x-wirekit::sparkline>` accept and forward `library` to the underlying chart, so mixed-library pages work via either tag shape. Example: `<x-wirekit-chart type="column" library="apexcharts" ... />` renders the ApexCharts column chart even when the app's default is `chartjs`.

### Changed

- **`<x-wirekit::feature>` `tone` prop accepts the canonical intent vocabulary.** `primary` is now accepted as an alias for `accent` (same color role), and `info` as an alias for `soft` (same tinted-accent treatment). The pre-2.3.0 names (`accent`, `neutral`, `soft`, `success`, `warning`, `danger`) are unchanged. Developers copying `intent="primary"` from a `<x-wirekit::button>` no longer get a 500 on `<x-wirekit::feature>`.
- **`<x-wirekit::heading>` `size` prop accepts `md` / `4xl` / `5xl`.** The size enum now reads `sm | md | base | lg | xl | 2xl | 3xl | 4xl | 5xl`. `md` is an alias for the existing `base` tier (matches the rest of the WireKit's middle-tier convention); `4xl` (2.25 rem) and `5xl` (3 rem) are new tiers for hero copy. The `--text-wk-4xl` and `--text-wk-5xl` design tokens are shipped in `dist/wirekit.css`.
- **`<x-wirekit::card>` accepts `variant="outline"` as alias for `outlined`.** `<x-wirekit::button>` accepts `surface="outlined"` as alias for `outline`. Same visual treatment, two spellings — the WireKit's pre-2.3.0 vocabulary diverged. Both forms now work on both components; canonical spellings (`outlined` for card, `outline` for button) are unchanged.
- **`<x-wirekit::profile>` `avatar` prop accepts `string | array{src?, initials?, alt?}`.** Pre-fix the prop required a URL string; passing an array crashed with a `htmlspecialchars(): Argument #1 ($string) must be of type string` error. Now matches the shape that `message.author` and the avatar primitive already accept. The initials fallback renders a rounded-full muted-background span with `aria-label` set to the profile's `name` so screen readers still announce who the profile belongs to.
- **`<x-wirekit::reveal>` `preset` prop accepts `fade-up` / `fade-down` / `fade-left` / `fade-right`.** Aliases for `slide-up-in` / `slide-down-in` / `slide-left-in` / `slide-right-in`. Matches the `fade-*` shorthand naming convention designers reach for first. Existing `slide-*-in` callers unaffected.
- **`animateIn` prop on every marketing component accepts the same `fade-*` aliases.** Card, feature, hero, stat, callout, alert, empty-state, cta, and footer all route through `WireKit::resolveAnimateIn()`, which now resolves the four `fade-*` shorthand aliases identically to `<x-wirekit::reveal>`. Pre-fix, `<x-wirekit::card animateIn="fade-up">` rejected the alias as an unknown enum value (silent no-op in production, `InvalidArgumentException` in debug). Now byte-identical resolution: `fade-up` → `slide-up-in` on every surface. Existing callers unaffected.
- **`<x-wirekit::sidebar.item>` consumes `data-current` as fallback for `:active`.** Livewire 4 emits `data-current` automatically on `wire:navigate` links. Pre-fix, sidebar.item ignored the attribute and required the developer to manually pass `:active="request()->is('posts*')"`, duplicating routing knowledge already encoded in `routes/web.php`. The explicit `:active` prop still wins; if unset and the attribute bag carries `data-current="true"` / `data-current="page"` / `data-current="1"`, the item highlights automatically.
- **`/blueprints/` documentation route — `partials/*` and `recipes/*` sub-sections.** The `/blueprints/` index page lists every entry with per-card previews and copy-paste guidance, organized under two sub-sections:
  - **`/blueprints/partials/*`** — 8 drop-in marketing-section building blocks: hero, features, pricing, testimonials, logo-cloud, FAQ, CTA, footer. Each partial is a single Blade snippet you paste into a parent layout and customize.
  - **`/blueprints/recipes/*`** — 10 worked examples composing several WireKit primitives into a complete UI shape: long-form article shell, documentation reader, on-page TOC, marketing landing page (with TOC variant), hero-with-code-aside, live KPI strip, stat-with-sparkline, feature-with-numbered-marker, toolbar-filter-bar.
- **`<x-wirekit::brand>` accepts `<x-slot:logo>` for custom logo markup.** Pre-fix the prop required a URL string for `<img src>`; passing a slot stringified the slot HTML inside the `<img src>` attribute and produced broken DOM output (visible attribute-fragment text). The component now detects a `ComponentSlot` instance and renders the slot directly. URL-string callers unaffected.

### Fixed

- **`<x-wirekit::multi-select>` internal combobox `<input>` now always carries an `aria-label`.** Previously, the parent `<x-wirekit::field label="…">`'s emitted `<label for="$id">` did not reach the internal combobox input (whose id is `$id-input`, not `$id`), so screen readers + axe's `label` rule reported an unlabeled form element. The component now synthesizes an `aria-label` via the fallback chain above and emits it on the internal input. WCAG 2.1 AA-compliant for any developer that has been wrapping multi-select in a `<x-wirekit::field>` with a `label` — no developer-side change required. Backward compatible.
- **`<x-wirekit::range-slider>` thumb-circles now sit vertically centered ON the track line.** Pre-fix, the two thumb `<div>`s were siblings of the track div, so their `top: 50%; transform: translateY(-50%);` resolved relative to the FULL component wrapper (track + edge labels + value display) and the thumbs visually dropped BELOW the track line. The thumbs now live INSIDE the track div, so their vertical centering resolves to the 8 px track height and the circles sit pixel-centered on the line.
- **`<x-wirekit::range-slider>` edge labels now show the slider BOUNDS, not the current values.** Pre-fix, the bottom-of-component value display rendered `minVal` / `maxVal` (the current handle positions) at the container edges via `justify-between`. The result was that the visible label "20" sat at position 0 % of the container, while the thumb representing value 20 sat at its proportional position (e.g. 6.7 % on a 0–300 range), and the two looked disconnected — and when the values matched the bounds exactly, the label and the thumb collided at the same pixel. Now: the edge labels render the constant slider min / max (`{{ $min }}` and `{{ $max }}`), and the current values surface as a tooltip-style badge ABOVE each thumb that tracks the handle horizontally as it moves. Screen-reader users still get a live announcement of the current range via an `aria-live="polite"` text node ("Range: 20 to 200") that updates on drag.
- **Prop validation no longer crashes the whole page in dev HTTP requests.** Pre-fix, `<x-wirekit::heading size="4xl">` (or any other invalid prop value) threw `InvalidArgumentException` in `APP_DEBUG=true` and the whole blade view 500'd. Now: strict-mode is split into two paths. CLI / Pest / `wirekit.validation.throw_on_invalid=true` still throws (fail-fast — the signal a dev wants in a test or artisan command). HTTP dev requests log at ERROR level and render with the first-allowed fallback so a single prop typo doesn't take down the entire page. The dev still sees the typo loudly in the log; iteration continues without a request-response restart.
- **`<x-wirekit::button>` warns at log level on unknown prop keys.** Pre-fix, `<x-wirekit::button variant="ghost">` (the prop is `surface`, not `variant`) was silently swallowed — the button rendered with default `surface="filled"` and the developer got no signal that their intended `ghost` treatment didn't apply. The new `WireKit::warnUnknownProps()` helper logs a Levenshtein-ranked Did-you-mean warning for any unknown key not in the canonical Blade-passthrough allowlist (`aria-*`, `data-*`, `wire:*`, `x-*`, `@*`, `:*`, plus reserved HTML attrs). Public helper — opt-in for every component that wants the warning; `button` and `main` already adopt it.
- **`<x-wirekit::reading-spine>` hover-boundary flicker.** Cursor jitter (1–2 px) at the spine's top or left edge fired alternating `mouseenter`/`mouseleave` events on every frame, toggling the spine expanded ↔ collapsed once per frame and producing a visible flicker. Hover-collapse now debounces by 120 ms — a re-entry within that window cancels the pending collapse, absorbing the jitter. Expand still fires immediately on initial hover, so the responsive direction is unchanged. Focus-driven expansion (`expandOnFocus` / `collapseOnFocus`) is unaffected.
- **`<x-wirekit::reading-spine>` implicit horizontal scrollbar inside the spine.** The spine carried `overflow-y-auto` but no explicit `overflow-x`. Per CSS spec, setting one axis to `auto` while the other defaults to `visible` computes BOTH axes to `auto` — so long `white-space: nowrap` heading labels inside the spine produced a thin horizontal scrollbar gutter even though the ellipsis truncation already clipped them visually. Pinned to `overflow-x-hidden overflow-y-auto` for true vertical-only scroll.
- **Themed scrollbar (`.wk-scrollbar`) applied across every component with an internal scroll region.** `<x-wirekit::modal>` body, `<x-wirekit::drawer>` body, `<x-wirekit::alert-dialog>`, `<x-wirekit::code-block>`, `<x-wirekit::combobox>` dropdown list, `<x-wirekit::command-palette>` overlay + inner list, `<x-wirekit::kanban>` (horizontal row) + `<x-wirekit::kanban-column>` (vertical list), `<x-wirekit::main>`, `<x-wirekit::reading-spine>`, and `<x-wirekit::reading-toc>` previously showed the OS-default scrollbar (thick on Windows, hidden-until-scroll on macOS). Each now applies WireKit's themed scrollbar — thin width, theme-aware thumb / track colors, dark-mode reactive. `<x-wirekit::prose>` `<pre>` children inherit the same treatment via a `.wk-prose pre` CSS rule (no template change required). `<x-wirekit::multi-select>`, `<x-wirekit::scroll-area>`, and `<x-wirekit::table>` already carried `.wk-scrollbar` and are unchanged. Override the colors / width via `--color-wk-scrollbar-thumb`, `--color-wk-scrollbar-track`, `--color-wk-scrollbar-thumb-hover`, `--size-wk-scrollbar` in `:root {}`.