Skip to main content

WireKit v2.3.0

Minor release

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 {}.