Skip to main content

WireKit v2.4.0

Minor release

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::stage-card> 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. role="group" + aria-label from the stage label.
  • New <x-wirekit::activity-row> component. An activity-feed / timeline / changelog row: a kind-colored leading dot, optional bold actor, content, right-aligned timestamp, and a badge slot. The kind → color map (commit / merge / deploy / comment / system / user) is extensible via config('wirekit.components.activity-row.kinds'); unknown kinds fall back to a muted dot. The dot is decorative; the kind is exposed as a visually-hidden label.
  • <x-wirekit::stat intent="…"> KPI-tile chrome. Setting intent (primary / success / warning / danger / info / neutral) gives the stat an intent-colored left stripe + a faintly tinted body and turns it into a labeled role="group". Default (no intent) renders the existing plain card surface unchanged.
  • <x-wirekit::progress intent="…">. intent is now the canonical color axis (matching badge / button / alert), adding info and neutral. variant keeps working as a back-compat alias; intent wins when both are set.
  • <x-wirekit::avatar from-initials>. 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. WireKit::avatarPaletteFor($key) exposes the same palette for custom inline chips.
  • size="md-compact" on <x-wirekit::input>, <x-wirekit::select>, and <x-wirekit::button>. A 2.25rem middle tier between sm and md for dense list/filter toolbars.
  • <x-wirekit::badge> gains tooltip and leadingIcon. tooltip sets a discoverable native title; leadingIcon renders 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::card.body :padded="false">. Opt out of body padding for edge-to-edge content (a flush hero image or full-bleed table).
  • <x-wirekit::dropdown> accepts an optional <x-slot:trigger> named slot. When the slot is supplied, the parent auto-wraps the trigger element in <x-wirekit::dropdown.trigger> and the default slot in <x-wirekit::dropdown.panel>, 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.
  • Reading- boundary prop on reading-progress, reading-spine, and reading-bookmark.* Default null preserves the existing viewport-pinned behavior. Pass boundary="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 to position: sticky; the spine and bookmark swap to Tailwind absolute. Documented under "Scoping a primitive to its parent" with the required wrapper recipe.
  • <x-wirekit::hero tightOnMobile> opt-in prop. When true on a size="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 for size="sm" and size="md" (mobile already runs at the tightest tier).
  • New docs/strict-validation.md 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.
  • <x-wirekit-chart> gains valueDecimals, valuePrefix, and valueSuffix (ApexCharts). Round raw float y-values in the tooltip to N decimal places and wrap them with a unit or currency affix — valueDecimals="2" valueSuffix=" ms" turns a hovered 50.523626895740676 into 50.52 ms. All three default to null (raw value, no affix), so existing charts are unchanged. No-op on the Chart.js adapter.
  • .wk-reading-content responsive article wrapper class. Wrap the <article> in a reading-spine composition with class="wk-reading-content" and it reserves the spine's inline-end gutter only at md+ (where the spine is visible) while using the full width below md (where reading-spine defaults to hideBelow="md" and is hidden). Replaces the hand-rolled inline max-width: calc(100% - 12rem) / padding-right: 12rem reserve, which — being an inline style — could not @media and so stayed applied on phones, collapsing the article to a single character per line. Override the gutter with --reading-content-spine-gutter (default 12rem).

Fixed

  • <x-wirekit::table alpine-sort> now actually reorders rows when a header is clicked. The Alpine sort read the table body from the clicked <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::table alpine-sort> sorts date and other non-numeric columns correctly. Numeric detection used 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::data-list> no longer renders a doubled border under the last row. The inter-row separator was an inline border-bottom on 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" and variant="accent". A nested <x-wirekit::text variant="muted"> 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.
  • Raw <td> / <th> cells inside <x-wirekit::table> now receive readable padding. Previously a raw table (cells not wrapped in the table.* sub-components) rendered cramped flush-to-border; raw cells now get the same padding table.td uses, complementing the existing debug-mode warning. An edge-to-edge card.body pads a flush raw table's cells too.
  • <x-wirekit::range-slider> is more usable on touch devices. The thumb grows to a comfortable 28px on coarse pointers, touch-action: none stops a horizontal drag from scrolling the page, and discrete sliders (step > 1) with a readable step count render snap tick-marks.
  • wirekit:export-json manifest no longer false-positively flags class-side public properties as required template slots. Class-based components (currently <x-wirekit-chart>) expose public properties like $alpineComponent and $chartConfig that the Blade template references as {{ $name }}. The slot-detection layer treated these as <x-slot:...> requirements, so the manifest's chart entry emitted {name: alpineComponent, required: true} — leading AI tooling to wrap chart content in <x-slot:alpineComponent> and produce broken Blade. The exclude is applied to both wirekit:export-json and the .wirekit-schema.json file written by wirekit:install.
  • <x-wirekit::tabs> tab bar is now horizontally scrollable on narrow viewports. The tablist previously used a non-scrolling 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::data-list> labels and values wrap long single tokens instead of overflowing. A <dt> carried white-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 use overflow-wrap: anywhere + min-width: 0.
  • <x-wirekit::menubar> and <x-wirekit::navigation-menu> dropdown panels now open at their trigger even inside a transformed ancestor. Both teleport their position: fixed panels to <body> (like modal / drawer / context-menu / tooltip), so a menubar or navigation-menu nested inside any element with a transform / 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::sparkline> no longer clips sibling cards in a multi-column KPI grid on mobile. A block-mode sparkline now carries min-width: 0; overflow: hidden so the ApexCharts canvas's fixed intrinsic width can't floor a surrounding CSS-grid track — previously a repeat(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::toolbar> wraps to a second row on narrow viewports instead of cramming. The leading (search) cluster used 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 carries flex-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> boundary prop now accepts CSS-selector strings in addition to 'container'. Pass a selector like boundary="#article" and the Alpine init logs a console.warn when no ancestor matches — runtime verification that the developer's intended ancestor exists. The default (null) and 'container' shapes are unchanged.
  • <x-wirekit::reading-shell> gains a boundary prop 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::icon> accessibility hardening — when the underlying SVG package source ships a baked-in 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 via aria-label / role="img" were silently skipped by assistive tech.
  • IconResolver fallthrough now correctly resolves bare heroicon-family names like <x-wirekit::icon name="pencil-square"> to 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. Walks storage/logs/laravel*.log for 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 via wirekit.doctor.scan_logs config or WIREKIT_DOCTOR_SCAN_LOGS=false.
  • wirekit:export-json manifest entries carry a new component_kind field (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 the tightOnMobile prop with a preview, plus a new tightOnMobile row 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-library console.error now emits ONCE per page-load (deduplicated) instead of once per chart instance.