WireKit v2.4.0
Minor release. New display components (stage-card, activity-row),
an intent color axis on stat and progress, deterministic
avatar from-initials, badge tooltip / leadingIcon support, a
md-compact size tier on inputs and buttons, and chart tooltip value
formatting (valueDecimals / valuePrefix / valueSuffix). Plus
reading-primitive boundary scoping, a hero tightOnMobile option,
menubar / navigation-menu dropdown positioning fixes, sortable-table
fixes, and a round of mobile-layout polish. Additive-only — every
existing template renders identically. No back-compat breaks.
Added
- New
<x-wirekit::component. A pipeline / kanban / roadmap stage card: an intent-colored left stripe, a faintly intent-tinted body, an optional item-count pill (announced as "N items"), and an optional progress bar. Replaces the per-column inline-style block dashboards previously hand-rolled.stage-card> role="group"+aria-labelfrom the stagelabel. - New
<x-wirekit::component. An activity-feed / timeline / changelog row: a kind-colored leading dot, optional bold actor, content, right-aligned timestamp, and aactivity-row> badgeslot. Thekind→ color map (commit / merge / deploy / comment / system / user) is extensible viaconfig('wirekit.; unknown kinds fall back to a muted dot. The dot is decorative; the kind is exposed as a visually-hidden label.components. activity-row. kinds') <x-wirekit::KPI-tile chrome. Settingstat intent="…"> intent(primary / success / warning / danger / info / neutral) gives the stat an intent-colored left stripe + a faintly tinted body and turns it into a labeledrole="group". Default (nointent) renders the existing plain card surface unchanged.<x-wirekit::.progress intent="…"> intentis now the canonical color axis (matching badge / button / alert), addinginfoandneutral.variantkeeps working as a back-compat alias;intentwins when both are set.<x-wirekit::. Derives a deterministic, theme-independent background color from the initials hash (AA-contrast background + white text) so the same person always renders the same color.avatar from-initials> WireKit::exposes the same palette for custom inline chips.avatar Palette For($key) size="md-compact"on<x-wirekit::,input> <x-wirekit::, andselect> <x-wirekit::. A 2.25rem middle tier betweenbutton> smandmdfor dense list/filter toolbars.<x-wirekit::gainsbadge> tooltipandleadingIcon.tooltipsets a discoverable nativetitle;leadingIconrenders a decorative status glyph before the label. Every badge also gains a subtle depth shadow (inset ring + faint drop shadow) so it reads less flat.<x-wirekit::. Opt out of body padding for edge-to-edge content (a flush hero image or full-bleed table).card. body :padded="false"> <x-wirekit::accepts an optionaldropdown> <x-slot:trigger>named slot. When the slot is supplied, the parent auto-wraps the trigger element in<x-wirekit::and the default slot indropdown. trigger> <x-wirekit::, so the canonical composition no longer has to be repeated per dropdown. The explicit-sub-component form keeps working unchanged — use whichever reads better for the page. Both forms produce identical rendered HTML and ARIA wiring.dropdown. panel> - Reading-
boundaryprop onreading-progress,reading-spine, andreading-bookmark.* Defaultnullpreserves the existing viewport-pinned behavior. Passboundary="container"to scope the primitive to its nearest positioned ancestor instead — useful when embedding a reading surface inside a modal body, sidebar pane, preview iframe, or Livewire panel. The progress bar swaps toposition: sticky; the spine and bookmark swap to Tailwindabsolute. Documented under "Scoping a primitive to its parent" with the required wrapper recipe. <x-wirekit::opt-in prop. Whenhero tightOnMobile> trueon asize="lg"hero, mobile padding drops from--space-wk-section-md(5 rem) to--space-wk-section-sm(3 rem) while desktop keeps--space-wk-section-lg(7 rem) — useful for dark-variant heroes with gradient overlays where the previous 5 rem mobile padding showed as a visible dead-zone on iPhone-class viewports. No-op forsize="sm"andsize="md"(mobile already runs at the tightest tier).- New
docs/page. A context-matrix reference for WireKit's prop-validation gate: when does an invalid value throw vs log + fall back, how to grep the Laravel log for silent typos that shipped, the env-knob recipe to opt staging into fail-loudly mode, and a Pest test pattern for asserting strict-mode rejection.strict-validation. md <x-wirekit-chart>gainsvalueDecimals,valuePrefix, andvalueSuffix(ApexCharts). Round raw float y-values in the tooltip to N decimal places and wrap them with a unit or currency affix —valueturns a hoveredDecimals="2" valueSuffix=" ms" 50.523626895740676into50.52 ms. All three default tonull(raw value, no affix), so existing charts are unchanged. No-op on the Chart.js adapter..wk-reading-contentresponsive article wrapper class. Wrap the<article>in a reading-spine composition withclass="wk-reading-content"and it reserves the spine's inline-end gutter only atmd+ (where the spine is visible) while using the full width belowmd(wherereading-spinedefaults tohideBelow="md"and is hidden). Replaces the hand-rolled inlinemax-width: calc(100% - 12rem)/padding-right: 12remreserve, which — being an inline style — could not@mediaand so stayed applied on phones, collapsing the article to a single character per line. Override the gutter with--reading-content-spine-gutter(default12rem).
Fixed
<x-wirekit::now actually reorders rows when a header is clicked. The Alpine sort read the table body from the clickedtable alpine-sort> <th>instead of the table root, so it found no<tbody>and bailed out — the sort-direction arrow flipped but the rows never moved. Clicking a sortable header now sorts the rows as expected.<x-wirekit::sorts date and other non-numeric columns correctly. Numeric detection usedtable alpine-sort> parseFloat, which read a leading number out of any string — so every ISO date (2025-01-15) collapsed to its year (2025), making a whole date column compare as equal and never reorder. Values are now only compared numerically when the entire cell is a number; dates, sizes (12px), prices ($5), and counts (3 items) fall through to locale-aware comparison, which orders ISO dates chronologically.<x-wirekit::no longer renders a doubled border under the last row. The inter-row separator was an inlinedata-list> border-bottomon every item, including the last — which doubled against the container's own bottom border into a 2px seam. The separator now omits the final row.- Muted text stays legible on
<x-wirekit::/hero> <x-wirekit::cta> variant="dark"andvariant="accent". A nested<x-wirekit::previously resolved against the light-theme muted color on the near-black surface — invisible. It now reads a high-contrast muted color scoped to the dark/accent surface, auto-adapting to any theme.text variant="muted"> - Raw
<td>/<th>cells inside<x-wirekit::now receive readable padding. Previously a raw table (cells not wrapped in thetable> table.*sub-components) rendered cramped flush-to-border; raw cells now get the same paddingtable.tduses, complementing the existing debug-mode warning. An edge-to-edgecard.bodypads a flush raw table's cells too. <x-wirekit::is more usable on touch devices. The thumb grows to a comfortable 28px on coarse pointers,range-slider> touch-action: nonestops a horizontal drag from scrolling the page, and discrete sliders (step > 1) with a readable step count render snap tick-marks.wirekit:export-jsonmanifest no longer false-positively flags class-side public properties as required template slots. Class-based components (currently<x-wirekit-chart>) expose public properties like$alpineandComponent $chartConfigthat the Blade template references as{{ $name }}. The slot-detection layer treated these as<x-slot:...>requirements, so the manifest's chart entry emitted{name: alpine— leading AI tooling to wrap chart content inComponent, required: true} <x-slot:alpineand produce broken Blade. The exclude is applied to bothComponent> wirekit:export-jsonand the.wirekit-schema.file written byjson wirekit:install.<x-wirekit::tab bar is now horizontally scrollable on narrow viewports. The tablist previously used a non-scrollingtabs> inline-flex, so a layout with five or more tabs overflowed the viewport on mobile with no way to reach the off-screen tabs. The tablist now caps at the container width and scrolls (the standard mobile tab-bar pattern); tabs keep their natural label width (shrink-0+whitespace-nowrap) instead of squishing. Desktop rendering is unchanged. Keyboard access is preserved —role="tablist"with arrow-key navigation already owns its keyboard model.<x-wirekit::labels and values wrap long single tokens instead of overflowing. Adata-list> <dt>carriedwhite-space: nowrap, so a long single-word label (e.g. a long compound noun, a URL, or a file path) bled past its 33% track and pushed the row wider than its container on narrow viewports. Both<dt>and<dd>now useoverflow-wrap: anywhere+min-width: 0.<x-wirekit::andmenubar> <x-wirekit::dropdown panels now open at their trigger even inside a transformed ancestor. Both teleport theirnavigation-menu> position: fixedpanels to<body>(like modal / drawer / context-menu / tooltip), so a menubar or navigation-menu nested inside any element with atransform/filter/perspective/contain(which establishes a containing block for fixed descendants) no longer opens its dropdown far from the trigger. Keyboard navigation, outside-click close, and hover behavior are unchanged.<x-wirekit::no longer clips sibling cards in a multi-column KPI grid on mobile. A block-mode sparkline now carriessparkline> min-width: 0; overflow: hiddenso the ApexCharts canvas's fixed intrinsic width can't floor a surrounding CSS-grid track — previously arepeat(2, 1fr)KPI grid kept both columns at the chart's ~300px width and pushed the right-hand cells off-screen on narrow viewports.<x-wirekit::wraps to a second row on narrow viewports instead of cramming. The leading (search) cluster usedtoolbar> min-w-0, so on a phone the search field collapsed toward zero width while the filter selects and action button squeezed beside it. The leading cluster now keeps a usable minimum width (min-w-[min(100%,14rem)], growing to fill spare space) and the default-slot wrapper carriesflex-wrap justify-between, so the filters and actions drop to a second row once they no longer fit beside a usable search field. Desktop layout is unchanged.
Changed
<x-wirekit::,reading-progress> <x-wirekit::,reading-spine> <x-wirekit::reading-bookmark> boundaryprop now accepts CSS-selector strings in addition to'container'. Pass a selector likeboundary="#article"and the Alpine init logs aconsole.warnwhen no ancestor matches — runtime verification that the developer's intended ancestor exists. The default (null) and'container'shapes are unchanged.<x-wirekit::gains areading-shell> boundaryprop that pass-through to every composed primitive — set once on the shell and the entire reading surface stays inside the parent positioned ancestor.<x-wirekit::accessibility hardening — when the underlying SVG package source ships a baked-inicon> aria-hidden="true"on the SVG root (blade-heroicons does this), the component now strips that attribute and re-emits exactly one resolved aria-hidden value matching the developer's intent. Fixes a screen-reader regression where icons marked informative viaaria-label/role="img"were silently skipped by assistive tech.IconResolverfallthrough now correctly resolves bare heroicon-family names like<x-wirekit::toicon name="pencil-square"> heroicon-m-pencil-square. Previously the fallthrough probed a binding key that has never existed in blade-ui-kit/blade-icons, so every such call threw an "Unknown icon alias" exception.wirekit:doctor(wirekit:verify) gains an environment-tier silent-typo log scan. Walksstorage/forlogs/ laravel*.log WireKit [...]ERROR / WARNING lines emitted by the prop-validation fallback path and surfaces them as a WARN with example lines. SAFE-DEGRADE at every failure mode (missing log file, non-file LOG_CHANNEL, custom log channels) — the helper NEVER blocks the doctor's exit code. Opt-out viawirekit.config ordoctor. scan_ logs WIREKIT_.DOCTOR_ SCAN_ LOGS=false wirekit:export-jsonmanifest entries carry a newcomponent_kindfield (anonymous|class) so downstream AI tooling can branch on the value when generating composition shapes — anonymous components accept<x-slot:name>; class-based components accept only constructor-mapped props.- Hero docs ship a new "Tight mobile padding for
size='lg'" subsection demonstrating thetightOnMobileprop with a preview, plus a newtightOnMobilerow on the Props table. - Reading docs gain a new "Scoping a primitive to its parent" subsection with a contained-surface preview block and a 3-row behavior matrix covering
null/'container'/'<css-selector>'boundary shapes. - Dropdown docs gain a new "Composition forms" section walking both the quick-form (
<x-slot:trigger>) and the explicit-sub-component path with previews and a do-not-mix tip. - Icon docs + Variants & Intents docs carry explicit cross-reference notes documenting that the four status names (
info,success,warning,danger) appear deliberately in both the icon-alias namespace and the canonical intent-value enum — the shared keyword is a feature, not a namespace collision. - Chart docs Troubleshooting section names the silent-failure mode (chart adapter bundle not loaded at all → Alpine references an unknown factory) and contrasts it with the in-DOM advisory panel rendered when the adapter bundle IS loaded but the underlying chart library (
window./ApexCharts window.Chart) is missing. Chart.js + ApexCharts missing-libraryconsole.errornow emits ONCE per page-load (deduplicated) instead of once per chart instance.