Skip to main content

WireKit v2.6.0

Minor release

Minor release. A wave of new components for data-dense SaaS apps, plus the earlier editor / layout additions and a broad set of enrichments to existing components — all additive and backward-compatible. The SaaS set: <x-wirekit::data-table> (client-mode sort / search / select / column-manager + a server contract), <x-wirekit::filter-builder> (active-filter chips + a typed add/edit popover), <x-wirekit::status-matrix> (a 2D grid of tristate / toggle / status / heat cells), <x-wirekit::notification-center> (a bell + grouped, realtime-capable panel), <x-wirekit::usage-meter> (usage-vs-limit meters + a plan-paywall gate), <x-wirekit::event-calendar> (month / week / agenda scheduling views), and <x-wirekit::map> (a MapLibre / Leaflet adapter with an accessible marker list). Also new: <x-wirekit::editor>, a Tiptap rich-text adapter; <x-wirekit::sticky-panel>, a sticky companion column; and <x-wirekit::collapsible>, a standalone WAI-ARIA disclosure. <x-wirekit::color-picker> gains a from-scratch popover HSV picker. Existing components grow: checkbox / radio size + variant="card"; a success (valid) state and field.set / field.legend grouping for form controls; per-tab icons / badges and vertical tabs; select <optgroup> groups; dropdown checkbox / radio items with shortcut hints; slider step marks + value tooltip; a vertical carousel; a frozen table column; textarea rows="auto"; an image-compare ratio prop; and an empty-state variant.

Security

  • The sandbox preview renderer no longer compiles developer-supplied prop or slot values as Blade. Pushery\WireKit\Sandbox\SandboxRenderer assembled its preview markup by interpolating prop and slot values into a Blade string that was then compiled — so a value containing Blade echo or directive syntax ({{ … }}, @…) was evaluated server-side rather than rendered as text. Values are now bound as runtime data and referenced through Blade expressions, so they never reach the Blade compiler as source. HTML-escaping of string values is unchanged. If you embed the sandbox renderer behind any untrusted input, update to this release.
  • The sandbox audit log neutralizes control characters in the component-name field. A component name carrying a tab or newline could forge an extra tab-delimited log record; control characters are now collapsed before the line is written.
  • The ApexCharts adapter's config merge skips prototype-polluting keys. As defense-in-depth, the deep-merge that layers your chart config over WireKit's themed defaults now drops __proto__ / constructor / prototype keys, so a config assembled from app-influenced JSON can't tamper with object prototypes.

Added

  • New <x-wirekit::data-table> — a sortable, searchable, selectable data table. The ergonomic client-mode wrapper handles the common case entirely in the browser: pass a rows array and a columns definition and it sorts (string + numeric, with aria-sort headers), searches (a filter box), selects rows (per-row checkboxes plus a tri-state select-all and a bulk-action bar), toggles column visibility (a column manager), and switches density. Cell types: text, number, and badge (a status word maps to a tinted intent pill). For large datasets the server flag emits sort-change / search-change / selection-change events so you re-query from Livewire (a documented WithDataTable trait is the contract). The table sits in a labeled, keyboard-reachable scroll region; slots cover toolbar, bulkActions, and rowActions.
  • New <x-wirekit::filter-builder> — an active-filter chip bar. Active filters render as removable chips; an "Add filter" popover walks the user from a field to an operator valid for that field's type (no contains on a number) to a typed value editor (text / number / select / date / bool). It emits the normalized [{field, op, value}] array as a filter-change event and as JSON on a hidden input, so wire:model and plain forms both bind; an optional searchable box emits its own search-change. Chips are keyboard-removable and the popover is a labeled role="dialog" with focus management.
  • New <x-wirekit::status-matrix> — a 2D grid of typed status cells. One engine, four cell types via cell-type: tristate (inherit → allow → deny, editable — a role-permission matrix), toggle (on/off — a preferences grid), status (an intent badge — a compliance/controls grid), and heat (a color-scaled value — a retention heatmap). Sticky row and column headers; editable cells emit the normalized cell map (cell-change + JSON bridge) and diff against a baseline. Every encoding is conveyed in text or shape, never color alone — tristate cells use distinct check / cross / dash glyphs and carry the state word in their aria-label, heat cells always print their value. The heat ramp is themeable via heatFrom → heatTo (a cold→hot color interpolation), and each heat value rides in a contrast-checked chip so it stays readable at any point on the ramp.
  • New <x-wirekit::notification-center> — a bell with a grouped notification panel. An unread badge opens a role="dialog" panel of grouped, actionable notifications with an optional single-select type filter (a radiogroup with arrow-key selection), an empty state, a "see all" footer, and optimistic realtime insertion (an optional window-event bridge for Laravel Echo). Activating a row marks it read and emits notification-action with { id, href } — items with href render as real links, and actionLabel adds a call-to-action line. Read changes emit notification-read / notification-read-all and mirror the unread count to a hidden input. The bell's accessible name carries the unread count in words, so the state never depends on the badge color; an open prop supports inline embeds.
  • New <x-wirekit::usage-meter> — usage-vs-limit meters for billing surfaces. Shows how much of a plan limit has been consumed with a labeled bar (composing <x-wirekit::progress>) and a used / limit (%) readout, shifting intent as usage approaches the limit. The over-limit and approaching-limit states are spelled out in words, never color alone. Ships <x-wirekit::usage-meter.panel> (a responsive grid of meters) and <x-wirekit::usage-meter.gate> (a plan-paywall wrapper that dims and inert-disables a gated action with a reason and an upgrade prompt). :limit="null" renders an unlimited tier; thresholds are config-overridable.
  • New <x-wirekit::event-calendar> — a scheduling calendar with month / week / agenda views. The month view is a day grid with event pills and a "+N more" overflow; the week view is an hour-row time grid with overlap-split event blocks and a current-time line; the agenda view is a chronological list grouped by day. Navigation and the view switcher recompute the visible window, and clicking an event emits event-click. A dayMarkers prop tags individual days as holiday, working, or blocked — blocked days carry a diagonal-hatch overlay (the wk-day-blocked class) and spell out the unavailable state in their accessible name, never relying on color alone. Built on CSS grid with no external dependency, token-themed, and distinct from <x-wirekit::calendar> (the date-picker widget). Every event is a focusable button with a full date/time label.
  • New <x-wirekit::map> — a map adapter (MapLibre / Leaflet peer dependency). WireKit ships the themed chrome, the declarative API, the Alpine glue, and — mandatory for accessibility — an always-present marker list; the heavy map engine and the tiles are your app's concern. With no library loaded it degrades gracefully to the marker list plus a placeholder. It diffs markers reactively for Echo-pushed live positions and emits marker-click on selection. Clicking a marker — on the map pin OR in the list — selects it: the map pans and the matching list entry is highlighted, so the two stay in sync. A marker's intent colors both its list dot and its map pin (the pin via MapLibre's marker color or a themed Leaflet pin). Each pin opens a hover bubble whose content follows the marker's data shape — a text-only label, a styled name + detail card (body), a photo card (image), or a photo-only bubble (tooltip: 'image') — and the selected list row highlights via highlight="ring|fill" in a choosable highlight-color. Both engines render on-canvas zoom controls, and an attribution prop passes tile attribution (e.g. © OpenStreetMap contributors) through to the map's attribution control — required by some tile providers' usage policies. The marker list is the screen-reader path — each entry a focusable button labeled with the name and coordinates, so the data never depends on the map being visible.
  • variant="card" on <x-wirekit::checkbox> and <x-wirekit::radio> — the selectable-card pattern. Turns the whole control into a bordered, fully-clickable card that highlights (accent border + tinted surface) when checked — ideal for feature toggles and pricing-tier pickers. It reacts to its own input via CSS :has(), so no JavaScript is required. variant="default" (the inline control + label) is unchanged.
  • size prop on <x-wirekit::checkbox> and <x-wirekit::radio>. Both controls now scale via sm / md (default) / lg, matching the size already on <x-wirekit::toggle> — so a form mixing checkboxes, radios, and switches stays visually consistent at any size. The box (or circle) and its checkmark / dot scale together. Defaults to md, so existing checkboxes and radios are unchanged.
  • success (valid) state on <x-wirekit::input>, <x-wirekit::select>, and <x-wirekit::textarea>. The mirror of the existing error state: pass a string to render a green border plus a confirmation message below the field (e.g. success="Username available"), or :success="true" for just the green border. The field stays valid — it never sets aria-invalid; the message is linked via aria-describedby instead. error always wins when both are set. Backed by a new --color-wk-border-success design token (aligned with --color-wk-success-text, exactly as --color-wk-border-error aligns with --color-wk-danger-text), so the border and confirmation text read as a coherent pair in both light and dark mode.
  • rows="auto" on <x-wirekit::textarea> grows the field with its content. No JavaScript — it uses the CSS field-sizing: content feature (supported across the WireKit browser baseline). The numeric rows still serves as the minimum height. Numeric rows behaves exactly as before.
  • ratio prop on <x-wirekit::image-compare> sizes the component itself. Image-compare layers two absolutely-positioned images, so it needs a box with explicit dimensions. Previously you wrapped it in an element with an aspect-ratio (or a fixed height); now pass ratio="16/9" (or "4/3", "1/1", …) directly and the component applies the aspect-ratio to itself — no wrapper needed. Additive and backward-compatible: omit ratio and the component keeps filling the height of whatever box you give it, so an existing <div style="aspect-ratio: …"><x-wirekit::image-compare … /></div> continues to work unchanged.
  • variant on <x-wirekit::empty-state>. default (no container chrome — unchanged), outline (a dashed-bordered placeholder that reads as a drop zone), or muted (a filled muted-surface card). Lets an empty state stand on its own as a card without wrapping it in a <x-wirekit::card>.
  • animation prop on <x-wirekit::skeleton> — shimmer / pulse / none. shimmer (default) is the gradient sweep; pulse is the lighter opacity fade (half the GPU layers); none is a static placeholder with no motion. The legacy :shimmer="false" still works (equivalent to animation="pulse"). All three respect prefers-reduced-motion: reduce.
  • <x-wirekit::dropdown> gains checkbox / radio menu items and a shortcut hint. <x-wirekit::dropdown.checkbox-item> is a self-toggling menuitemcheckbox (Alpine owns the checked state, seeded from :checked); <x-wirekit::dropdown.radio-item> is a menuitemradio whose selection is coordinated by a shared Alpine variable named via model. Any item (including these) accepts a shortcut prop that pins a keyboard-shortcut hint (e.g. ⌘K) to the inline-end. All follow the WAI-ARIA Menu pattern.
  • <x-wirekit::table> gains a stickyColumn prop. Freezes the leftmost column while the rest of the table scrolls horizontally — ideal for wide tables where the first column is the row's identity. The frozen column keeps a solid background, and pairs with stickyHeader (the top-left corner stays on top). (The footer/totals row was already available via <x-wirekit::table.foot>.)
  • <x-wirekit::carousel> gains orientation="vertical". Scrolls slides up/down instead of left/right — the previous/next buttons move to the top and bottom edges, the indicators stack on the side, and the carousel pins a default viewport height (override with a height class or style) so each slide fills it. Horizontal stays the default and is unchanged.
  • <x-wirekit::slider> gains step marks and a value tooltip. marks draws tick marks along the track — a list of positions ([0, 25, 50, 75, 100]) or a position-to-label map ([0 => 'Min', 100 => 'Max']). tooltip floats a value bubble above the thumb that follows it as the user drags. The slider reserves its own vertical space for both (no hand-padding around the field, no clipped bubble inside overflow: hidden ancestors, no labels overlapping the content below), and it carries the same min-width usability floor as <x-wirekit::range-slider> so it stays draggable in shrink-to-fit contexts (flex/grid auto items, table cells). Both features are off by default, so existing sliders are unchanged.
  • <x-wirekit::color-picker> gains a popover mode — a full custom HSV picker. Add popover for a design-system-consistent panel: a saturation/value plane, hue + alpha sliders, a format-aware text field (cycle HEX → RGB → HSL → OKLCH — oklch is WireKit's own token color space), a screen eyedropper (where supported), copy-to-clipboard with an inline copied-checkmark confirmation, your presets swatches, and a recent-colors history (localStorage). It's built from scratch — no third-party library, ~6 KB of glue. The plane and sliders are keyboard-operable (role="slider" + arrow keys). On touch devices you can keep the platform's own color sheet: opt-in native-on-mobile opens the OS dialog on touch-primary pointers (pointer: coarse) while desktop pointers keep the popover. The default (native <input type="color">) is unchanged byte-for-byte, and the form-binding contract is identical, so flipping popover on never breaks submission.
  • New <x-wirekit::editor> — a rich-text editor (Tiptap peer-dependency adapter). Drops into a Livewire form like <x-wirekit::textarea>: a Blade tag, a hidden form field, wire:model support, theme-token styling, full keyboard access. It adapts Tiptap (built on ProseMirror), which stays a peer dependency you install from npm — WireKit ships only the lightweight Alpine glue (and gracefully falls back to a <textarea> if Tiptap isn't loaded). Props cover toolbar presets (basic / full / custom), format (html / json), editable, placeholder, maxLength (renders a live, soft character counter in the bottom bar with a debounced screen-reader announcement), size, maxHeight (cap the editable area so a long document scrolls inside the field instead of growing the page), and the usual label / hint / error. The undo / redo toolbar buttons disable themselves when there is nothing to undo or redo. Ships with <x-wirekit::editor.toolbar> (a role="toolbar" of aria-pressed command buttons) and token-driven document typography (wk-editor-content). The content region is a role="textbox" with aria-multiline. Sanitize saved HTML on store (the docs page calls this out).
  • New <x-wirekit::sticky-panel> — a sticky companion-column layout primitive. Pins a panel beside an article while the page scrolls (a cart summary, filter sidebar, table of contents, comparison builder) using native CSS position: sticky for the pinning itself. It has optional non-scrolling header and footer slots with a scrollable body between them, and it's bounded to the viewport height (100dvh, so the footer never scrolls out of reach). The body is a keyboard-reachable scroll region (WCAG 2.1.1) with overscroll-behavior: contain, and shows top/bottom scroll shadows when it overflows (opt out with :scrollShadow="false") — they render as overlays above the content, so a hovered row at a scroll edge never covers them, and auto-hide at the scroll extremes. A pure-CSS wk-scroll-shadow utility class plus the overlay classes (wk-scroll-shadow-top / wk-scroll-shadow-bottom) are exposed for your own containers. Below the hideBelow breakpoint it un-sticks and flows inline (or set mobileBehavior="hide").
  • New <x-wirekit::collapsible> — a standalone single disclosure. One trigger toggles one collapsible region — the bare WAI-ARIA Disclosure pattern, no card chrome or group coordination. The trigger is a real <button> (keyboard-operable), wired with aria-expanded + aria-controls; the region animates open/closed via Alpine's x-collapse. Pass trigger="…" for a plain label or a trigger slot for rich content, and :open="true" to start expanded. Reach for it for a "read more" or an advanced-options panel; use <x-wirekit::accordion> when you have a coordinated group of panels.
  • <x-wirekit::select> options now support <optgroup> groups and per-option disabled. The :options array accepts three shapes you can mix freely: flat (['de' => 'Germany']), grouped (['Europe' => ['de' => 'Germany']] → an <optgroup label="Europe">), and per-option attributes (['de' => ['label' => 'Germany', 'disabled' => true]]). Flat options render exactly as before.
  • <x-wirekit::field> gains orientation="horizontal", plus new <x-wirekit::field.set> / <x-wirekit::field.legend> for grouped fields. orientation="horizontal" places the label in a column beside the control (the classic settings-form layout) with the error/hint still aligned under the control. <x-wirekit::field.set legend="…" hint="…"> renders a native <fieldset> + <legend> — the WCAG-recommended way to group a radio or checkbox set so the legend is announced before each control; use <x-wirekit::field.legend> in the slot for rich legend content. Defaults are unchanged (vertical).
  • <x-wirekit::tabs> gains per-tab icon + badge and a vertical orientation. Using the array-of-objects items shape, each tab can carry an icon (a leading glyph) and a badge (a trailing count/status chip): ['key' => 'inbox', 'label' => 'Inbox', 'icon' => 'inbox', 'badge' => 8]. orientation="vertical" stacks the tabs in a column beside their panels (settings screens, side nav) with the correct WAI-ARIA keyboard model — Up/Down arrows for a vertical tablist, Left/Right for horizontal. The tab bar was already horizontally scrollable when labels overflow. All additive; existing tabs are unchanged.
  • wirekit:install now stops with a clear message when the project is still on Tailwind CSS v3. WireKit's styles are built on the Tailwind v4 engine (@theme, @source, color-mix(), @property) and cannot run on v3. The Tailwind version lives in npm — out of reach of Composer's require constraints — so the install command is the first place WireKit can check it: a v3 project now aborts before anything is written and points you at npm install tailwindcss@latest @tailwindcss/vite@latest plus the Tailwind v4 upgrade guide, instead of failing later with a confusing wall of errors. wirekit:doctor reports the same guidance as a failed check. Detection only acts on positive v3 evidence, so a valid v4 (or an undetermined) setup is never blocked.
  • New --text-wk-2xs design token — the smallest text size in the scale. A micro/caption step below --text-wk-xs, for dense UI like table sub-labels, chip counts, and timestamps. Available to your own CSS like every other --text-wk-* token and documented in the theming reference.
  • <x-wirekit::replay-button> is now publicly documented. The component was always callable in the package; its documentation page, README listing, the Public CSS API catalog row, and the machine-readable component manifests now all surface it. The button re-mounts the closest [data-replay-target] ancestor from a saved snapshot — it powers the ↻ Replay control on docs.wirekit.app's live previews, and it's a ready-made building block for re-running an animation, resetting an interactive demo, or restoring a dismissed badge or alert in your own app.

Changed

  • dist/wirekit.js, dist/wirekit-alpine.js, and dist/wirekit.esm.js grew to absorb the new editor adapter, the from-scratch color picker, and the SaaS data components (data-table, filter-builder, status-matrix, notification-center, event-calendar, map). Raw sizes: wirekit.js ~148 KB (~43 KB gzip), wirekit-alpine.js ~194 KB (~59 KB gzip), wirekit.esm.js ~148 KB (~43 KB gzip). The core chart-only bundle (wirekit.core.js, ~11 KB) is unchanged.

Fixed

  • <x-wirekit::charts-apex> tooltip now HTML-escapes the series color before rendering. The unified tooltip renderer escaped the x-axis label, series name, and data value but interpolated the resolved series color into the marker's inline style="background: …" unescaped. An app that wired chart colors from unvalidated user input could break out of the attribute — now the color goes through the same escape helper as its sibling values, so only a valid CSS color reaches the DOM.
  • <x-wirekit::range-slider> value bubbles now track, clamp, and merge cleanly. Each thumb carries a live value bubble above its handle that follows the handle as you drag. At the ends the bubbles close flush with their handles instead of overhanging the track; when the two thumbs sit close enough that the bubbles would collide they collapse into a single combined "min – max" bubble centered between the handles — which now lines up at exactly the same height as the individual bubbles. All of it updates live during a drag.
  • <x-wirekit::slider tooltip> value bubble now sits over the thumb when the slider is given a width. Passing an explicit width could push the floating value bubble far to the side of the handle; the width is now applied so the bubble, the track, and the tick marks all share it, keeping the bubble aligned with the thumb (and the marks aligned with the track) at any size.
  • <x-wirekit::range-slider> and <x-wirekit::image-compare> no longer force a layout read on every drag frame. Both read the track's bounding rectangle once at the start of a drag instead of re-measuring it on each pointermove — the track doesn't move while dragging, so the per-frame measurement was wasted work that could cause jank on long tracks or low-end devices. Dragging is now smoother; the click-to-position path still measures fresh.
  • wirekit:doctor no longer false-warns that no font CSS was found when fonts are correctly published. The font-assets check scanned only the top level of public/vendor/wirekit/fonts/, but vendor:publish --tag=wirekit-fonts writes font CSS one level deeper (fonts/<category>/<name>/<name>.css) — so a correct publish was reported as "Font directory exists but no CSS files found". The check now scans recursively. On a fresh install using the default font (where the system-ui stack means no font CSS is published), the empty directory is reported as informational rather than a warning.