WireKit v2.6.0
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:: (client-mode sort / search / select / column-manager + a server contract), <x-wirekit:: (active-filter chips + a typed add/edit popover), <x-wirekit:: (a 2D grid of tristate / toggle / status / heat cells), <x-wirekit:: (a bell + grouped, realtime-capable panel), <x-wirekit:: (usage-vs-limit meters + a plan-paywall gate), <x-wirekit:: (month / week / agenda scheduling views), and <x-wirekit:: (a MapLibre / Leaflet adapter with an accessible marker list). Also new: <x-wirekit::, a Tiptap rich-text adapter; <x-wirekit::, a sticky companion column; and <x-wirekit::, a standalone WAI-ARIA disclosure. <x-wirekit:: 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\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 (WireKit\ Sandbox\ SandboxRenderer {{ … }},@…) 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/prototypekeys, so a config assembled from app-influenced JSON can't tamper with object prototypes.
Added
- New
<x-wirekit::— a sortable, searchable, selectable data table. The ergonomic client-mode wrapper handles the common case entirely in the browser: pass adata-table> rowsarray and acolumnsdefinition and it sorts (string + numeric, witharia-sortheaders), 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, andbadge(a status word maps to a tinted intent pill). For large datasets theserverflag emitssort-change/search-change/selection-changeevents so you re-query from Livewire (a documentedWithDataTabletrait is the contract). The table sits in a labeled, keyboard-reachable scroll region; slots covertoolbar,bulkActions, androwActions. - New
<x-wirekit::— 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 (nofilter-builder> containson a number) to a typed value editor (text/number/select/date/bool). It emits the normalized[{field, op, value}]array as afilter-changeevent and as JSON on a hidden input, sowire:modeland plain forms both bind; an optionalsearchablebox emits its ownsearch-change. Chips are keyboard-removable and the popover is a labeledrole="dialog"with focus management. - New
<x-wirekit::— a 2D grid of typed status cells. One engine, four cell types viastatus-matrix> 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), andheat(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 theiraria-label, heat cells always print their value. The heat ramp is themeable viaheatFrom→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::— a bell with a grouped notification panel. An unread badge opens anotification-center> 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 emitsnotification-actionwith{ id, href }— items withhrefrender as real links, andactionLabeladds a call-to-action line. Read changes emitnotification-read/notification-read-alland 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; anopenprop supports inline embeds. - New
<x-wirekit::— usage-vs-limit meters for billing surfaces. Shows how much of a plan limit has been consumed with a labeled bar (composingusage-meter> <x-wirekit::) and aprogress> 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::(a responsive grid of meters) andusage-meter. panel> <x-wirekit::(a plan-paywall wrapper that dims and inert-disables a gated action with a reason and an upgrade prompt).usage-meter. gate> :limit="null"renders an unlimited tier; thresholds are config-overridable. - New
<x-wirekit::— 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 emitsevent-calendar> event-click. AdayMarkersprop tags individual days as holiday, working, or blocked — blocked days carry a diagonal-hatch overlay (thewk-day-blockedclass) 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::(the date-picker widget). Every event is a focusable button with a full date/time label.calendar> - New
<x-wirekit::— 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 emitsmap> marker-clickon 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'sintentcolors 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 viahighlight="ring|fill"in a choosablehighlight-color. Both engines render on-canvas zoom controls, and anattributionprop 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::andcheckbox> <x-wirekit::— 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 CSSradio> :has(), so no JavaScript is required.variant="default"(the inline control + label) is unchanged.sizeprop on<x-wirekit::andcheckbox> <x-wirekit::. Both controls now scale viaradio> sm/md(default) /lg, matching thesizealready on<x-wirekit::— 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 totoggle> md, so existing checkboxes and radios are unchanged.success(valid) state on<x-wirekit::,input> <x-wirekit::, andselect> <x-wirekit::. The mirror of the existing error state: pass a string to render a green border plus a confirmation message below the field (e.g.textarea> success="Username available"), or:success="true"for just the green border. The field stays valid — it never setsaria-invalid; the message is linked viaaria-describedbyinstead.erroralways wins when both are set. Backed by a new--color-wk-border-successdesign token (aligned with--color-wk-success-text, exactly as--color-wk-border-erroraligns 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::grows the field with its content. No JavaScript — it uses the CSStextarea> field-sizing: contentfeature (supported across the WireKit browser baseline). The numericrowsstill serves as the minimum height. Numericrowsbehaves exactly as before.ratioprop on<x-wirekit::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 animage-compare> aspect-ratio(or a fixed height); now passratio="16/9"(or"4/3","1/1", …) directly and the component applies the aspect-ratio to itself — no wrapper needed. Additive and backward-compatible: omitratioand the component keeps filling the height of whatever box you give it, so an existing<div style="aspect-ratio: …"><x-wirekit::continues to work unchanged.image-compare … /></div> varianton<x-wirekit::.empty-state> default(no container chrome — unchanged),outline(a dashed-bordered placeholder that reads as a drop zone), ormuted(a filled muted-surface card). Lets an empty state stand on its own as a card without wrapping it in a<x-wirekit::.card> animationprop on<x-wirekit::—skeleton> shimmer/pulse/none.shimmer(default) is the gradient sweep;pulseis the lighter opacity fade (half the GPU layers);noneis a static placeholder with no motion. The legacy:shimmer="false"still works (equivalent toanimation="pulse"). All three respectprefers-reduced-motion: reduce.<x-wirekit::gains checkbox / radio menu items and adropdown> shortcuthint.<x-wirekit::is a self-togglingdropdown. checkbox-item> menuitemcheckbox(Alpine owns the checked state, seeded from:checked);<x-wirekit::is adropdown. radio-item> menuitemradiowhose selection is coordinated by a shared Alpine variable named viamodel. Any item (including these) accepts ashortcutprop that pins a keyboard-shortcut hint (e.g.⌘K) to the inline-end. All follow the WAI-ARIA Menu pattern.<x-wirekit::gains atable> stickyColumnprop. 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 withstickyHeader(the top-left corner stays on top). (The footer/totals row was already available via<x-wirekit::.)table. foot> <x-wirekit::gainscarousel> 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 orstyle) so each slide fills it. Horizontal stays the default and is unchanged.<x-wirekit::gains stepslider> marksand a valuetooltip.marksdraws tick marks along the track — a list of positions ([0, 25, 50, 75, 100]) or a position-to-label map ([0 => 'Min', 100 => 'Max']).tooltipfloats 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 insideoverflow: hiddenancestors, no labels overlapping the content below), and it carries the same min-width usability floor as<x-wirekit::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.range-slider> <x-wirekit::gains acolor-picker> popovermode — a full custom HSV picker. Addpopoverfor a design-system-consistent panel: a saturation/value plane, hue + alpha sliders, a format-aware text field (cycle HEX → RGB → HSL → OKLCH —oklchis WireKit's own token color space), a screen eyedropper (where supported), copy-to-clipboard with an inline copied-checkmark confirmation, yourpresetsswatches, 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-innative-on-mobileopens 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 flippingpopoveron never breaks submission.- New
<x-wirekit::— a rich-text editor (Tiptap peer-dependency adapter). Drops into a Livewire form likeeditor> <x-wirekit::: a Blade tag, a hidden form field,textarea> wire:modelsupport, 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 covertoolbarpresets (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 usuallabel/hint/error. The undo / redo toolbar buttons disable themselves when there is nothing to undo or redo. Ships with<x-wirekit::(aeditor. toolbar> role="toolbar"ofaria-pressedcommand buttons) and token-driven document typography (wk-editor-content). The content region is arole="textbox"witharia-multiline. Sanitize saved HTML on store (the docs page calls this out). - New
<x-wirekit::— 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 CSSsticky-panel> position: stickyfor the pinning itself. It has optional non-scrollingheaderandfooterslots 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) withoverscroll-behavior: contain, and shows top/bottom scroll shadows when it overflows (opt out with:scroll) — 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-CSSShadow="false" wk-scroll-shadowutility class plus the overlay classes (wk-scroll-shadow-top/wk-scroll-shadow-bottom) are exposed for your own containers. Below thehideBelowbreakpoint it un-sticks and flows inline (or setmobile).Behavior="hide" - New
<x-wirekit::— 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 realcollapsible> <button>(keyboard-operable), wired witharia-expanded+aria-controls; the region animates open/closed via Alpine'sx-collapse. Passtrigger="…"for a plain label or atriggerslot for rich content, and:open="true"to start expanded. Reach for it for a "read more" or an advanced-options panel; use<x-wirekit::when you have a coordinated group of panels.accordion> <x-wirekit::options now supportselect> <optgroup>groups and per-optiondisabled. The:optionsarray 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::gainsfield> orientation="horizontal", plus new<x-wirekit::/field. set> <x-wirekit::for grouped fields.field. legend> 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::renders a nativefield. set legend="…" hint="…"> <fieldset>+<legend>— the WCAG-recommended way to group a radio or checkbox set so the legend is announced before each control; use<x-wirekit::in the slot for rich legend content. Defaults are unchanged (vertical).field. legend> <x-wirekit::gains per-tabtabs> icon+badgeand averticalorientation. Using the array-of-objects items shape, each tab can carry anicon(a leading glyph) and abadge(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:installnow 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'srequireconstraints — so the install command is the first place WireKit can check it: a v3 project now aborts before anything is written and points you atnpm install tailwindcss@latest @tailwindcss/plus the Tailwind v4 upgrade guide, instead of failing later with a confusing wall of errors.vite@latest wirekit:doctorreports 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-2xsdesign 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::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 closestreplay-button> [data-replay-target]ancestor from a saved snapshot — it powers the↻ Replaycontrol 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/, andwirekit-alpine. js dist/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. esm. js wirekit.js~148 KB (~43 KB gzip),wirekit-alpine.~194 KB (~59 KB gzip),js wirekit.esm.js~148 KB (~43 KB gzip). The core chart-only bundle (wirekit.core.js, ~11 KB) is unchanged.
Fixed
<x-wirekit::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 inlinecharts-apex> 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::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.range-slider> <x-wirekit::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 tickslider tooltip> marksall share it, keeping the bubble aligned with the thumb (and the marks aligned with the track) at any size.<x-wirekit::andrange-slider> <x-wirekit::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 eachimage-compare> 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:doctorno longer false-warns that no font CSS was found when fonts are correctly published. The font-assets check scanned only the top level ofpublic/, butvendor/ wirekit/ fonts/ vendor:publish --tag=wirekit-fontswrites 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.