WireKit v2.27.0
Minor — the whole triage, and what the tickets did not know.
Every addition here is opt-in: an unchanged call site renders exactly what it did in 2.26.1.
Added
-
<x-wirekit::step-marker>— the numbered chip a sequence of steps was being hand-rolled for. No existing primitive fits, and each near-miss misses differently: a badge is a pill, and a pill reads as a label about something — a status, a count, a tag attached to its neighbor. A step marker is not about the step, it is the step, which is what the square corners carry and why the shape is not a prop. It is filled always, and each intent's fill is paired with that intent's own foreground token in one place, because that pairing is precisely the part a call site has no way to get right: a number on a colored square is legible or it is not. -
The three accessibility hooks can be renamed to match your application.
data-reduce-motion,data-contrastand--font-scale-wkare defaults, not fixed names —motion_attribute,contrast_attributeandfont_scale_propertyinconfig/wirekit.phpset what WireKit reads. An application that already has its own preference attributes keeps them instead of carrying a second set. Theming -
safeObserver, for the observer that keeps firing after the component is gone. An observer's callback runs once more after Livewire morphs the node away and Alpine tears the component down — into athiswhose fields are already null. The page fills withCannot read properties of nullpointing at your plugin rather than at the teardown that happened, and your browser tests go red for a reason that is not yours.safeObservergives you an observer whose callback simply does not run once you stop it, including for a batch the browser had already queued. It ships with the Composer package rather than from npm, so you import it by path — there is nothing to install. It does not replacedestroy()— nothing can — it removes the guard you would otherwise write in every callback. Authoring custom Alpine plugins -
A type-size preference, which completes the accessibility trio. Motion and contrast each got an override in this release; type size had none, so an application offering its own "larger text" setting had nothing to set. The browser zoom is not the same thing — it scales the whole page and reflows a dense interface into something harder to use. One custom property now moves the whole ramp:
<html style="--font-scale-wk: 1.25">. The default is1and multiplying by one changes nothing, so an application that sets nothing renders exactly as before. Integration -
<x-wirekit::field.spacer>— the other half ofreserve-message. That one holds the space below a control so a field does not shove its neighbors down when validation fires; this one holds the space above, so a button beside a labeled field starts where the input starts instead of a label-height too high. Neitheritems-endnoritems-startsolves it — the first follows the field as it grows, the second lines up with the label. It renders the real label rather than a copy of its classes, so it stays exactly as tall when the typography changes. Field -
php artisan wirekit:csp-audit— checks every Alpine expression in your own Blade views against Alpine's real CSP grammar. Under a policy without'unsafe-eval'an expression outside that grammar is never evaluated: nothing throws, nothing logs, and the page looks correct while the control is dead — so there is no symptom to notice and the check has to be mechanical. The verdict comes from Alpine's own parser rather than a pattern list, because the grammar is wider than it looks and judging by eye over-reports badly. It exits non-zero when it found nothing to scan, too: an audit that measured nothing must not report a pass. CLI reference -
optimisticArgson every component that supports optimistic UI. The optimistic layer always spreadargsinto the action call and no component exposed it — so the commonest optimistic surface there is, a list of identical controls with one per row, had no way to say which row it meant, and the only path was to hand-mount the factory and give up the component. Optimistic UI -
start(payload)andsetBody()on<x-wirekit::stream>. Thefetchrequest body was read once at mount and could not be changed, so a stream could only ever send the payload it was born with — while the case this transport exists for rebuilds its body every run.startMessage/readyMessage/stoppedMessage/failedMessageare now props as well. -
Seven locales ship, not two. Spanish, French, Italian, Dutch and Portuguese join English and German. Set your app's locale to any of them and every string WireKit renders on your behalf follows — no publishing step, no configuration. Authored rather than machine-translated, and checked against the reference catalog key by key: nothing missing, no dropped
:placeholder, no broken plural form. See Localization. -
An application can ask for increased contrast, or decline it.
data-contrast="more"on<html>lifts the muted end of the palette — helper text, placeholders, borders, the values a reader with low vision loses first — and"no-preference"holds the default even when the OS asks for more. Without the attribute the OS decides, as before. This completes the set: motion, color scheme and contrast can now each be stated by the application rather than only by the system. -
New icon aliases, so a page can name what it means instead of a glyph:
envelope,user-add,user-remove,building,webhook,arrow-left,clock,lock, and a commerce set —store,cart,receipt,truck,package,barcode. Every alias resolves in all four icon sets. See Icon. -
WireKit::isIconAlias()andWireKit::iconVocabulary()answer a questionWireKit::icon()cannot: whether a name is one WireKit declares, rather than one that merely renders.icon()always returns something — an unknown name falls through to the icon set's own naming — so it tells you what will appear, never whether the name was recognized. Useful for editor completion and for checking a design system's names against the ones that exist. -
error(andhint) onslider,range-slider,ratingandsegmented-control. All four took alabeland none took anerror, so a validation message passed to them was silently dropped into the attribute bag. They now render it, announce it politely, and wirearia-invalid+aria-describedbyto the element the message is about. -
wirekit:doctor:propsfinds a misspelled prop before a page renders. An unknown prop is not an error at render time — Blade passes it through and the component uses its default — so<x-wirekit::button intnet="danger">renders a perfectly good button in the wrong intent. The linter is static, so it covers templates nobody has opened yet. -
variant="flush"ontable.headdrops the header fill and keeps the divider and the sticky behavior. Previously the fill could only be removed by overriding the whole class string, which discarded both. -
zoneInsetonsidebarstops the head and foot zones from insetting content that already insets itself — asidebar.itemin a zone was indented twice. -
nameondropdownpins the panel id, so it survives a Livewire round trip verbatim.
Fixed
-
A
segmented-controlinside a form submitted nothing while the page showed a choice. Its hidden input was filled from JavaScript, so between the server render and Alpine booting the field was empty with the selected segment highlighted right beside it — and a submit in that window sent no value at all. Under a Content-Security-Policy that blocks Alpine's evaluator, that window is the entire session: nothing 404s, nothing throws, and the form is silently empty every time. The field now carries its value from the server render, read from the same expression that seeds the client state so the two cannot disagree. -
The
command-palettereopened showing the previous search. Closing and reopening cleared the visible input but told the host nothing, so a server-driven palette kept the last query alive: type "strategy", close, reopen, and the field is empty while the results still answer a word the reader can no longer see. The empty query is now announced at the moment the empty field appears. Its results list also takes a height override like every other part of the panel — the 288px it was fixed at is a reasonable default and a poor ceiling for ranked results on a large screen. -
Opening a WireKit asset URL directly no longer garbles its text. The nine
distroutes did not declare their character set, so a browser hitting/wirekit/wirekit.csson its own fell back to a legacy single-byte encoding and every em-dash rendered as mojibake. Loaded from a page the file inherits the document's encoding, which is why this was invisible in every application and visible only on the direct hit. -
wirekit:verifynow checks the ApexCharts adapter wherever it is switched on. The check only ran whencharts.librarywasapexcharts, whilescripts.apexemits the adapter independently — so the configuration that ships the adapter with the library set elsewhere went entirely unexamined, and every chart stayed blank while the installation was reported healthy. It also asks the harder question now: finding the package inpackage.jsonproves it can be resolved, not that it reaches the page. Registering it twice is reported too. -
The public API map listed every recipe twice, and its
blueprintsgroup held no blueprints. The group scanned its directory recursively and swept up the pages belonging to two other groups, so a tool reading the manifest saw duplicate entries under two different names and a group whose contents did not match what it is called. Every page now appears under exactly one group. -
Picking a date range with the keyboard now shows the same preview a pointer gets. Moving through the grid after choosing one end left the shading — and the
aria-selectedthat says "commit here and this is your range" — untouched, because the preview was wired to hover alone. There is no second way to learn what a range will be before making it, so for anyone not using a pointer the feature was not harder to reach, it was absent. Calendar -
A dropdown inside a Livewire component no longer throws on every round trip. The panel read its id out of the Alpine scope, which is right across the teleport and wrong across a morph — so every re-render raised
panelId is not defined. That is worth more than console noise: a JavaScript error during a morph ends evaluation at that point, so whatever the same pass would have done next silently does not happen, and nothing turns red. The id is written from the component now instead of bound, leaving no scope-dependent expression on the teleported node. Reported against 2.25 and 2.26 alike, so it was not new — only unnamed. Dropdown -
Streaming over
fetchnow works for an endpoint that sends more than one kind of frame. The reader kept onlydata:lines and dropped theevent:name, so a stream of named events — which is how Laravel's own SSE helper frames them — arrived as one run-on string with a refusal indistinguishable from a token, and not one error anywhere. Name the frame that carries text withevent-name, and every other named frame is dispatched as awirekit-stream-eventon the element (with its JSON payload decoded) instead of being pasted into the output. A stream that names nothing is unaffected. See Named events. -
A failed stream no longer discards what had already arrived.
stop()and completion both revealed the buffered text underprefers-reduced-motion; the failure path did not — so a response that died at 90% showed the reader nothing at all. The one terminal state where a partial answer is worth most, and the reader affected was the one who had asked for less motion. -
Stream failures can be announced in the application's language. The failure announcement and all five reasons behind it were English literals in the JavaScript with no way through
__()— so an interface translated everywhere else announced its failures in English, in the moment the reader most needs to understand what happened. All six are now in the shipped catalogs (seven languages) and overridable viafailed-message. -
A date range now previews backwards. Dragging toward an earlier day showed nothing at all until the click, while dragging forward shaded as expected. The gesture was always accepted — only the preview refused to show it.
-
calendar's range caps follow the ordered ends, so the rounding no longer lands on the wrong day while the pointer sits before the start. -
Ten numeric props stopped discarding a deliberate
0. A toast asked not to auto-dismiss vanished after five seconds; a dropdown or popover asked to sit flush against its trigger sat eight pixels away. Alsocarousel's interval,hover-card's delays and offset, andreading-meta's two reading speeds. -
Nine labels the catalog already knew were rendered in English regardless of locale — the data table's screen-reader column header, the event calendar's view switcher, the status matrix legend, two calendar labels, the combobox empty state, and the alert dialog's cancel.
-
An upgrade's new assets are served instead of the old published copy. Staleness was decided by which file was written later, and a
vendor:publishfrom the previous version can easily carry the later timestamp — so the old bytes shipped, with a fresh-looking cache-buster. -
A
dropdowninside a Livewire component survives a round trip. Its panel teleports out of the morphed subtree while the id was regenerated per render, so the trigger and the panel stopped naming each other. -
wirekit:verifyno longer calls a correct setting invalid.scripts.bundle = 'csp'— the one correct choice under a strict Content-Security-Policy — was reported as an invalid value with advice to undo it. -
wirekit:doctor's stale-views advice is no longer a loop. Running the suggestedview:clearproduced exactly the state the warning reported. -
wirekit:doctor's typo scan can go green again. It read the whole log history, so a typo fixed weeks ago kept the warning yellow for the life of the file. It now looks at a configurable window, 24h by default —WIREKIT_DOCTOR_SCAN_LOGS_WINDOW_HOURSsets it. -
Every teleported overlay panel now lives inside a landmark. Dropdowns, tooltips, popovers, comboboxes and menus lift their panel out of the document flow so an
overflow: hiddenancestor cannot clip it — which also placed them outside every region on the page. An accessibility audit reported it on every one, and a developer could not fix it, because the markup is WireKit's. They now teleport into a named region WireKit creates. -
A responsive
tableshows its horizontal scroll. On a phone there is no scrollbar until you are already dragging, so a cut-off column looked like the end of the table.
Documentation
-
A recipe for numbering a feature list. Replacing the auto-generated icon chip with a numbered marker via
iconSlotis the composition the new step marker exists for, and it is now written down rather than left as something to work out from two component pages. Feature with numbered marker -
The one-time-code page no longer reads as a rule about one-time codes. Every preview on it used a trimmed character set, and the section above them explained why a recovery code drops
IandO— so the page looked like a statement that the component removes ambiguous characters. It does not:alphabetis the developer's declaration, and it accepts lowercase, symbols and non-ASCII alike. A second preview now shows a permissive set beside the conventional one. Two previews, two audiences: one shows the convention, the other shows the range. OTP input -
A named Content-Security-Policy route for a Livewire app. The page sent an app with a strict policy toward the self-contained CSP bundle and warned in the same breath that a Livewire app must not load it — leaving the one stack WireKit is built for with no route at all. The route is simpler than either warning suggests: the default bundle carries no evaluator of its own, so switching Livewire to its own CSP distribution is enough and WireKit needs no change. Also documents that a Livewire method named like a JavaScript keyword needs index access. Integration
-
gridexplains thattemplateis not responsive and that a strictstyle-srccan drop it entirely — where other components lose a shade, the grid loses its whole column definition. Passcolsalongside to declare the fallback. -
app-shellnames the coupling that silently disables a sticky header:headerPlacement="content"puts the header inside a column that never scrolls, and nothing sticks to a container that does not move. -
inline-editalready carried its click-away note from 2.26.1; the reduced-motion attribute is now documented as fixed rather than configurable, because setting it never had any effect.