Skip to main content
WireKit

WireKit v2.50.0

Minor release

Minor. MapLibre GL 6 is published as an ES module only, and 6.4.1 is the first release with a fix for a critical advisory that no 5.x release will get — so the map takes its engine as a module now, and its guide teaches a setup that actually draws. A modal whose content outgrew the window lost its top: the header and the first lines sat above the start of the scroll area, where no scroll could reach them. And a data table inside a Livewire component forgot its search text, density and selection every time the server answered, while a server-mode table had no honest way to say it was waiting.

Added

  • <x-wirekit::quick-replies> puts a row of suggested answers under a message. Each chip dispatches what the reader picked — value, label and the chip's index — and your application decides whether that sends the reply, fills the composer or calls a tool. A suggestion is a string, or an array with a value that differs from the label and a disabled for one that cannot be taken right now. The row is a named group of buttons rather than a toolbar, since that role promises an arrow-key model this has none of; every chip keeps the target floor the rest of the library uses, and the row wraps on a phone instead of running off the side.
  • The event calendar can tell you which day was pressed, and show that day's events. With selectable-days, each day number in the month view becomes a button that dispatches wirekit:event-calendar-day-select with the date and the ids of its events. day-detail also lists the pressed day's events under the grid, and +N more opens that list instead of switching to the week view. Both are off by default. A theme restyles each state of the day number through the day-number-today, day-number-selected, day-number and day-number-outside keys, whether or not the days are selectable.
  • The event calendar can filter by category. Give events a category and set filterable, and one toggle per category appears above the views. A hidden category leaves every view, including the month grid's +N more and the week grid's columns, and a polite status announces how many events remain. Events without a category are never filtered. The toggles take their look from the category-shown and category-hidden keys.
  • The event calendar shows who attends an event. Give an event attendees, as names or with an avatar, and the agenda and the day list show up to three faces beside its title, then a count. Every name joins the row's accessible name, joined the way the calendar's language joins a list.
  • wirekit:doctor checks that every page shell your layouts render loads WireKit. Finding the directives somewhere in your views said nothing about whether the file your login pages render carries them. The check follows the app and auth layouts to the files that close their <head> and <body>, and warns about any that doesn't load WireKit.
  • registerMapEngine() hands the map its engine without a global. MapLibre GL 6 sets no window.maplibregl, which was the only place the map looked. A bundled app passes the module namespace instead — registerMapEngine('maplibre', maplibregl), exported from wirekit.esm.js — and a map that mounted before its engine arrived boots the moment it is registered. A page that assigns window.maplibregl itself, or loads Leaflet's classic build, works as before.
  • <x-wirekit::activity-row> takes a detail slot and a kind-label prop. The detail slot holds block content under the line, such as the fields a change touched or a quoted comment, and starts flush with the text above it; the default slot sits inside the line and cannot hold a block. kind-label names the kind for a screen reader, which is how a custom kind gets a spoken name.
  • <x-wirekit::image-gallery> takes a zoomOverlay, and <x-wirekit::lightbox> a slideOverlay. itemOverlay covers the thumbnails only, so a label that belongs on the picture, such as an "AI-generated" marking, disappeared the moment the reader opened it. zoomOverlay takes the same ($item, $i) closure and lays its markup exactly over the enlarged image, shown only while that image is; pass the same closure to both when a label has to travel with the image. The lightbox's slideOverlay is the same mechanism for a lightbox of its own.
  • <x-wirekit::message-group> prints a run of messages from one sender under one avatar and one name. Three messages in a row used to mean three identical headers, which turns a conversation into a table. The group carries the sender; the messages inside keep their own timestamps, their own role="article" and their own accessible names, and the repeated name stays in the accessibility tree — hidden with the visually-hidden pattern rather than removed, because a reader who lands on the third bubble still has to be able to say who wrote it. The stack's inner corners round tighter, written in logical properties, so a right-to-left thread mirrors without a second rule.
  • The MCP server exposes resources as well as tools. wirekit:mcp-serve answers resources/list and resources/read for wirekit://catalog, wirekit://themes, wirekit://recipes and wirekit://changelog, so an editor can attach the catalog to a conversation once instead of calling a tool on every turn, and a client's resource picker shows what the server knows. The changelog resource serves the newest version's section rather than the whole file, which is several hundred kilobytes.
  • <x-wirekit::feature> takes intent, and <x-wirekit::card> takes surface. Those are the names the rest of the kit uses for these two axes — intent for the color role, surface for how heavily it is applied — and these were the last two components asking for a different word. tone and variant keep working for the whole of v2 and mean exactly what they meant; the canonical name wins when a call site sets both. intent="primary" is tone="accent" and surface="outline" is variant="outlined", so moving a call site is a rename rather than a re-render. See Prop naming conventions.
  • Cards can move between the columns of a kanban board. Add cross-column to a sortable board and a column-id to each column: a card can then be dragged onto another column, or carried there with and while it is lifted with the keyboard, and Escape puts it back where it started. The move is reported once, from the column the card lands in, as a bubbling wirekit:sortable:moved event carrying both columns' new order. Without cross-column a board behaves exactly as before, arrow keys included.
  • The data table can start sorted. sort-key and sort-dir set the column and the direction it opens with. In server mode they mirror the order the server already applied, so aria-sort and the arrow describe the rows on screen; until now every column read "none" over a sorted table, and a screen reader said so. A column can also set sortStart: 'desc' to sort descending on its first click, which is what a column of counts is clicked for.
  • A data table cell can link to the row's own page. Give a column cellType: 'link' and name the row field that holds the URL in hrefKey: each cell becomes a real link in the kit's link style, so middle-click, the context menu and "copy link" all work. A row without a URL shows its value as text, and a URL that would run script, such as javascript:, is refused the same way.
  • <x-wirekit::grid>, bento-grid and feature-grid take scale, as row and stack do. WireKit has two spacing ladders that share the rung names xs to 2xl and differ from md up, and a grid's gap reached only the roomier one. scale="gap" points gap at the tighter --gap-wk-* ladder the components use inside themselves, so a card grid written as gap-[var(--gap-wk-md)] can become a grid without every gap growing by a third. The default is space, which is what gap has always meant.
  • <x-wirekit::text> takes break for an unbroken token. A checksum, a key or a long URL has no space to wrap at, so it widened its container, and on a phone the whole page slid sideways. break="anywhere" breaks it only where the line would otherwise overflow, all breaks between any two characters, and normal restores the default rules.
  • A server-mode data table takes search, the filter the server applied. The search field shows it on every render, so a page opened with a query in its address shows that query above the rows it filtered.

Changed

  • wirekit:csp-audit no longer says a factory is unregistered when it simply did not read the file that registers it. Both cases used one wording. Not registered means the page has no scope for the factory, so x-show never hides anything and the panel renders visible and dead; not found in the files this run read is a limit of the run, as on a package's views, whose registrations sit beside them. The report now counts the two separately in its heading and says on each line which one applies.
  • wirekit:doctor:props also finds a slot closing tag that Blade does not compile as one. A closing tag with a word directly behind it stops being a closing tag: Blade rewrites it to @endslot with nothing after it, the word joins the directive's name, and the slot never closes — its content lands in the default slot, the component is handed an empty string where it expected the slot, and that directive name is printed on the page as text. Nothing throws and nothing is logged, so a linter is where it can be found. The exit contract is unchanged, --fail-on=none included.
  • The map's missing-engine hint waits until the page has loaded. It used to fire the moment a map mounted without an engine, so an engine loaded on purpose after the first render printed a console error on a page that was about to work. The hint now waits for the load event and a three-second grace period, and stays silent if an engine arrives first.
  • wirekit:doctor:props tells you which of two prop spellings the kit calls canonical. Several components accept variant or tone where the shared vocabulary says intent, and card accepts variant where it says surface. Both spellings resolve to the same value and both keep working, so the linter names the canonical one at the moment a template writes the other and leaves the exit code alone unless you pass --fail-on-legacy-axis, once your own tree has converted; --fail-on=none still overrides both. The pairs are read out of the component templates, so a component that gains its canonical spelling in a later release is covered by the same command on the day it ships.
  • wirekit:make and the long-form-article recipe scaffold the canonical prop name. The generated dashboard, settings and article pages wrote <x-wirekit::text variant="muted">; they write intent now.
  • Three controls in the documentation's live prop editor take the canonical name. alert and callout offer intent, card offers surface with outline spelled the way button spells it. One control per axis: offering both names would let you set both and then wonder which won. Every component still accepts the older name.

Fixed

  • A form control keeps the aria-describedby you give it, next to its own hint or error. color-picker, date-picker, input, number-input, password-input, radio, segmented-control, select, slider, textarea, time-picker and toggle wrote their own aria-describedby beside yours when they showed a hint or an error, and a browser keeps only the first copy of an attribute; on segmented-control yours came first and took the control's own error out of its description. editor, file-upload, inline-edit, multi-select, rating and tags-input put yours on the wrapper around the control, where it describes nothing, and otp-input dropped it. Your ids now join the control's list after the component's own, as checkbox and range-slider already did, so a notice elsewhere on the page can describe the field.
  • An avatar group rendered by Alpine's x-for no longer pulls its first avatar over what sits before it. The overlap applied to every child after the first, and in an x-for stack the first child is the <template> element, so the first visible avatar was treated as a second one. The rule now skips template elements.
  • The breadcrumb's structured data leaves out a step that has no page. A trail with a navigation group in the middle emitted that step without a URL, and Google discards a whole BreadcrumbList over one such entry, so the search result showed a bare URL instead of the trail. The group stays in the visible trail; the JSON-LD skips it and counts its positions over what remains, and consecutive steps with the same URL become one entry. Schema::breadcrumbList() follows the same rule and now takes null for a step without a page.
  • An unlinked step in the middle of a breadcrumb no longer looks like the current page. It borrowed the current page's classes, so a trail with a navigation group in it showed two crumbs in the emphasis color, and personalizing current restyled both. It renders muted now, in an ancestor block of its own that WireKit::personalize() reaches separately.
  • wirekit:install wires WireKit into the Livewire Starter Kit. The kit's layouts/app.blade.php hands the page to a shell such as layouts/app/sidebar.blade.php, and only the shell holds the <head> and <body>. The installer looked at the wrapper alone, found nowhere to insert, and reported the directives as already present, so WireKit's stylesheet and scripts reached no page. It now follows each layout to the files that close <head> and <body>, wires the auth layout's shells as well, including the alternative shells the kit keeps beside the ones in use, and names every file it changed. Adding WireKit to the Livewire Starter Kit walks through the result.
  • wirekit:doctor names the file the install actually creates. With no layout yet, its hint said the install would create resources/views/components/layouts/app.blade.php. The install runs Livewire's livewire:layout, which writes resources/views/layouts/app.blade.php.
  • wirekit:publish-fonts --prune deletes only what it published. A family directory that was a link was walked like any other, so the prune deleted the files at the link's target, outside public/vendor/wirekit/fonts, and only then stopped on the link itself; a linked category directory did the same for every family inside it, without stopping at all. Such a directory now stays where it is, the output names it, and the command exits 1. See wirekit:publish-fonts.
  • Every segmented control segment keeps a 24px target, whatever size the text is themed to. A segment's height came from its padding and the line height of its text alone, so a theme with a smaller type scale pushed the small segments below the WCAG 2.5.8 minimum without a sound. The segments now take --size-wk-target-min as their floor; at the default tokens nothing visible changes.
  • A context menu opened by a right-click that had to scroll the page stays open. Browsers announce a scroll a frame after it happens, so when the right-click itself brought the trigger into view, that scroll's own event arrived after the menu had opened, and the menu closed as if the page had moved under it. It now closes only on a scroll that actually moved its trigger. The notification center, the filter builder and the navigation menu closed their panels on scroll the same way and follow the same rule.
  • A chart whose library loads after the chart appeared draws itself. Both the Chart.js and the ApexCharts adapter looked for their library once, when the chart mounted, and showed the missing-library panel on the spot — so a page that loaded the library on demand kept the panel even after the library had arrived. Both adapters now keep looking: a library that arrives within a few seconds of the page loading draws the chart without the panel ever appearing, and one that arrives even later replaces the panel with the chart. The panel and its console hint appear only when the library is still missing a few seconds after the page has loaded.
  • A drawer holding only text can be scrolled from the keyboard. Its body scrolled but had no stop in the tab order, so a drawer of terms, a log or a long description gave a keyboard nothing to land on. The body is a tab stop with a visible focus ring now, and a drawer that holds a control still opens on its first one, not on the body.
  • A modal taller than the viewport scrolls its body instead of losing its top. The panel is capped to the viewport, modal.body scrolls between the header and the footer, and a dialog that fits is centered exactly as before. The panel used to grow with its content while its container centered it, which pushes anything taller than the window past the start of the scrollable area: on a consent screen, the notice began mid-sentence with its header out of reach. The body is keyboard-reachable now, with tabindex="0" and a focus ring, so a long document can be scrolled from the keyboard; initial focus still goes to the first control when the dialog has one.
  • The modal header keeps its close button in the top corner and has equal padding on every side. The close button was vertically centered, so a header carrying an avatar and a wrapping subtitle left it at half height, and the top padding was half the side padding.
  • A multi-select starts its text as far from its border as the fields beside it. Its frame padded all four sides with the small vertical spacing token, so in a filter row next to a select or a combobox its placeholder and its selected values began half as far from the left edge as their text did. The sides take the same horizontal token as select and combobox now.
  • An activity row announces its kind in the language of the page. The label that stands in for the colored dot read the kind's key, so a screen reader on a German page heard "comment" in the middle of an otherwise German row. The built-in kinds are announced from the translation catalog now, in every language WireKit ships.
  • A radar chart's tooltip stays on the point you hover when two of its axes share a label. The ApexCharts adapter pins a radar tooltip to the hovered point, but it found that point through the tooltip's title, so on two axes named alike it pinned the tooltip to the second one whichever you pointed at. It follows the marker under the pointer now, and falls back to the title only for a tooltip no pointer opened.
  • A lightbox and an image gallery return focus to the control that opened them in Safari as well. Closing the viewer handed focus back to whatever was focused when it opened, and in Safari a mouse click does not focus a button, so focus fell to the top of the page instead of the thumbnail or trigger the reader had used. The viewer now remembers the control that opened it, whether by a click inside the component or by an open event dispatched from a control, and returns focus there.
  • A breadcrumb with a long trail wraps onto another line on a phone. The trail never wrapped, so each item shrank to its longest word and wrapped its own label, and a deep trail still ran past the edge of the screen. It now breaks between items, so every label stays whole.
  • A combobox, multi-select and command palette name their popup list. A role="listbox" with no accessible name is announced as "listbox" and nothing else once a reader moves into it. Each popup now carries the name the component already resolved for itself, which is what scope switcher has always done — four components on one ARIA pattern now give one answer instead of two.
  • A dropdown in its quick form can name its menu. The quick form composes the menu panel itself, and nothing written on the call site reached it, so a menu built that way was announced as "menu" and nothing else. label on <x-wirekit::dropdown> now names it. The explicit form keeps taking label on <x-wirekit::dropdown.panel>, and a label given to the dropdown there is reported in the development log instead of being dropped in silence. The theme controller's menu takes the control's name.
  • The reading TOC keeps the section you jumped to marked. It stood the heading you picked just below its strip, then decided the active section at the top of the window, so the section before your pick was marked the moment anything recomputed. On a page that scrolls inside its own region under a header, that line lay above the region altogether. The active section is now decided at the line a jump stands a heading on and follows whichever region scrolls, a jump keeps its section until you scroll yourself, and at the end of the page the last heading in view is marked. A strip pinned to the bottom no longer leaves a gap the height of the strip above the heading it jumps to.
  • The reading spine works in a page that scrolls inside its own region. Every part of it spoke to the window. A click scrolled the window, which had nowhere to go, so nothing moved; its end-of-page rule found a document that does not scroll at its end, so the last section was marked from the first paint on; the per-section fill never moved; and back-to-top did nothing. It now finds the region the headings scroll in, as the reading TOC does, and measures, jumps and fills there. A jump keeps its section until you scroll yourself, and a page that cannot scroll at all marks its first section rather than its last.
  • A data table column with a cellType the table does not know shows its values as text. It matched none of the table's cell shapes and rendered every cell empty, so a typo in a column definition emptied the whole column without a word.
  • A hovered button keeps one color from edge to edge. The filled surface drew its border in the fill's own color and changed it by hand beside the fill, and the neutral, success and warning intents changed only the fill, so hovering one of them left a ring of its resting color around it. The border of every filled button is transparent now: the fill shows through it, and the button keeps its size. The border is no longer painted as a second shape over the fill either, which is where a light seam could show along the rounded corners.
  • An indeterminate checkbox shows its third state on a page with no Alpine component around it. The state is a DOM property the server cannot render, so the checkbox carries it in an attribute and an Alpine directive applies it, and a directive only runs where Alpine initializes the page: inside an x-data or a Livewire component. In a plain form the box stayed empty. The directive now starts on its own, as x-init does.
  • A checked checkbox and a selected radio keep their accent border under the pointer. Both draw a hover border and a checked border on the same box, and the hover one came later in the stylesheet at the same weight, so it won: a hovered checked checkbox wore a gray ring around its accent fill, and a hovered selected radio lost the ring that marks it at all. An unchecked control answers the pointer exactly as before.
  • A data table inside a Livewire component keeps what only the browser holds across renders. The search text, the density, the hidden columns and the selection went back to their start every time the server answered, so the search field emptied the moment its results arrived.
  • Selecting rows in a data table bound with wire:model no longer breaks it. The selection reached the bound property as a JSON string rather than a list, and every request after the first selection answered 419, so the table could no longer sort, search or page.
  • A server-mode data table is busy exactly while a round trip it started is out. From the moment the request carrying a sort or a search leaves until it comes back, with a response, an error or a cancellation, the table carries aria-busy="true" and its status region says "Loading results" once. The loading prop could not do that, because it is read when the server renders and a response is rendered after the wait is over; the documented recipe passed :loading="true" and marked the table busy for good. See the loading state. loading remains for a wait the table does not start, such as a queued import.
  • A data table wider than its space shows the edge it continues toward. On a phone there is no scrollbar until a drag is underway, so a table cut off between two columns looked complete. A soft shadow now marks the edge with more table behind it, as it does on table.
  • An inline edit focuses its control when it opens in Safari. The focus call ran before the control was revealed, so in WebKit, the engine of every browser on an iPhone, focus stayed on the page.
  • An inline edit reaches a WireKit component in its editor slot. x-ref="control" on a slotted component, such as the editor, gave the inline edit nothing to focus or describe. It now finds the element you type into.
  • An inline edit shows a rich-text display value as block content, with the editor's typography. An Htmlable value put paragraphs and lists inside an inline element.
  • The editor works with x-model in both directions. Canceling did not reach the editing surface, and every keystroke set the bound value to undefined.
  • An aria-label on the editor names its textbox. The editing surface was announced as "Rich text editor" whatever the page called it, and required now reaches it as well.
  • A number input bound with wire:model shows and steps from the bound value. It started from min, so the field could show one day where the component held seven.
  • A segmented control scrolls its own track when its options are wider than the column. It used to push an ancestor sideways, and on a phone the last segment was cut off at the edge of the screen with nothing to say more existed.
  • The reading minimap's heading anchors stay on screen when the minimap sits against the edge of the viewport. The labels ran past the edge; the strip now moves inside, with a backdrop of its own, when there is no room outside.
  • The component sandbox escapes a string prop once. A title with an ampersand showed &amp;, a link with a query string pointed at a broken address, and Show Code taught the escaped value.
  • The map's console hint leads with a route every bundle can take. It recommended registerMapEngine(), which only wirekit.esm.js exports; it now leads with window.maplibregl = maplibregl and names registerMapEngine() together with the ESM build.
  • wirekit:doctor no longer asks for a null-guard on a module-level observer. An observer built once at the top of a file is never null and has no destroy() to race, so the finding could only be silenced by decorating it or by an opt-out that would also hide a real case.
  • A dropdown, context menu, menubar, navigation menu, filter builder and notification center no longer take focus back from a reader who has already moved into the panel. Each of them put focus into its panel once it had finished positioning it, but the panel is on screen and usable a moment before that. Focus moved to a row in that moment was sent back to the first one, so a key pressed there acted on the wrong row. A submenu opened from the keyboard followed the same order. Focus now goes into the panel only when it is not already there.

Documentation

  • The MapLibre GL guide is rewritten for MapLibre GL 6. Version 6 ships no maplibre-gl.js and sets no global, so following the old steps either kept an app on a release affected by GHSA-jrc7-96c5-q579 — critical, fixed in 6.4.1 and in no 5.x release — or produced a map that never drew. The guide names 6.4.1 as the minimum and shows the bundler setup that emits MapLibre's worker: without it the worker is requested from a file the build never wrote, and the map stays blank without an error. Each way of loading the engine comes with the Content-Security-Policy it needs.
  • The application shell blueprints and the app shell and shell bar pages show the shell tag exactly as you write it. Each preview pinned its viewport shell to the documentation column with an inline height and overflow: hidden, which Show Code then handed on as if it were part of the pattern, together with a comment telling you to delete it again. The documentation column now bounds the preview itself, so the code you copy is the code you keep.
  • The feature, feature grid and hero examples say what is true instead of promising quality. "Production ready", "battle-tested" and "built for speed" said neither what was tested nor against what, and an example's text is what lands in the interface built from it. The cards now name things you can check, such as one class switching every component to dark mode.
  • Search results and link previews for the documentation show a full sentence about each page. Many pages described themselves in a few words, such as "Small status label", which named the component and said nothing about what it is for, so search engines often put a passage of their own choosing in its place. Every page now carries a description that says what the component does and when to reach for it.
  • The documentation writes the canonical prop spelling throughout. The prop naming conventions page asks you to write intent and surface in new code, while the examples across the catalog were still writing the older spelling: on the alert page an alert variant="danger" sat one line from a button intent="danger". Every example now writes the canonical name, and variants and intents names intent in its table. Both spellings produce identical markup, so nothing you render changes.
  • The README names the chat and AI components. The catalog table on the front page had no row for them and now lists them: message, message-group, quick-replies, conversation, assistant-message and the rest.
  • The lightbox page no longer links to a page that does not exist. The row for --color-wk-bg in its token table ended in a dead link.
  • The inline edit page shows rich text through the editor slot, with a live preview, the Livewire source, and what cancel and confirm do to the editor.
  • The MapLibre GL guide says what the server must send for a self-hosted .mjs. A browser runs a module only when the response carries a JavaScript MIME type, so the section gains a check against the deployed file and the console messages both engines print when it does not.
  • The reading page shows the minimap's heading anchors in a preview.