Skip to main content

WireKit v2.70.0

Minor release

Minor. A new datetime picker picks a date and a time together, from a calendar and a list of times or typed into its field, as one moment or as a range, and opens as a modal on a phone. An image carries hotspots, markers that each name what they point at, with an editor that places them by click, drag or keyboard; a video player loads a file, a stream, YouTube or Vimeo only once the reader agrees; and an image editor crops with handles, the arrow keys or numbers. The date and the time picker open the same dialog with popover. A calendar takes limits and unavailable days, presets for a range, a today button, week numbers and a locale of its own, and a Livewire property typed with DateRange or DateTimeRange binds a range. ApexCharts 8 is a supported version of the chart. A map takes its style or its tiles from the config, so every map of an application loads them from its own server. A badge cuts a long label at one width and shows it whole in a tooltip, a table head takes the help for its column, and a product card's stars take a note. A list of checkboxes picks a range with Shift held, and a data table with selectable rows does it by itself. A bulk selection around a list of the application's own and a data table in server mode offer every match beyond the page, and an action bar says how many are selected. An entry of a command palette opens a page of values in the same palette, a palette takes the shortcut of another while it is on the page, as the palette of a selection does over a page's search, and the overview of keyboard shortcuts lists the hotkeys of the page's palettes. A form that Livewire does not send goes out once with submit-once, and the sign-in button of Apple shows its logo in the color of the button's words. A radio or a checkbox drawn as a card holds a description and an image in its frame, a sign-in button signs in with a passkey, and wirekit:theme derives success, danger and warning from brand colors as it does the accent. A heading takes help, the question mark that explains a page or a section, and a navbar takes a menu slot for what only its menu shows. A kbd draws a shortcut's keys for the reader's platform, ⌘ S or Ctrl S, and a tooltip draws the shortcut of its control. A card header takes buttons at its end and a sentence under its heading, a stat can be a toggle that sets the filter it counts, a menu entry carries a count at its end, a table column can give way only in a wide window, two taps on the quantity of a till line ask for a quantity, a countdown can be stopped, a save over a button in a card counts that card, the question before leaving names what is unsaved and asks before an ordinary link too, a link without an address can show its words as plain text, a translatable input reads as one field, WireKit writes its numbers in an application's own format where it gives one, and a series of a chart names its own decimals. A node of a tree view hands its value to the selection event, and an editor names the engine it opens in. The chevron of a collapsible card stands in a circle and closes an open card when clicked. On a phone, checkboxes and radios stand as far apart as on a desk, their boxes stand on the middle of the first line of their labels, and the select of the theme controller draws the chevron every other select of the kit draws. Inside a Livewire component, what the reader did survives an update: a tree's tab stop and closed branches, a split's size, a table's sort and a kanban board's announcements. On a short or narrow window a panel stays beside its trigger: a picker's dialog, the navigation menu, the popover, the color picker, the filter builder and the notification center are capped to the room there instead of lifted over the trigger, and a panel placed beside its trigger turns below or above it where neither side has room. A card's border takes the color of an intent, an aligned row wraps into lines that each line up on their controls, and a calendar takes disabled and readonly. A table stacks its rows below a width of its own with stack-below-container, and a stacked figure aligned to the end keeps its name; a chart offers its data as a table under it with table; an alert a Livewire render adds is announced; and the backdrop behind a dialog dims the page less.

Security

  • A length, a ratio or a duration given to a component reaches its style as one value. The ratio of an aspect ratio, an image and an image compare, the height of a chart, a mixed chart, a sparkline and a map, the max-height of a conversation, an editor and a scroll area, the offset, max-height and width of a sticky panel, the width and list-max-height of a scope switcher, the width of a dropdown panel and of the reading minimap, the sticky-header-max of a table, the logo-aspect of a brand and a testimonial and the brand's mobile-logo-aspect, the overlay of a lightbox, the offset of an indicator, the duration of a shimmer and the heat-from and heat-to of a status matrix went into a style attribute as given. Escaping leaves a ; alone, so a value from a request could carry declarations of its own and restyle the element. Each takes one value now: a length, a ratio, a duration, a color, or a function such as calc(), min() or var(). Anything else is reported while debugging and falls back to the prop's default.

  • The sandbox validator caps the elements of an array prop. One prop's arrays hold at most 1 000 elements over all their levels together, refused like an oversized string or a nesting too deep, with the limit in the violation. A schema that allowed an array prop would otherwise have checked and rendered every entry of an array of any length.

Added

  • New image hotspots for an image that names what it shows. <x-wirekit::image-hotspots> lays markers over an image at positions in percent, each a button named after its target that shows the name beside it on hover and focus, and a press sends wirekit:image-hotspots-select with the target, so the page decides what opens. A phone file takes positions of its own, and on the phone a strip under the image lists the targets as links.

  • New image hotspots editor that places the points of an image with hotspots. <x-wirekit::image-hotspots-editor> sets a point with a click on the image or with its Add button, moves one by dragging, with the arrow keys or through its X and Y fields in the list beside the image, and orders and deletes the points there. Its value is the list image-hotspots takes as markers, bound with wire:model or x-model, and a drag writes it once, when the point is let go. With a phone file every point takes a position of its own on it, and selecting a point sends wirekit:image-hotspots-editor-select, so the page can show its own fields for that point.

  • New video player for a video in the page. <x-wirekit::video> plays a file, a stream (HLS) or a video on YouTube or Vimeo when the reader presses play, and nothing loads before that. A provider's player loads nothing from the provider until the reader agrees: a notice offers "Load the video" and sends wirekit:video-consent, which a consent manager can take and answer with wirekit:consent, and consented starts a player whose consent the page already holds. YouTube plays from its cookieless host and Vimeo without tracking. tracks adds captions, and background runs a file as a muted loop with a pause button, still under prefers-reduced-motion and Save-Data. A stream plays natively in Safari and through hls.js elsewhere, handed over with registerHlsEngine().

  • New image editor for a crop over an image. <x-wirekit::image-editor> draws a crop over an image, moved and sized by dragging its frame or one of its eight handles, with the arrow keys or through four number fields, and held to a ratio such as 9/16 when one is given. The image stays as it is: the value is the crop, a rectangle in percent of the image, bound with wire:model or x-model, and Pushery\WireKit\Support\ImageCrop turns it into pixels for a resize and moves the markers of image hotspots into the crop.

  • An accordion item and an FAQ question can start open. open renders the item open from the first paint, its header expanded and its panel shown before any script runs, so nothing moves when the page starts. In single mode the first item with open stays open.

  • Whether a collapsible, a sidebar section or an accordion item starts open can be set once, in the configuration. components.collapsible.open, components.sidebar.collapsible.open and components.accordion.item.open set the start of every box whose call does not say, and all three ship as false. An accordion item takes its default in multiple mode only: in single mode, where one panel is open at a time, only an item with open starts open.

  • A datetime picker picks a date and a time together, from a calendar and a list of times in a dialog. The trigger looks like a field and shows the moment it holds, "Oct 27, 2026 at 10:30 AM". The value is written YYYY-MM-DDTHH:MM, takes a date object, binds with wire:model and submits with a form. A day picked without a time waits for one rather than becoming midnight; default-time gives it one, and with-confirmation keeps both until Select. interval, min-time, max-time and time-format shape the list of times, and a max-time before the min-time lists the night. With time="input" the time is typed instead, the hour, the minute and AM or PM as segments in the order of the application's locale, with the list as suggestions and a sentence under the field when a typed time is closed. min, max and unavailable close days, times and spans, a span such as 22:00-06:00 runs over midnight, and a date object among them closes a booked moment. The moment is written in the application's locale, or in the one locale names, alike by the server and by the script, and a date object or a moment written with its offset is read in the application's time zone. A moment in another language than the page's carries it in lang, on the trigger and in the list of times. The calendar in the dialog takes the calendar's own options, the trigger slot takes a button of the page's own, and the picker dispatches wirekit:datetime-picker-open, wirekit:datetime-picker-close and wirekit:datetime-picker-clear. Its dialog, and the dialogs of the date and the time picker with popover, carry the theme marker data-wk-picker-dialog.

  • A datetime picker with range picks two moments, a start and an end. The calendar shows two months and picks the two days, and a row for each end below it takes its time, from a list or typed with time="input". Select range stays closed until both ends have a day and a time and the end comes after the start, which a sentence under the times says, on one day too. The trigger writes "Oct 27, 2026 at 9:00 AM – Nov 1, 2026 at 5:00 PM", with the end's time alone on the start's day. The form receives name[start] and name[end], and wire:model binds the two ends as paths of an array or of a DateTimeRange. default-time takes a time for each end, min-range and max-range limit the days, and presets lists ranges as on the calendar, a preset taking its whole days.

  • A datetime picker with type="input" takes the whole moment typed in its field, the day, the month, the year, the hour, the minute and AM or PM as segments in the order and with the separators of the locale, with a button at the end that opens the dialog. A moment typed whole and open is taken at once, as a native field takes it, and the field and the dialog show each other's moment. A day its month does not have, a closed day and a closed time are not taken, and a sentence under the field says why. A pasted timestamp, or a moment written as the locale writes it, fills every segment, and a pasted date whose month or day does not exist is not taken either. With range the start and the end are typed in two fields, taken together once the end comes after the start and the range covers as many days as min-range and max-range allow. In a field too narrow for the whole moment the time goes on a line under the date.

  • On a phone the datetime picker opens its dialog as a modal, centered over a backdrop inside the safe area, with the page behind it held still and inert. The days grow to the size of a touch target as far as the width allows, a range shows one month with its presets as a select above it, the list of times gives way to a select the phone opens as its own wheel, and a pick waits for Select. This holds below 640 pixels of width and below 480 of height, a phone held sideways, where a dialog taller than the screen scrolls in itself with Cancel and Select in sight; a tablet keeps the panel below the trigger. Above those sizes the panel stands below the trigger, or above it near the bottom of the window, and on a window too short for all of it takes the room on its side and scrolls in itself, with the trigger in view and Cancel and Select in sight; a control the focus reaches stands clear of those two in either form.

  • A date picker and a time picker take popover. In place of the native field, the date picker opens a dialog with the calendar, two months for a range, with the calendar's limits and options, and the time picker opens a list of times on the application's clock, or with time="input" takes the time typed in segments with the list as suggestions. The value, the field names and wire:model stay the same, so switching it on changes nothing on the server, and without popover both render as before. With popover, format sets the time picker's clock, a max before the min lists the night as the native field reads it, and each picker dispatches its own open, close and clear events. On a phone, and on a screen less than 480 pixels tall, both open as a modal centered over a backdrop with the page held still, as the dialog of the datetime picker does, the date picker with days as large as a touch target and one month for a range, its presets as a select above it. Inside a field, and next to a <label for> of the page's own pointed at the picker's id, that label names the trigger or the typed time, a press on it opens the dialog or starts the typing in the first segment, and the button that clears the value names it as well. The datetime picker does the same. With readonly both show their value and open nothing, as the datetime picker does. A range shows a validation message filed under one of its ends, {name}.start or {name}.end, as it shows one filed under its name, here and on the datetime picker.

  • A calendar takes flush, without its panel's frame, for a calendar on a surface of its own, as the datetime picker places it in its dialog.

  • A calendar takes locale, for the dates of one calendar in another locale than the application's, such as a schedule that belongs to another region than the page. Its weekdays, its months and the first day of a week that follows the locale take it; its texts stay in the application's language. In another language than the page's, what the calendar shows in that locale carries it in lang, and the full date a screen reader hears for a day stays in the page's language, as the words added to it do.

  • A calendar moves a year with Shift and Page Up or Page Down, as far as min and max allow, onto the day with the same number; and the date, time and datetime pickers open their dialog with Down Arrow on the trigger as well as with Enter and Space.

  • A calendar takes min, max and unavailable, and in range mode min-range and max-range. A day outside them stays in the grid and within reach of the arrow keys, dimmed and named as unavailable, and neither a click nor Enter picks it. The month buttons, Page Up and Page Down stop at a month that lies wholly outside the bounds, and without a value a calendar whose limits close today opens on the next day that can be picked. Once the start of a range is picked, every day that would make the range too short or too long is closed, and the preview shows only a range that can be made. min and max take a day, a date and time, a date object, today or now, each read as its day in the application's time zone, text that names no day is reported through strict validation, and a limit that a Livewire render changes reaches the grid without resetting it.

  • A Livewire property typed with DateRange binds a calendar or a date picker with range, and stays a range. Pushery\WireKit\Values\DateRange reads every value the two components take for a range, and a range a form is still filling in is one too: hasStart(), hasEnd() and isComplete() say which ends there are, and a missing end throws nothing. It is a period of Carbon, so whereBetween() takes its two ends with the whole of the last day, and a loop over it runs a day at a time. Validation reads the ends as period.start and period.end. DateTimeRange keeps a time of day on each end, to the minute.

  • A calendar and a date picker with range take presets, ranges such as "Last 7 days" picked in one press. The list stands beside the calendar, and on a narrow screen it is a select above it. presets alone offers today, yesterday, this week, the last 7 days, this month, the year to date and, with min, all time; a list of keys names others, from days, weeks, months, quarters and years back or ahead to custom, which marks a range no preset names. A range picked in the grid marks the preset it equals, and a preset outside the limits cannot be picked. The range keeps its preset: a form receives name[preset], a date picker binds it as a third path beside the two ends, and a DateRange returns it from preset(), so a filter set to "Last 7 days" is still "Last 7 days" when it is read again tomorrow. DateRange::fromPreset() computes the days of a preset for today, and each preset is a static constructor as well, DateRange::last7Days(). With presets, the grid's mark of today and the today button count from the application's day as the presets do.

  • A calendar takes a today button, a column of week numbers, six weeks in every month, the month it opens on and three sizes. with-today adds a button beside the arrow to the next month, named with today's date, that goes back to today's month and picks today there; a today the limits close is only gone to. week-numbers adds the ISO 8601 number of each week, counted on its Thursday, as the header of its row. fixed-weeks draws six rows in every month, so a calendar keeps its height from month to month. open-to names the month a calendar without a value opens on, as a day, YYYY-MM or a date object. size draws the days at sm, md or lg. week-starts-on takes any day from 0 to 6, and locale for the first day of the application's locale, read from the locale data of PHP's intl extension; null in config/wirekit.php makes that the default for every calendar.

  • An editor names the engine it opens in with engine. An application with several schemas registers one factory per name in window.wirekitEditors, and a field with engine opens in that factory and in no other, waiting for it when its script runs after a wire:navigate visit. With one name for every engine, a field could open in whichever factory had run last and lose on the next save what that schema does not know. Without the prop a field takes window.wirekitEditor as before.

  • fit stands a table as wide as its columns, at most as wide as the space. Wider than that it behaves as without fit: the columns wrap, or with a frozen first column the table scrolls sideways in its own scroller with the shadows at its edges. A wrapper that sized itself by its content, such as w-fit or inline-block, left a responsive table no width at all, because the container its columns measure against takes no width from what it holds.

  • --wk-scroll-inset-below-fab lifts the corner buttons over a floating bar without a script on the page. WireKit writes the band of the surfaces pinned to the bottom, the floating action bar among them, without the buttons that stand on --wk-fab-lift themselves: --wk-fab-lift: var(--wk-scroll-inset-below-fab, 0px) raises a floating action button and a scroll-to-top stacked over it while the bar stands. --wk-scroll-inset-bottom counts the buttons as well, so as their lift it raised them by their own height on every measurement. A measurement that changes the lift measures once more, and once a sliding button has come to rest, so the bottom band counts the buttons where the lift put them.

  • ⌘↵ or Ctrl+Enter confirms an open alert dialog or modal with its primary control, from a field as well. An alert dialog presses the control inside alert-dialog.confirm, a modal the control a page marks with data-wk-dialog-primary, which wins in an alert dialog too. Focus stays on Cancel, so a plain Enter still approves nothing, and a control that is disabled or held back by its confirmation phrase is not pressed. The chord is the control's click, so wire:click and a page's own listeners hear it as one. The overview of keyboard shortcuts lists it among the open dialog's shortcuts, and the control carries it in aria-keyshortcuts. confirm-shortcut turns it off for one dialog, components.alert-dialog.confirm-shortcut and components.modal.confirm-shortcut in the configuration for every one.

  • A navigation menu item, an FAQ question, an accordion item and a swatch group option name the language of their text. Each takes its text as a string and uses it again, for an id, for the structured data or inside a sentence of the page, so a marked-up text did not fit. trigger-lang, question-lang, title-lang and an option's lang write the language on the element that shows the text and leave the id, the schema and the sentence as they were; in "Blau, unavailable" only the label takes it.

  • An image, an image gallery and a lightbox name the language of an alternative text and of a caption. Where either differs from the page's language, alt-lang and caption-lang write it on the image and on its caption alone; a lang on the tag landed on the figure and reached both. A gallery takes altLang and captionLang per image, in the grid and in the lightbox, keeps a caption that is markup as markup in the grid, and the lightbox draws such a caption's text. The lightbox's announcement of the slide it moved to carries the language of the text it reads.

  • An FAQ takes the level of its questions. Its questions stood on level 3 wherever it stood, so right under the page's h1 the outline missed a level, and under an h3 they could not go to 4. level, 1 to 6, reaches every question as variant and size do.

  • The breadcrumb builder takes a list of steps. Schema::breadcrumbList() took a name => url map, which keeps each name once, at the place of the first and with the address of the last: a category and a product with the same name left the BreadcrumbList one step short of the visible trail, and pointed the category at the product. A list of ['name' => …, 'url' => …] steps keeps both, under the rules the map follows: a step without a page is left out unless it is the last, and two steps in a row with the same address are one. The map works as before.

  • A callout and an alert take a level for their title. Both drew the title as a heading in a div, so a screen reader moving from heading to heading never met it, and the headings in a callout's text counted from a level that was not there. With level, 1 to 6, the title is a heading of that level in the component's own size; without it, it stays the div it was.

  • A table column can give way to the table's width only from a window width up, and table.stand-in shows what stands in for it. hide-below-container-from on table.th and table.td sets the window width from which hide-below-container applies, so a list may scroll in its own frame on a phone and still has to fit on a desk. In a neighboring cell, table.stand-in with the column's two values shows its slot exactly where the column gives way, and the value is on screen once at every width.

  • A dropdown item takes a badge, a count or a short word at its end. It is drawn as a sidebar item draws its counter and read with the entry's name, "My drafts 3". An empty value draws nothing, so an entry can carry its count only while there is one, and beside a shortcut the count stands before the key hint.

  • A badge with truncate stays on one line and cuts a long label at one width. A badge that carries a stored name grew as wide as the name, past the edge of a phone or wide enough to make a table scroll sideways. With truncate it stops at --size-wk-badge-max, the label ends in an ellipsis, and the whole label shows in a tooltip while it is cut. With tooltip as well, one panel shows the whole label above the tooltip's text.

  • A stat can be a toggle button that sets the filter it counts. With pressed, true or false, the whole tile is a <button type="button"> with aria-pressed and stays one item of a stats grid. A pressed tile draws the accent frame of a chosen card in place of its neutral border, an edge twice as thick as the border, and the frame follows aria-pressed wherever it is set, so a binding of the page moves it as well. wire:click, wire:key and data-* reach the button, which is named by what the tile shows.

  • A range of checkboxes picked with Shift held. x-wk-range-select on the element that holds them, a table body for one, gives every checkbox from the one clicked last to the one clicked with Shift the state this click gives it, and clears the range when the click clears its box. Each box in the range is clicked in turn, so wire:model on an array and every other binding hear it as a click, the text between the rows is not selected, disabled boxes and switches keep their state, and Shift and Space do the same from the keyboard. In lists inside each other, such as a table body that carries it inside a bulk selection, each list picks in itself. A data table with selectable picks a range by itself.

  • A radio or checkbox card holds a description and a trailing part inside its frame. With variant="card", the hint stands under the name as the option's description, and the trailing slot takes a price, a logo or an image at the end of the row, both inside the card. Neither becomes part of the control's name: the control takes its name from the label and its description from the hint.

  • A sign-in button for a passkey. service="passkey" is a <button> from which the page's passkey script starts the sign-in, with the words for signing in, continuing or signing up, and the passkey icon of the FIDO Alliance beside them once it is named in config/wirekit.php.

  • wirekit:theme derives success, danger and warning from brand colors, as it derives the accent. --success=, --danger= and --warning= take a color each and write the state's fill, hover, label and text color for light and dark, the text color into the state's border token as well. The label reads at 4.5:1 or more on the fill and the hover, and the text color on the page, the card and the tints an alert or a callout draws.

  • A command palette takes the shortcut of another while it is on the page. With takes-hotkey, a palette that shares its hotkey with others on the page has the key to itself: the key opens and closes it, and the others stay quiet until it leaves the page, as the palette of a table's selection leaves with the selection. Two palettes on one key opened together. A search field for one of the others stops stating the key while it is taken, in aria-keyshortcuts and at its end.

  • An action bar says its count with summary. Its live region said "Bulk actions available" when the bar appeared, and the count beside the actions, "3 selected" then "4 selected", reached no screen reader. The summary slot shows the count first in the bar and the region says it in place of that sentence, on each change the server renders. Without the slot the bar says what it said.

  • A bulk selection selects every match of a server list, not only its page. Around a table of the application's own, bulk-selection.toggle is the header checkbox, checked when every row of the page is and mixed when some are. Once the whole page is selected and total says there are more, a notice above the rows names both numbers, "The 50 rows on this page are selected. Select all 1,284 matches on all pages", and afterwards offers the page alone again. Every match is a boolean that wire:model binds both ways. While it holds, the rows of each page the reader turns to are selected as they render, and a render with another filter clears it and says why. A screen reader hears each change, and a click with Shift held picks a range of rows. A switch in a row, such as a toggle for a setting of its record, is no row, so selecting or clearing the page leaves it as it is.

  • A data table in server mode offers every match with total. Once the reader has selected the whole page, the bar offers all matches; taken, its count says "1,284 selected, on all pages", and selection-change carries allMatches. Another search or filter clears it. A screen reader hears the offer, every match taken and the page alone again from the table's status region.

  • An entry of a command palette opens a page of values in the same palette. opens names a command-palette.page, such as "Set status…" with Active, Draft and Archived: the field empties and searches the page, without case and accents, the first nine values carry the digits 1 to 9, and a digit chooses one while the field is empty. Backspace in the empty field or the new Back button goes up a level, Escape closes the palette, and a screen reader hears each level with its count and each value's digit as its aria-keyshortcuts. A page with remote takes its values from the application's search, as the top level does: the query event names the page in page. Command Palette with Pages shows both kinds.

  • The overview of keyboard shortcuts lists the hotkey of every command palette on the page. It names each under the palette's label, under Everywhere, where the key opens the palette from, or with the new hotkey-area="page" under On this page, for a palette that belongs to one page. The key of such a palette does nothing while a modal dialog is open over the page, like a shortcut of the page. The palette still handles its key alone, so the key acts once, and a key another palette has taken is listed under that one.

  • A kbd draws the keys of a shortcut for the reader's platform, and a tooltip draws the shortcut of its control. shortcut="cmd+s" draws ⌘ S on a Mac and Ctrl S elsewhere, the words among the keys in the reader's language (Strg in German), and a sequence such as g d with "then" between its steps, as the overview of keyboard shortcuts draws them. The overview names its keys in the reader's language as well. A tooltip draws the shortcut its control binds with x-wk-shortcut, or the one its new shortcut prop names, after its text, and so does the tooltip of an app rail module. A single-key shortcut is drawn only while single keys work. A shortcut the grammar does not read is reported through strict validation, and the kbd draws its slot in its place. A shortcut that names ctrl beside cmd draws ⌃ beside ⌘ on a Mac, in the overview as well, since it needs both keys there.

  • An application's own number format, through WireKit::formatNumbersUsing(). An application whose numbers follow an account's settings rather than the language of the request gives WireKit its own way of writing a number, and every number WireKit formats on the server goes through it, the counts of a pagination summary among them. A percentage and an amount in a currency keep the way the locale writes them and take the formatter's grouping and decimal marks. See Localization.

  • A series of a chart names its own decimals. decimals on a dataset takes the place of valueDecimals for that series, in the tooltip, in the list of a chart's linked points and on the y-axis that shows it, so a mixed chart of returned units beside a return rate writes 12 and 4,53 on a German page, where one setting for the whole chart wrote 12,00. A series without it keeps the chart's valueDecimals.

  • Amazon's button image in one file per pixel density. A sign-in button for Amazon showed its one image at the image's own size, blurred on a dense screen from the single file and twice as large from the double one. logo and wirekit.sign_in.logos.amazon take ['1x' => …, '2x' => …], written as srcset, so the button stays sharp at the size Amazon gives it, and a 2x file alone shows at half its own size.

  • Apple's logo in the color of the button's words. Apple asks for a white logo on its black button and a black one on its white button, and a sign-in button with variant="auto" takes the page's mode, so a single file was wrong in one of the two. The logo of Apple can be given as ['white' => …, 'black' => …]: every look carries both and shows the one it asks for, auto the one the page's mode asks for, and in forced colors, where the button stands on the reader's own colors, each shows the one the reader's contrast theme asks for. A single file holds for every look as before, and a list that names no file, by density or by color, is reported.

  • An ordinary link to another page asks in the kit's dialog too, and the page can take over the question. With x-wk-unsaved.confirm, a link of the same site without wire:navigate asked in the browser's own words, which name nothing and stand in the browser's language. It now asks in the unsaved hint's dialog, as a wire:navigate link does, and Leave follows it without the browser asking as well; a click with a key held, a download, another window, another site and a fragment of the page stay the browser's. Stay or Escape gives the focus back to the link that asked. Leave lets that one visit pass: a link that answers with a download or with no content leaves the page as it was, and the next unload asks again. wirekit:unsaved on window says whenever the page gains its first unsaved field or loses its last, and wirekit:unsaved-release sent to window stops every question until the next page, for a page that asks in its own way.

  • The question before leaving names what is unsaved and where. Under the question, the dialog of the unsaved hint lists up to five unsaved fields, each by its label, a group by the caption of its legend, and by its place (the tab, the card, the accordion item and the marked part around it), and a line with the number of the others. Each entry closes the dialog without leaving, opens the tab, the accordion item, the collapsible card and the details around its field, and gives the field the focus.

  • A save over a button in a card counts the card as saved, and a path counts everything under it. Under x-wk-unsaved.until-saved, wirekit:saved with trigger: true counts the fields of the part of the page around the button whose click sent the request, or around the field with the focus where a key such as Enter sent it: its form, else the part marked data-wk-unsaved-area, else its card, else its scope. A screen of several cards, each saved by its own button, keeps the cards that were not saved unsaved. paths: ['variants.3'] now counts variants.3.sku and every other field under that path, for a save of several parts that says which went through, and wirekit:not-saved reads both the same way. See unsaved hint.

  • A countdown can be stopped. pausable puts a button after the time that stops the clock where it stands and starts it again, named "Pause countdown" or "Resume countdown" in each language WireKit ships. static works the remaining time out once, as the page loads, and leaves it standing, for a page served from a cache. A countdown that stands beside other content and whose ticking the task does not need meets WCAG 2.2.2 with either. At the deadline both still turn to the expired text, once, and fire wirekit-countdown-expired, and the button of pausable goes with the clock, handing the focus to the next control after the countdown when it has it. A headless countdown reads paused and calls togglePause() for a control of its own, which leaves paused as it is past the deadline.

  • Two taps on the quantity of a till line ask the application for a quantity. In a cart line with stepper="buttons", two taps on the quantity between − and +, or a double click, dispatch wirekit:cart-quantity with the line's key, so a till can open a keypad of its own for a count that would take many presses. One tap does nothing. The quantity is a target as large as the buttons beside it, and a double click marks none of its digits.

  • A card header takes buttons and a sentence under its heading. card.header takes an actions slot for buttons at its end and description for a sentence under the heading, as a prop or a slot. The buttons stand beside the heading while the card has room and drop under it where it has not, measured in the card rather than in the window, as the actions of a page header do. A header without either renders as before. A card that is itself a link or a button is one control and takes no buttons in its head: there actions is reported and left out, and a heading that links gives the card its destination instead.

  • wirekit:card-opened says when a collapsible card stands open. It rises from the card once the part that folds has slid open to its full height, after any way of opening it, and in answer to wirekit:card-open when the card was open already, so a script that opens a card can bring an element inside into view and land where it means to.

  • :error="false" turns the error of a form field off. The field then shows no message and no invalid state and reads nothing from the shared $errors bag, for a page that already reports the failure somewhere else, such as a notice above the form for a rejected sign-in link. Every form control that reads the bag takes it, a field turns it off for the control inside it as well, and a field.set turns its group message off the same way while its controls still leave the group's entries alone. Without it nothing changes, and error="false" written without the colon means the same.

  • A drawer can fill the screen on a phone. Every drawer left a strip of the page beside its panel, 48px on a 390px phone, and nothing changed that, since the panel renders outside the page. full-below names the breakpoint, sm to 2xl, below which the panel takes the full width as a side drawer or the full height as a top or bottom one, and keeps its content out of the safe areas on all four edges. Above it the drawer keeps its size.

  • A number input takes the names of its − and + buttons. decreaseLabel and increaseLabel name them where the label alone words them badly, and without them the buttons are named from the label as before. A cart line uses them: the stepper of a line read "Decrease Quantity of Desk lamp", and in French "Diminuer Quantité de", and now reads "Decrease the quantity of Desk lamp", as the press buttons of a till line already did.

  • The name of a cart line can link to its product. href makes the name a link in either layout, with the name's look and a link's underline on hover, and the controls of the line still say the name. nameLang sets the language of a name that is not in the page's, on the name alone.

  • A link without an href can draw its words as text. A page that names an order or a customer links it for a reader who may open its page and should show the words alone to one who may not. Without an href the link rendered an <a> without one, which looks like a link and takes neither focus nor a click. With plain-without-href it renders a <span> instead, without the link's color, underline, pointer and focus ring, and keeps size and the attributes it is given, except those only a link reads, such as target, rel, wire:navigate and wire:current. With an href, and without the prop, a link renders as before.

  • An image compare can be disabled. It submits a position like a field and had no way to be locked. disabled locks it as it locks a native field: the handle leaves the tab order and takes no press and no key, it shows that it is locked, and a form submits no position. Inside a disabled group the slider is locked the same way and now looks it, and a press that still reaches the handle moves nothing.

  • A field set takes required. A group of radios or checkboxes that needs an answer had no way to say so: the attribute landed on the <fieldset>, where it means nothing, and the legend showed no asterisk. The legend now shows the asterisk a required field shows after its label, hidden from assistive technology, and its caption ends in "(required)" in the page's language, which a screen reader reads on entering the group, a group of checkboxes included. The browser's own check stays off, as on a required field.

  • A textarea takes its text from value, as an input does. In a form without Livewire, :value="old('bio', $user->bio)" brings back what the reader typed after a request that failed validation. The attribute used to land on the <textarea>, where it does nothing, so the field stood empty and the text was gone. Text written between the tags still fills the field, and wins when both are there.

  • A form that sends itself once. A form that Livewire does not send went again on a second press before its answer arrived, and where the server takes a request only once, such as a code for one sign-in or a mail whose link the next mail replaces, the second request undid the first. A form with submit-once lets the first submission go and holds every further one back until the page changes, without disabling anything: the form and the button that sent it carry aria-busy="true", and the focus stays on the button. It takes a submission again when the browser shows the page from its back-forward cache, and after submit-once-release milliseconds for an answer that leaves the page as it is, ten seconds unless the form names its own. A plain <form> asks the same with data-wk-submit-once, and a submission another script has taken over, as wire:submit does, is left alone.

  • A table head takes help, the question mark that explains its column. It is the one a field shows beside its label, with the text in a tooltip on hover and on keyboard focus, and it follows the head's align. In a sortable head it stands beside the sort button, so a click on it opens the help and sorts nothing, and the head stays named by its words. help-id writes the text as a hidden description for the fields of the column to list in their aria-describedby.

  • A heading takes help, the question mark that explains a page or a section. It is the one a field shows beside its label, and it stands beside the heading element rather than in it, so the heading stays named by its words. A click on it dispatches wirekit:field-help with the heading's id, and classes given to the heading go on the row that holds both.

  • A navbar takes a menu slot for what only its menu shows. On a phone the menu holds it after the entries: the links to sign in, say, which a narrow bar has no room for beside the menu button. Above the breakpoint it is hidden, and its counterpart in actions takes hidden md:flex, so each is rendered once. Under force-mobile the menu shows it at every width, and a bar with mobile="scroll", which has no menu, renders nothing of it.

  • A file upload can send each file of a selection in a request of its own. With multiple and wire:model, Livewire sends a selection in one request, so a limit on the size of a request, in PHP, the web server or a proxy in front of it, failed the whole selection when the files together passed it, the small ones included. Add upload-separately and each file goes up alone: one over a limit fails alone, its row says so, and the others stay on the property. The upload events still describe the whole selection, and upload-separately in the config turns it on for every upload.

  • A link to an accordion item or an FAQ question opens it. An item's id stands on the item itself, so /help#shipping lands on the question, and the accordion opens it when the page loads or the address changes. A link to an element inside a closed item opens that item and brings the element into view.

  • quantity-name names the quantity of a cart line for a form. A cart that works without script sends its quantities with a form, and the field of a line was named after its id, number-input-1, number-input-2, a name that followed the order on the page and matched no line. With quantity-name="lines[42][quantity]" the form sends the quantity under that name; without it the field carries no name and a form sends nothing for it.

  • help-field names the field the help of a field set explains. A click on the question mark beside the legend reports it as name in its event, where the set's own name is not that field.

  • A node of a tree view takes value, and tree-node-select carries it beside the label. The event named a node by its text alone, so two nodes that read the same could not be told apart, and a label that names the state of its record changed with that state. A Livewire page opens the record by its key from $event.detail.value, which is null for a node without one.

  • A product card's stars take a note, the ratingNote slot right after the number of reviews, in the row of the stars: where a shop says whether and how it checks that its reviews come from buyers, or a link to where it says so, which a statement on the product page alone does not replace. It renders only where the card shows stars, and a link in it takes its own click above the card's link.

  • A swatch draws a value its name says best as a pill. shape="pill" draws the name as the swatch's text, for a size such as XXL, as tall as a swatch of its size and at least as wide. An unavailable pill strikes its name through and reads "unavailable" with it, and a color or an image given with a pill is a dot before the name. A swatch group takes shape="pill" for an axis such as sizes: each pill is drawn for the eye, and the option's name, with its language, is the one a reader hears.

  • A product card takes the values of an axis over its price. The options slot holds them, such as the colors the product comes in, each a link to its variant, and a link to the rest; its links take their own click apart from the card's link. The row takes the free space above the price, and the price follows it down, so the prices in a row of cards stand on one line whether a card has values or not.

  • A map takes its style or its tiles from the config when it sets none. components.map.maplibre.style-url is the style a MapLibre map loads, components.map.leaflet.style-url the tiles a Leaflet map draws, and components.map.leaflet.attribution the credit shown beside those tiles, so one setting keeps every map of an application on its own tile server. A map that sets its own style-url keeps it and shows only its own attribution. All three default to null: a MapLibre map then loads MapLibre's demo style, with its tiles and its fonts, from demotiles.maplibre.org, which receives each visitor's IP address, and a Leaflet map draws no tiles, as before.

  • A calendar takes disabled and readonly, as the date and datetime pickers do. disabled locks every control and takes the calendar's fields out of the form. readonly lets the reader page through the months and move between the days, picks nothing, and still sends the value. Neither stops an empty submit with required.

  • A card takes intent, which draws its border in the color of an intent. info, success, warning and danger take the border a callout draws for the same intent, in place of the border of the card's surface; neutral and no intent keep it. An intent the card does not know is reported.

  • A row with align-fields and wrap wraps its fields into lines, each lined up on its own controls. The fields take as many columns as fit at min, 14rem unless set, and every line of fields shares its tracks for labels, controls and messages, whatever the labels and hints on it carry. Below 48rem the row stacks, as every aligned row does.

  • A field takes an after slot for what follows it in its cell, a button that adds a value under a select for example. In a row with align-fields the content takes a track of its own below the messages, so the controls and messages of the row stay on their lines; a row without such a field keeps its height.

  • A dropdown item, checkbox item and radio item and the command palette's trigger take a shortcut as a list of keys, such as ['Ctrl', 'K'], as a menubar item and a command palette item do, and draws them with a space between them. A list ended the page before.

  • wirekit:doctor:props names a list item written as a bare element. A <dt> or <dd> written straight into a data list, or an <li> straight into a timeline, is reported with its line and the item to write, since such an element renders without the layout and the look of the item. See the CLI reference.

  • actions on table.td says which cell of a stacked table holds the row's actions. Where stack-below lays the rows out as cards, that cell stands as the card's footer without a name. Unset, it is a cell with align="right" and no label of its own, as before; :actions="false" keeps a column of figures aligned to the end as lines named by their column, and actions makes any cell the footer.

  • stack-below-container on table stacks the rows below a width of the table rather than of the window. Beside a sidebar or in one column of a wide page the window is the wrong thing to measure: a table in a narrow column scrolled sideways while the window was wider than the breakpoint of stack-below. The new prop takes the container scale a column's hide-below-container reads, sm to 5xl, and the rows stand as cards, each cell named by its column, as soon as the table has less room than its step, at any window width. It needs the table's frame, so a table with :responsive="false" reports it.

  • table on a chart renders its data as a table under it. A chart draws its numbers where a screen reader does not reach them, and its name is a summary, so the values were nowhere as text unless each page built the table by hand. The table writes one row per label and one column per dataset, each value as the tooltip writes it, with value-decimals, value-prefix and value-suffix, and names a missing value. table="disclosure", or the attribute on its own, puts it behind a summary every reader can open; table="screen-reader" keeps it out of sight. A Livewire update reaches it with the data, and chart-mixed takes the prop as well.

Changed

  • ApexCharts 8 is a supported version of the chart, and wirekit:verify no longer warns about it. The chart suite runs against 8.3.1 in a real browser, as it ran against 5, 6 and 7. A build that runs wirekit:verify --fail-on=warning no longer fails on the ApexCharts version when 8 is installed.

  • A calendar marks today with a dot under its number instead of a ring, and draws the month and year selects of selectable-header as pills. The ring competed with the fill of a chosen day; the dot stays visible on it, in the accent's foreground color. The selects keep the control border, on the muted surface.

  • An alert dialog with close-on-confirm needs a control that confirms, and strict validation says so. The option closed the dialog only through alert-dialog.confirm, so a dialog whose action was a plain button stayed up after the action, with aria-modal, over a page it kept out of reach, and nothing said why. A control marked data-wk-dialog-primary confirms as well now and closes the dialog the same way, and a dialog with neither is reported, as an exception in tests and on the console and a log line in a request.

  • wirekit:csp-audit fails on Js::from() and @js() in a directive attribute, so it will fail builds it used to pass. Laravel's encoder is the one for a <script> block. In an attribute it writes a non-empty array or object as JSON.parse('…'), which Alpine's CSP evaluator cannot run, so the expression parses and then dies without a message, and the audit listed it only as a pointer with the exit code at zero. It is now a finding of its own with its own exit condition, and the fix is one line: {{ \Pushery\WireKit\Support\AlpinePayload::from($value) }} writes every shape as a literal the evaluator reads, a number or a string as well as an array. The CLI reference lists the condition, and the examples for a toast flashed to the session and for the inbox blueprint use the attribute encoder.

  • The tooltip of an app rail module draws two or more waiting items as a list, with a bullet before each. A sentence that wraps goes on under its own words, so where one item ends and the next begins stays clear. One sentence, the line of how many more and the module's name take no bullet.

  • The chevron of a collapsible card stands in a circle with a border, so a closed card reads as one that holds more than its head. The circle is the new chevron class block, so a personalization can restyle it, and the arrow takes its color from it. A rule an application sets on .wk-card-chevron still reaches the arrow.

  • A select reports multiple and a list as its value. With multiple the browser drew a list box clipped to the height of a field, under a chevron that opens nothing, and nothing said so. Strict validation now names the multi-select for several values, and the attribute stays where it was, so a page that styled the list box keeps it. A list as value, which old('country') gives after a request that sent country[], answered the page with "Array to string conversion". It is reported the same way, and where strict validation only logs, the select renders with nothing selected.

  • An icon name on a menu, palette, navigation or floating-button entry is checked in every installation. A name no icon preset knows, on a dropdown item, a context menu or menubar entry, a command palette item, a sidebar or app rail entry or a floating action button, fails a console command or a test also without an icon package, as it does on an icon. A request still draws the page and logs the name.

  • The backdrop behind a modal, a drawer, an alert dialog, a tour and the command palette dims the page less. At half black in the light theme and seven tenths in the dark one, the page beside a dialog all but vanished, and the reader lost the page they came from. --color-wk-overlay now defaults to oklch(0% 0 0 / 0.35) and / 0.5 in the dark theme; a theme that wants the old strength sets it back. The lightbox keeps its darker backdrop under the new --color-wk-overlay-media, since a photo is looked at rather than read beside the page and its white caption keeps its contrast there.

  • The self-contained bundles carry Alpine 3.17.5. wirekit-alpine.js and wirekit-alpine.csp.js bring their own Alpine, and 3.17.5 no longer applies x-cloak again to an element a morph leaves rendered, so a Livewire update cannot hide what was already shown. The plugin bundles, which use the application's Alpine, are unchanged apart from the collapse plugin's version.

Fixed

  • A locale PHP's formatters refuse no longer takes the page down. Under an application locale or a locale such as POSIX, C, xx or de_DE@euro, a calendar, a datetime picker and a price, with every component that shows one, answered with an error page, in a request as well. They now write in the nearest locale the formatters take, down to the application's fallback_locale, and a locale they take stays as it was set. A component that formats in the browser was handed en-US.UTF-8 under en_US.UTF-8, which the browser's Intl refuses, and now reads en-US.

  • An x-model on a WireKit component binds the page's property whatever its name. The binding was read in the component's own Alpine scope first, so a property of the page named like one of the component's, selected on a combobox, password on a password input or value on a number input, bound the component to itself, without an error, and the page never received a value. It is now read in the page's Alpine component, with its modifiers, on a component's root as on a field inside it.

  • A color picker with popover shows a color an Alpine page writes into it. It kept showing the color it started with, or the last one picked in it, when an x-model of the page changed the value.

  • With optimistic, an x-ref on a textarea, select, checkbox, toggle or time picker reaches the $refs of the Alpine component around it. The field carried the component's own x-ref first, and the caller's was dropped. It is filed on the caller's Alpine component now, as on an input and a date picker.

  • Icons and marks in a code block, a color picker and a status matrix take their intent's text color. They took the fill color of the intent, which only has to suit the label on it: with a light brand green as --color-wk-success, the copied check of a code block stood at 2:1 on white. They read at 4.5:1 or more on the page now, as text in that intent does.

  • wirekit:make recipe:<name> writes the page its recipe shows first. Most scaffolds composed other components than the first example of their page: the documentation reader and the long-form article wrote a page without the reading shell their pages are built on, and the landing page a brand bar its page does not use. Each scaffold now builds that example, the landing page in the seven sections its page names as the smallest complete one.

  • A price in a currency given by its symbol writes its amount in the page's own format. Given € or another text where a code of three letters belongs, a price wrote the amount as it is written without the intl extension, 1,234.50 € on a German page, which a German reader takes for another number. It now writes the amount in the page's own pattern with that text where the symbol stands, so € reads as EUR does, and strict validation reports the value. An empty currency reads as none, so the default currency of the configuration counts instead of XXX, the code for no currency, and a list given as currency no longer answers with an error page. A product card, a pricing table, a cart list and a cart summary draw their amounts through it.

  • A radial progress with an infinite max reads it as out of 100, as a progress bar does. The ring stood empty, and screen readers were told a maximum of INF and 0% beside a value of 40.

  • Two bounds written the wrong way round read as the range between them, as a calendar already reads two days. A slider, a range slider, a number input, a cart line, a date picker's own field and the moments of a datetime picker took a min above their max as written: a field with no valid value, which a form never sends, a slider with no travel, a cart line with both buttons disabled. They now put the two in order. A time picker keeps a window across midnight, such as 17:00 to 09:00, which HTML defines.

  • A date picker without popover takes today, now and a date and time as the bounds of its own field, as its calendar does. It wrote them into its date field as given, and a browser passes over a bound that is no YYYY-MM-DD, so the field had no bound at all where the calendar of the same picker closed the days outside it.

  • A rating, a strength meter, a skeleton with its text and table forms, and an OTP input draw at most a hundred stars, bars, lines, rows, columns or boxes. Each drew one per unit of its max, lines, rows, cols or length, so a count of a hundred thousand from a broken record ran the request out of memory, an error no handler catches. A larger count now draws a hundred, and strict validation reports it. A count far below zero no longer ends the page of a skeleton or a strength meter either.

  • A field named with a space, such as first name, is described by the hint and the error it shows. A name may hold a space, and a field without its own id took the name as its id: aria-describedby="first name-hint" named first and name-hint, ids no element carries, so a screen reader read neither the hint nor the error. An id given with a space did the same, in every input and picker. Each run of whitespace in an id now becomes a hyphen; the field's name stays as given.

  • A tab keyed with a space, such as new york, and its panel name each other. The key went into both ids as given, so the panel's aria-labelledby named …-tab-new and york, ids no element carries, and a screen reader read the panel without a name; the tab's aria-controls split the same way. An id given to the tabs with a space did the same for every tab. A row of the scope switcher carried its key in its id the same way. Each run of whitespace in these ids now becomes a hyphen, and the key stays as given.

  • An image address that is empty, white space or false is no image. Given one, an avatar, a brand, a product card, a testimonial and the other components that show a person's picture, a logo or a thumbnail drew an image that cannot load, which shows the sign of a broken image, where they have a state without one: the initials, the name alone, a card without a picture. Each now shows that state. An image with no address draws nothing, an image gallery shows an entry's zoom in its place or leaves the entry out, an image compare reports a missing image through strict validation, and the attendees of an event calendar show their initials.

  • A link address that is empty or white space is no link. A button, a text link, a card and the other components that become a link with an address drew one to the page they stand on when the address was white space, and the entries of a navbar, a sidebar, the bottom navigation, the app rail and the navigation menu did the same for an empty address and for false: a screen reader read a link, and a press loaded the page again. Each now draws as it draws without an address, the entries with their own #, and the name of a cart item given false is no link.

  • A link address of 0 is a link. A button, a text link, a card, a product card and the entries of a dropdown, a menubar, a context menu, the navigation menu, the command palette and the floating action button asked whether their address was true, and 0 is not: given href="0", they drew a button or an element without a target. Each now draws the link, as the steps of a stepper and the entries of a navbar or a sidebar already did.

  • A date that is no date reads as none. Given false, a countdown, a review, an event calendar, a message and a date separator drew 1 January 1970, and given the zero date 0000-00-00 that older MySQL schemas write for no date, 30 November of the year -1, which reads as 2 December of the year 2. Both now read as no date, and so does a date of white space, which strict validation reported as one it could not read.

  • A date that cannot be read is reported on a date picker, a time picker and the events of an event calendar. A min or max such as 2026-13-45 or a word set no bound on a date picker and a time picker, without a report, and the time picker wrote it into its field as given. An event calendar's event with such a start stood on no day, and one with such an end lasted no time. Each is now reported through strict validation, as the date of a countdown or a review already was, and counts as none: the field has no bound, the event is left out, and an event whose end counts as none takes the length of one without an end. An event whose start is no date, null, false, an empty text or the zero date, is left out without a report; null and false put it on the first moment of 1970 in UTC. The value of a date or time field is not checked, since after a failed validation it holds what the reader sent.

  • An event calendar places an event or a day marker whose date is written in another form than ISO 8601. A date the server reads, such as 31.12.2026 or a Unix timestamp, reached the browser as written, where Chrome reads 31.12.2026 as no date and every browser counts a number in milliseconds: the event and the marker stood on no day, or in January 1970. Each now reaches the browser in ISO 8601, a time without a zone as the wall-clock time it names, and so does a date and time written with a space, as a column without a cast holds it, in the form ECMAScript defines for every browser.

  • A time written as text reaches a time field in the form the field reads. A time picker and an input with type="time" wrote 9:30 into their field as given, and a field takes two digits for the hour, so the value stood empty and min="9:00" bounded nothing. A time with seconds from a time column, such as 08:00:15 given as min on a field that counts minutes, is the base the step counts from, so no whole minute was a step and the form refused to send. Each is now written as the field reads it, 09:30, with its seconds only where step counts them, as a date object already was, and a datetime-local field takes 2026-03-15 9:30 as well. The list of a time picker with popover reads no time from the zero date 0000-00-00 00:00:00, which names no date.

  • A keyboard key with no key to show draws nothing. A shortcut of white space, and in a request one strict validation reports, drew an empty key cap where its slot was empty as well, and a menubar item with a blank shortcut drew one in its row. A slot still stands in for a shortcut that names no key.

  • The caption of a table that scrolls sideways breaks within what shows of the table. A caption was as wide as its table, so under a table wider than its scroll area, such as a comparison on a phone, its sentences broke at the width of the whole table and the scroll area cut them mid-word. A caption is at most as wide as the space the table stands in now; under a table that fits, nothing changes.

  • An event calendar with a locale in another language than the page marks what it shows in that language. Its heading, its weekdays, its hours and its times stood in the locale with no lang, so a screen reader read them in the page's voice, and the accessible name of an event or a day set a date in the locale between words of the page, such as "All day", so one name held two languages. What the calendar shows in its locale carries it in lang now, a time that can read "All day" only while it is a time, and every name a screen reader hears, with the plural form of its sentence, is written in the page's language.

  • A kanban board inside a Livewire component stays draggable and reachable from the keyboard after an update that leaves its cards as they are. The board gives each card the attributes that dragging, the Tab key and a screen reader read, and an update that changed no card took them off again, so a pointer could no longer drag a card and Tab no longer reached one until a card moved. The confirm control of an alert dialog that waits for its confirmation phrase stays announced as unavailable, with its reason, through an update while the dialog is open, and a control that a page puts in the editor slot of an inline edit keeps its name and description through an update while it is open.

  • Every size an attachment shows reads in the page's language and in the application's own number format. A size in bytes and a size from ten up were written as plain digits, past the locale and past WireKit::formatNumbersUsing(), beside the sizes below ten, which went through both. A card could read 2,4 MB through the application's format beside 512 B and 24 MB without it, and 1022 KB stood ungrouped where the locale groups it.

  • A number written with no set decimals keeps its own without the intl extension. Without intl, a figure that asks for no number of decimals was rounded to a whole number, so a usage meter read 8 / 10 GB for 7.5 GB where the locale's way writes 7.5 / 10 GB. It now keeps up to three decimals with no zeros at the end, as it does with intl.

  • A file upload with multiple and wire:model holds on its property exactly the files its list shows. Livewire 4 adds every selection of a multiple field to the files the property already holds, and removing a file hands the field the files that stay. After a removal the property still held the removed file and the others twice, and a second selection joined the first while the list showed only the second, so a save stored files the reader had taken out or replaced. A new selection now replaces the one before it on the property as it does in the list. With upload-separately the files go up one after another, and the first that arrives replaces what the property held. A file whose upload is canceled leaves the list as it left the property, and one that failed stays in it and says so. A selection none of whose files arrives empties the property, without a request of its own, so the message the failure left stays and the selection before is not stored.

  • The marks and the value bubble of a slider follow a right-to-left page. Under dir="rtl" the browser draws the lowest value of the range on the right, while the marks and the bubble stood from the left, so the label of the lowest value stood over the highest one and the bubble pointed at the mirrored position. Both now stand from the start of the reading direction, as the thumb does.

  • The mark labels of a slider no longer run into each other on a narrow track. Each label was placed from its position alone, so on a phone two longer neighbors, such as "Empathetic" and "Enthusiastic" on a scale of five, ran together. Labels that would touch now move apart, each only so far that its tick stays under it, and where that does not make room the slider shows every second label, or every third, always with the first and the last. What the slider announces does not change.

  • Tabs inside a Livewire component survive an update that reorders, adds or drops a tab. Each panel was opened in one of two branches and closed after them, so Livewire's block markers stood unpaired and the update threw "Cannot read properties of null (reading 'before')". Every panel is one element now, with and without findable.

  • Inside a Livewire component, the suffix of an input and the brand of an app rail are updated as a block when they come or go. A comment in the code of the tag before each of them made Livewire read the block as part of the tag, so it marked the block at its end only, and an update patched the suffix or the brand by its position among the nodes around it.

  • The hints of a collapsible card drop under its heading where the head has no room for both, as the buttons of a card header do. The heading, the hints and the chevron stood on one line that never wrapped, so a heading with a word that does not break, such as a product code, and a badge in the hints made the card wider than its column on a phone, or the heading ran into the hints. The chevron keeps to the end of the head.

  • A read-only or disabled editor shows its document once the engine takes over. Neither has a form field, and since 2.66.0 the engine read the document only from that field, so on a page that loads an engine it replaced the first paint with an empty document: a moderation queue showed blank posts, and a form locked for review a blank body. The document given as value now reaches the engine without a form field, and the form still submits nothing for either.

  • A branch of a tree view inside a Livewire component stays announced as closed after an update. A branch rendered open and closed by the reader got its open state back from the server on the next update, so a screen reader heard "expanded" over a hidden group. The row now binds aria-expanded to the branch's state, which the update keeps.

  • A tree view, a resizable split and a status matrix inside a Livewire component keep what the reader did across an update. An update put the server's markup back over what their scripts had written: a tree without a selected node lost its tab stop, so Tab passed it by, the handle of a split lost its role, its place in the tab order and its values, and the split jumped back to its default size, and a status matrix sent its tab stop back to the first cell. The tab stop stays where the reader left it, and the handle and the split stay as the reader set them.

  • A move on a kanban board inside a Livewire component is announced after the first one as well. The live region the keyboard path speaks into was made by the script, and the update that saves a move took it off; the next sentence landed in a region made together with it, which a screen reader does not announce. A sortable column renders the region itself now.

  • A dropdown inside a Livewire component keeps the link from its trigger to its menu after an update. The update took the menu's id off, so the trigger's aria-controls named an element that was not on the page until the next full load. The menu takes its id back as soon as an update removes it.

  • The chevron of a dropdown trigger turns on its own control alone. It turned whenever any element around it reported itself expanded, so in a row of a tree or a table that a reader had opened, the chevron of a closed menu pointed up. It now follows the nearest element that reports a state, the button it stands in.

  • A long label no longer widens its tab of a bottom nav. With a mouse, a label with a long word, as German has many, made its tab as wide as the word and pushed the bar past its frame, so the label was never cut and never showed whole on hover. Each tab now takes its share of the bar and cuts its label; on a touch screen the 44px floor of a tab stays.

  • A long label of a data list in detail breaks inside its column and leaves the value its room. The label's column was as wide as its text unbroken, so a label with no space in it, such as a file name, ran past the list on a phone and left the value a few pixels, broken letter by letter. The column now holds its content up to 10rem, or up to 40% of the row where that is wider, and a longer label breaks inside it. In summary the value's column has the same limit, so a long code beside a label breaks in its column instead of pushing the label out. Labels and amounts within the limit stand as before.

  • A price wider than its column breaks its line, and "from" stays with its amount. The line never broke, so in a narrow column, such as a table cell on a phone, a price ran past the column and widened it. It now breaks between its parts, and the word "from" and the amount stand together: the struck price on the first line, "from €179.10" whole on the next. A screen reader reads the price as before.

  • A row in the slot of a cart summary stands in its columns. A data-list.item there took the default layout rather than the summary's, since the slot renders before the summary's own list: a box of its own in one cell, its label at a third of that cell, so a longer label broke over several lines and two rows could stand side by side. It now stands like the rows the component draws, the label on the left and the amount flush right under the others. A row that names its own layout keeps it.

  • The description of a product card without a link shows whole on hover. It is cut after two lines; a card with a link leads to the whole text on its page, and one without showed the rest nowhere. Such a card carries the description as its title now.

  • A long label of a compact file upload shows whole on hover. The compact control cuts its label so the row it sits in keeps its width, and nothing showed the rest of it. The cut label carries its whole text as its title now; a screen reader always heard it whole, as the name of the field.

  • The edge shadow of a table that scrolls sideways is as strong at its top row as anywhere else. It runs the full height of the frame, and it was a radial gradient dense only at the middle of that edge, so on a list taller than the screen the rows a reader sees first showed almost none of it and a cut-off column gave no sign that the table continued. Every scroll shadow is now a linear gradient across its strip, equally dense along its whole edge: the sideways pair of the table, the data table and the segmented control, and the top and bottom pair of the sticky panel, the scope switcher and the sidebar, mirrored right to left as before.

  • The first unsaved change of a status matrix, the first selected row of a data table and the empty state of a command palette are announced reliably. Each was a live region that showed together with its text: the matrix's count behind its own toggle, the table's count in the bar that shows with a selection, and the palette's "no results" behind the toggle that shows it. A screen reader announces text that appears in a region it already knows and a region that appears with its text unreliably, so the first change could go unheard. The three regions are in the page from the first render now, and only their text changes. command-palette.empty carries no role of its own, since the palette reads it out from its status region. A role passed on it still lands, and the palette then leaves the announcement to that role: role="none" keeps an application that announces the empty state itself from being heard twice.

  • The reading time follows an article that a Livewire update renders. The update took the per-paragraph annotations out of the article and none came back, and the estimate stayed at the words the page loaded with. It counts the article again in the next frame now, puts the annotations back, and leaves its own annotations out of the count.

  • A reading minimap in rendered mode keeps its picture through a Livewire update. An update that left the article as it was took the drawn minimap out, and it stayed empty until the article changed. The hover preview keeps its frame and its place through an update as well.

  • A table sorted with alpine-sort inside a Livewire component stays sorted through an update. The update put the rows back in the order the server rendered, while the header went on announcing the sort and showing its arrow. The table sorts again before the next paint now, by the values the update brought, and a sort that already holds moves no row.

  • A panel opened below its trigger stays beside it on a short window. The navigation menu, the popover with a top or bottom placement, and with it the scope switcher, the color picker, the filter builder and the notification center moved a panel taller than the room below its trigger up until it fit the window, and so over the trigger: at 400% zoom a mega menu covered the trigger that keeps the focus while it is open, and the color picker's panel the swatch that shows its value. The panel now stays on the side of its trigger with more room, capped to that room and scrolling inside, and a wide panel still moves sideways to stay on a narrow screen. A context menu still moves into the window over the point it opened at.

  • A data table follows a selection its component clears. The selection lived in the browser alone, so a bulk action that emptied the bound list left the bar showing the old selection, and the next change sent its ids again. With selected, and all-matches for every match, the table opens with the component's selection and takes it from a response that changed it, while a click made after the request left stays the reader's.

  • The header checkbox of a data table in server mode selects and clears its page, and leaves the other pages alone. It replaced the selection with the rows on screen, so selecting page two dropped what the reader had selected on page one, and clearing it cleared every page. Its mixed state now describes the page, where a selection on another page alone showed it mixed.

  • A panel placed beside its trigger that fits on neither side opens below or above it. A hover card, a multi-select or a dropdown placed left or right, a tour step beside its target and the submenus of the dropdown, the menubar and the context menu flipped to the opposite side only, and on a narrow screen they stood partly outside the window: a hover card is 288px wide, so beside a trigger on a 375px phone it rarely fit. Where a side has room, nothing changes.

  • A submenu taller than the window scrolls in itself. The submenus of the dropdown, the menubar and the context menu had no limit to their height, so on a page zoomed to 400% or a phone held sideways a long one ran past the bottom of the window, where neither a scroll nor the focus reached its last entries. They are capped to the window and scroll, as their menus already did. So is a step of a tour, whose Back and Next stood past the edge of a short window while its focus held the reader there, and which is no wider than a 320px window either.

  • An overlay refuses a placement it cannot place. A tooltip, a popover, a hover card, a dropdown, a tour step and the submenus took any word, and one outside the twelve placements, such as botom, put the panel on the corner of its trigger with nothing said. In debug the word is refused with the one it most resembles, and in production the component uses its default and logs the word, as the combobox and the multi-select already did.

  • A prop that takes one of a few values reports any other. The size of a combobox, a checkbox, a date picker and twelve more form controls, the position of a toast region and a scroll-to-top button, the variant of an accordion, a navbar and a sidebar, the orientation of a stepper, and fifty-three more props took any word and rendered their default for one they did not know, while an input and the other components reported the same word. So did a prop that takes a switch or one word: truncate="tooltp" on a text cut the line without its tooltip, as true does. Each now goes through strict validation as every prop with a fixed set of values does: with debug on, a Pest test or an artisan command throws on an unknown value, and a request logs it and renders what it rendered before. A bound null or an empty string is still the default, without a word. A prop whose default is none, such as an avatar's status, stays none for an unknown value rather than taking the first of the list. A stats grid given its cols as a number, :cols="3", now lays out three columns, where it fit its stats to the width before. A chart whose stream mode was another word than strict or stream let a streamed series grow without a bound; the word is reported and the series is capped, as strict does.

  • A prop that takes a number reports a text that holds none. On most such props a text read as the default without a word: a delayShow of soon on a tooltip, a maxTags of ten on a tags input, a weekStartsOn of monday on a calendar, a max of 1OO or a step of 0,5, while a prop with a fixed set of values reported an unknown one. Each now goes through strict validation: with debug on, a Pest test or an artisan command throws, and a request logs it and renders as before, with the default or, for the few props that take the number a text starts with, such as the searchDebounce of a data table, with that number. An empty text and a number written as text pass as before. The value of a field is not checked, since after a failed validation it holds what the reader sent, such as the minValue and maxValue of a range slider.

  • A prop that takes a list reads a text as an empty list instead of ending the page. options="Red,Green" written without its colon arrives as a text, and so does a JSON column read without its array cast. A select, a combobox, tabs, a stepper and nine more components ended the page with a type error at the first loop over it, and the other list props read the text as nothing without a word. Each now goes through strict validation: with debug on, a Pest test or an artisan command throws, and a request logs it and renders the component with an empty list. An empty text is an empty list without a report. A prop that takes a text as well reads it as before: the name of a message's author, the comma-separated value of a multi-select or a tags input, and a single country written as text on a phone field. The arguments of a chart keep the types its class declares: a text in its labels, datasets or options still ends the page with a type error that names the argument.

  • A prop that takes a text reads a list as none instead of ending the page. A label, a hint, a title or another text prop given a list, a collection or an object without a text of its own ended the page with a type error in most components, in a request too: __('checkout.address') returns a list when the key names a group of lines rather than one. Each now goes through strict validation: with debug on, a Pest test or an artisan command throws, and a request logs it and renders the component without the prop. An object that writes itself as text, such as an HtmlString or a date, is read as before, and so is a list given to the author of a message or to the badge label of an app rail item.

  • A label given as markup, an HtmlString, names its element by its text. Written into aria-label, placeholder, title or aria-valuetext, its markup stood in the attribute: a screen reader read the tags out, and a double quote in it ended the attribute early, so the rest of the tag landed on the page as text. Every component writes the text of such a prop there, and where a script shows it, the panels a dropdown, a menubar, an alert dialog or a drawer places at the end of the page and the search field of a command palette included. An id or a menu's name derived from the label takes its text as well, while the markup stays where the component shows the prop on the page. The same holds for the props that carry a text under another name: the alt of an avatar or a flag, the before-alt and after-alt of an image compare, the name of an assistant message, a profile, an attachment or an item of a cart list, the filename of a code block, the trigger of a navigation menu item or a collapsible, the question of an unsaved hint, and the tooltip that names a toggle button without a visible label. So do the names a component writes in one of its forms only: the clear button of an input and of the date, time and date and time pickers, the end field of a date range, the name of a stat with an intent, the remove button of a dismissible badge, and the placeholder of a translatable input with several lines. The unit of a cart item shows its markup, and the placeholder in the trigger of a date picker's popover its text, as the trigger of a date and time picker does.

  • A field's help given as markup shows the markup in its tooltip, not its tags. An HtmlString as the help of any field showed its tags as text in the tooltip, and so did one as the heading of shortcuts. The placeholder of a date and time picker, the name of the menu of an overflow nav, the words of a sign-in button and the name a tooltip gives its trigger read the text of such a value instead of its tags.

  • A value shared with every view no longer fills a prop or a slot the caller left out. An application that shares its page title through View::share saw it as the heading of every alert and empty state written without one, and a value shared under the name of another prop, such as label, size or href, reached every component that declares it. A value shared under the name of a slot did the same: a status shared from the session stood under every filter builder, and a text shared as meta ended the page of every page header written without that slot. A prop the caller leaves out takes its default, a slot reads as not given, and every other view still reads the shared value. The components still read the shared error bag.

  • A composer the application registers for every view changes no prop or slot of a component. A composer for * that writes a title into every view replaced the title of every alert and empty state, the one a caller wrote on the tag included, since it runs after the component has read its tag. A WireKit view renders the props and slots its caller wrote and its defaults without them, and every other view still gets what the composer gives it. That holds as well for a composer another package or the application registers while the application boots, and for one the application registers in the register() method of a service provider. A composer registered after the application booted, in a middleware for instance, still runs after this.

  • A component inside another takes its parent's settings, not those of an unrelated ancestor. Whenever their own parent was written without the setting, the questions of a FAQ took the size and the heading level of a card around it, the items of a navbar inside a compact toolbar were compact, and a table inside a page header written with stack-below stacked its rows. The items, tabs, steps and cells of an accordion, a FAQ, a tab list, a navbar, a sidebar, an app rail, a bottom navigation, a data list, a wizard and a table take such a setting from their own family now, past any other component between.

  • A table inside a cell of another keeps its own stripes, hover, density, sticky head and frozen column. The parts of a table read striped, hoverable, compact, sticky-header and sticky-column from a table around them at any depth, so a plain table in a cell of a striped, compact table was striped and compact, its head stuck in the outer table's scroller, and its first column froze at the outer table's edge. Each part reads them from its own table now, as the frozen column of the stylesheet already did, and so does the padding the stylesheet gives the plain cells of a table.

  • A carousel in a slide of a carousel of the other orientation keeps its own layout. A vertical carousel in a horizontal one took the gutter the horizontal one keeps for its arrows, and the slides of a horizontal carousel in a vertical one took the full width and height of a vertical carousel's slides, so one showing several slides at a time showed one. The stylesheet reads the orientation from each carousel's own root now, as it already read per-view.

  • A carousel counts, shows and steps through its own slides only, with a carousel in one of them. The slides of the carousel inside counted among the outer carousel's: it drew a dot for each of them, could take one of them for the slide in view, and End went to the last of them.

  • A table sorted with alpine-sort moves only its own rows. With a table in one of its cells, sorting moved the head and the rows of that table into its own body, and a head of that table naming the same column decided which cell a row was sorted by. The sort reads its own body, rows and head now.

  • A menu, a tooltip, a popover or a dialog opened in a light or a dark island takes the island's mode. The panel is placed at the end of the page, outside the island, so it took the page's mode: the menu of a button in a light island on a dark page opened dark. A panel takes the class of the nearest island it was opened from, keeps it through a Livewire update, and reads it again each time it opens or closes. A panel whose own markup names a mode keeps its own.

  • A panel opened in a section that sets lang or dir names the same language and direction. A menu, a popover or a dialog in a section with lang="de" on an English page stood outside that section at the end of the page, so a screen reader read its entries as English, and in a section with dir="rtl" its text ran left to right. Like the mode of an island, the panel takes both from the nearest element around it that sets them, keeps them through a Livewire update and reads them again each time it opens or closes; what its own markup names, or another script writes, stays.

  • An overlay opened inside another keeps the outer one open. A combobox, a dropdown or a picker in the panel of a popover stands beside that panel, since every panel is placed at the end of the page, and a press on one of its options closed the popover. The same held for the panel of a navigation menu and the menu of a menubar, and a hover card closed a moment after the focus moved into the menu of a dropdown in it. A popover opened in the panel of another closed the outer one, and its own trigger with it. Each now counts a panel opened from inside its own as inside, and an overlay of the same kind opened inside it is no longer taken for a sibling.

  • A key an element inside a menu already answered leaves the menu as it was. In a dropdown or a context menu, the Escape that dismissed the tooltip of a cut label in an item closed the menu as well and moved the focus to its trigger, and an arrow key or a letter that an element inside had already taken still moved the focus between the items. A key that something inside the menu answered is spent now, as it is in a dialog, and the next Escape closes the menu. With the focus in the menu of a dropdown inside a hover card, Escape closes that menu and leaves the card, which closes on the next one.

  • The arrow keys of tabs stay in their own row with a tabs in a panel. Tabs nested in one of the panels put their tabs under the same element as the outer row, and the arrow keys, Home and End walked both: from the last outer tab the focus went on into the inner ones instead of back to the first, and End landed on the last inner tab. They walk their own row now, as the tabs pattern asks.

  • A pagination given something other than a paginator says so. A number, true or a list ended the page with a type error that named neither the component nor the prop, and a collection, as all() returns where paginate() was meant, rendered no pages without a word. Each is reported through strict validation now and renders nothing, as no paginator does.

  • A number beyond the integer range ends no page on PHP 8.5. Given to the surface of a badge, a button or a card, the variant of a card or an empty state, the intent, tone or size of a feature, the layout or asideWidth of a hero, the size of an icon, the kind of an activity row, the preset of a reveal, the intent of a spinner or the value of a rating, a number such as 1e21 ended the page with a warning about casting it to an integer. It is read as any other value that does not fit now, and strict validation reports the props that take a word. The same held where one branch casts a prop: the progress of an uploading attachment, the searchDebounce of a data table or a filter builder with a search field, the max of the deprecated progress.circle, the size and margin of a QR code, the detailsColspan of a table row, the step of a time picker with popover, the months of a date picker with popover, and the maxlength of an input or a textarea with counter. Below PHP 8.5 the number turned into an unrelated one there, such as a search field waiting 8446744073709551616ms for -1e19. It reads as zero progress, no wait, the default maximum, size and quiet zone, no span, no step, the default months and no limit now.

  • A calendar draws the layout for the months it shows. Given a months that holds no whole number, 1e21 or 1.5, it drew one month in the layout for several, since its view read the value as given and its script as one month. The view reads it as the script does now, one to four months, and a date picker and a datetime picker hand their calendar that number.

  • An id with a space keeps the references built on it. Given one, such as an id written from a row's name, an inline edit named it in its label's for while its field wrote the space as a hyphen, so the field had no name from its label and its hint was not read. A data table's caption and the panels of a sidebar's collapsible sections and groups were named by references that no element carried, since a list of ids splits on a space. Each writes the space as a hyphen now, as the other components do.

  • A delay of 2^31 milliseconds or more waits as long as a timer can, not no time at all. Browsers read the delay of setTimeout and setInterval as a 32-bit integer and run a timer whose delay does not fit at once. Given such a number, PHP_INT_MAX among them, the duration of a toast region, the delayShow and delayHide of a tooltip or a hover card, the interval of a carousel, the duration of a clipboard button, the showAfter of a page progress, the simulateSpeed of a stream, the searchDebounce of a combobox, a multi-select, a data table or a filter builder, the submitOnceRelease of a form and the saveTimeout of an inline edit waited none: a toast meant to stay closed at once, and a form's guard against a second submit held for nothing. Such a delay is held to the longest a timer waits now, and a carousel given an interval of zero or below turns at its default instead of as fast as the browser lets it.

  • An icon in running text is followed by no space, and a missing icon takes the display a class gives it. A named icon ended its output with a line break after the SVG, and the placeholder of a missing icon after its span, which a page shows as a space between the icon and the period or comma that follows. The placeholder also wrote its display inline, so a block or hidden class on the tag never reached it.

  • A radio item of a dropdown takes only the name of an Alpine property as its model. The value was written into four Alpine expressions as it came, so a model holding anything else, such as a value taken from data, ran as code in the browser. A name or a path such as filters.sort binds as before; anything else is reported through strict validation and the item renders without a model.

  • A date that names a day that does not exist is reported, not moved into the next month. A countdown, an event calendar, a message, a review and a date separator read 2026-02-30 as 2 March without a word. Such a date is reported through strict validation now and read as no date, as a text that names none is.

  • A calendar, a date picker, a datetime picker and a time picker report a prop written wrong that they dropped without a word. An openTo that names no day or month (no day or moment on a datetime picker), a minRange or maxRange that holds no number, a defaultTime that names no time and a format other than 12h or 24h are reported through strict validation now, as min, max and minTime are. In a request each still draws as if it were left out.

  • An OTP input with parentheses, brackets, braces, a slash or a bar in its alphabet keeps its pattern. Browsers compile a field's pattern with the v flag, under which these characters are syntax inside a character class, so the pattern did not compile and the browser ignored it, with an error in the console. They are escaped now, as the backslash, the closing bracket, the caret and the hyphen were.

  • A component without the part it needs says which part that is. A popover, a hover card and a context menu without their trigger slot, and an image compare without its images, ended the page with an undefined variable; each names the slot or the props it needs now. An icon without a name is reported through strict validation and draws the placeholder a missing icon draws, where it ended the page with a type error.

  • A switch reports a text that says neither on nor off. A prop such as the dismissible of a modal read true, false, on, off, yes, no, 1 and 0, and any other text as on without a word, so dismissible="flase" left the dialog dismissible. Each switch now goes through strict validation: with debug on, a Pest test or an artisan command throws, and a request logs it and renders as before, with the switch on. An empty text is off, and the prop's own name, disabled="disabled" as HTML writes it, is on, both without a report. The wrap of a row reports a word other than responsive, as the other props that take a switch or one word do.

  • An entry of status tiles without an intent draws a neutral tile. It ended the page with a warning about the missing key, while the component meant neutral for it.

  • A range shows a validation message filed under one of its ends. A date picker with range sends {name}[start] and {name}[end], and a rule on one end files its message as {name}.start or {name}.end. The picker read the error bag only by {name}, so after a failed validation it showed nothing and was not marked invalid. It now shows that message and carries aria-invalid, as it does for a message filed under its name. A range slider does the same for a rule on one handle, {name}.min or {name}.max.

  • A form control without a name is no longer sent under a name WireKit made up. An input, a toggle, a select, a tags input, a multi-select and twelve more took their id as their name, the last two with [] after it, and without an id from the page that was one WireKit counted out: an unnamed search field in a GET form put input-1=… into the address. Such a control now sends nothing, as the input page said it did. An id the page gives still names the field, and the radios of a single-choice swatch group keep a shared name, which is what makes them one choice. An OTP input without a name still writes its code to the field wire:model binds.

  • A readonly on a toggle, a checkbox, a radio, a slider or a color picker is reported while debugging. HTML ignores readonly on a checkbox, a radio, a range and a color field, so these controls stayed operable, and the strictness gate counted them among the components that take it and said nothing. They now report it as doing nothing, and StrictnessGate::controlAttributesWithoutAControl() gives a test the same answer.

  • A color picker with popover hands autofocus to its trigger. The attribute landed on the element around the trigger, which cannot take the focus, so the page opened with nothing focused.

  • An inline edit no longer stops the form around it from submitting. Its editor stays on the page while the value is shown, and a form checks every field it owns, shown or not. With required and an empty value, or as a number off its step (a price of 12.99 against the default step of 1), the hidden field failed that check, and the form's submit button did nothing, with no message on screen. The field of a closed editor is disabled now, which leaves it out of the check, and an open editor is checked as before. The page shows the same binding for a control in the editor slot.

  • What acts on a field reaches it on three more components. A min, max or step on an inline edit with a number now reaches the number it opens. It landed on the element around the component, so the number had no bounds and stepped by one, and its arrows rounded a price to whole numbers. A step or list on a date picker with range reaches both dates, as it reaches a single date. A pattern on a translatable input reaches every language's one-line field, as maxlength already did.

  • A disabled or autofocus written as "false" turns nothing on. HTML reads both by their presence, so disabled="false" on a field set disabled every field in it, on a copy button, the close button of a modal or a drawer and seven more it disabled the button, and autofocus="false" focused a button, a tab or a menu item as the page loaded. A value that reads as false now leaves the flag off, as it already did on the form controls, and a bare flag or "true" turns it on as before.

  • An aria-describedby on a translatable input describes every language's field. It landed on the element around the languages, which nobody focuses, so no field read it. Each language's field now reads it after the message of the whole value, as every other form control does.

  • A tabindex on a file upload, a multi-select, a tags input, a one-time code or a translatable input reaches its fields. It landed on the element around them, so tabindex="-1" took none of their tab stops out, as on a honeypot field or a field in a folded section, and made the wrapper focusable instead.

  • x-on:blur, x-on:focus and wire:blur on a form component made of several parts run as the reader leaves or enters the control. A tags input, a multi-select, a one-time code, a file upload, a date range, a color picker in a popover, a range slider, a rating, a segmented control, a swatch group, an editor, an inline edit and a translatable input take focus on their parts and never on the element the attributes land on, and neither event bubbles, so such a listener never ran. Each now fires both there, once on the way in and once on the way out, as a field does, and a move between its own parts or into a panel it opened elsewhere on the page fires nothing. A tags input and a multi-select fired the blur already and now fire the focus as well.

  • A color typed into a color picker reaches the form as it was typed. The plane and the hue slider hold saturation, brightness and hue in whole steps, and a typed, picked or starting color was read into them and sent back as the nearest color they reach: #B87334 arrived as #b87333. The color now stands as it was read until the reader moves the plane or the hue slider.

  • A filter builder panel, a dropdown or any element with x-show and a transition, opened again while it closes, reports no unhandled rejection. Alpine rejects the hide that the show cancels with a marker it treats as expected, but handles the rejection one frame later, so an opening inside that frame left it unhandled: the console read "Uncaught (in promise)", and an error monitor or a browser test that listens for unhandled rejections counted an error. WireKit's bundles give every hide a handler as it starts, and any other error in a hide still reaches the console.

  • A filter builder bound with wire:model.live keeps a panel open when the answer to the last filter arrives. A reader who applied a filter and opened the panel again before the server answered saw it close under them, and what they had entered was gone: the answer renders the builder again with the filters it holds, and that render reset the builder. The answer now changes nothing a reader has open. A set of filters the server changes still reaches the chips, and an edit that is open stays on its filter, or ends when the server removed it.

  • A calendar, a color picker, a date picker range, an image comparison, a number input, a phone field, a slider, a range slider, a tags input, an editable status matrix, a toggle button and a branch switcher bound with wire:model.live keep what the reader did after a change when the answer to it arrives. Each of them took the value it shows from the expression that starts it, so the answer, which renders the value its request carried, started the component again: digits typed since, a tag added since or a second click went back, a drag ended, the sentence about a corrected number disappeared, an open color panel closed, and a calendar returned to the month of its date. Through the binding the older value even reached the property. Each one now follows a value the server sets without starting again, and leaves alone an answer that only returns what it sent, as Livewire does with the property.

  • A range slider bound with wire:model.live keeps a second step taken while the first is on its way. Each move fired input on both of its hidden fields, so on the first move the end that had not moved was written again as text where the server had sent a number. Every key of the range had changed, and Livewire sent the range as one object and kept that object itself as what the request carried. A step taken before the answer changed it as well, the answer then read as the server taking the step back, and the property kept the first step while the handle showed the second. Now input fires only on an end that moved, and on an end whose thumb does not show the stored value: both ends of a slider without min-value and max-value, whose first move replaces the stored range, and an end the scale held inside it.

  • The tooltip of a cut text shows it in its own language. With truncate="tooltip" the tooltip repeats a cut text whole, and it stands outside the text, so a name in another language, such as a product name marked with its own lang, appeared there in the language of the page, with the glyphs and quotation marks of that language. The tooltip now takes the language of the closest element that names one. Screen readers were not affected: they read the text from the element itself, which carried its language already.

  • An attribute bound to null leaves a component's own name, role and id in place, and so does a name, a description or a live region bound to false. Blade writes nothing for either, so :aria-label="$label" with no label wrote no name, but the components counted it as given and left their own out: a navbar lost "Main navigation", the dialog of a modal its name, a combobox its role, a phone field autocomplete="tel" and a data table its id, and a number input marked its steppers disabled for a null disabled. Such an attribute now counts as not written, in every component.

  • An empty name leaves a component its own. :label="$section->title" over a record without a title hands a component an empty text, and a component that names itself wrote it as its name: a theme controller, a button that shows a symbol only, lost "Dark mode", a bottom navigation "Main" and a rating "Rating", and a cart summary wrote an empty heading. An empty label, aria-label or placeholder now hands over to the next name a component knows, down to its own, and so does the empty label of a part, such as the words of a filter builder's add button, which had left the button without a name, and an aria-label, aria-labelledby or aria-describedby given as an empty text counts as not written, in every component, so the dialog of a modal with a header is named by its header.

  • A countdown written into a sentence keeps the paragraph whole. It rooted in a <div>, and a <div> inside a paragraph ends the paragraph in the parser: the time and the rest of the sentence fell out of it, and an empty paragraph followed. Everything a countdown renders is text-level, so it roots in a <span> now, which looks the same. A headless slot that holds block content takes as="div".

  • A ticker's delta and the number of reviews on a product card read in the page's language. The delta was written as PHP writes a number, with a point in every language, so a German dashboard read +8.4% beside its translated words, and the name of the delta said the same; an absolute delta and the number of reviews stood ungrouped. Both go through the number format of the page now: +8,4 % and 1.234 Bewertungen in German. A delta that is no number is shown as it comes.

  • A badge without text draws nothing. :badge="false", a badge of spaces and a badge slot that holds nothing, or only the markers Livewire writes around a condition, drew an empty pill on a dropdown item, a sidebar item, an app rail item, a bottom nav item, a tab, an indicator, a product card and an activity row. They draw nothing now, and 0 stays a count. The name of a bottom nav entry reads its count as the pill shows it, 1,234 rather than 1234.

  • A count in the badge of a tab given through items is grouped as the page writes numbers. A tab of its own wrote 1,234, and one from items wrote 1234; both write 1,234 in English and 1.234 in German now.

  • Page Up and Page Down in a calendar keep the day. They moved the focus to the first of the month they went to, where the date picker dialog of the WAI-ARIA practices, which the calendar follows, keeps the day's number, or takes the last day of a shorter month.

  • An expired countdown stops its clock. Past the deadline it went on updating itself every second, or less often when it showed no seconds, for as long as the page stayed open, with nothing left to change: it shows its expired text, and remaining holds at zero. The tick stops at the deadline now, and a tab that comes back does not start it again.

  • Words in a replay button's slot name the button. Its label named it whatever the slot showed, so a button that read "Play the animation again" answered to "Replay", and in another language every slot with words did: voice control could not reach it by what it showed. The label now names the button where the slot shows an icon alone, or where the label holds the words the slot shows.

  • An image gallery's zoom buttons hold no figure. A button takes text-level content only, and each thumbnail rendered a <figure> inside one, which an HTML validator reports on every image. The image takes as="span" for that place, rendering as a block with its caption as a line of its own, and the gallery uses it. The collapsible page's slot example no longer puts a <div> and a <p> in its button.

  • A text prop given false or an empty string reads as none. A condition at the call site such as :help="$isNew && __('Optional')" writes false when it fails, and a review drew an empty heading, the label of a form field a help button with nothing to say, a sticky panel a landmark with an empty name, a field set and a sidebar group 0 as their legend and name, and a drawer an empty question before it discards. A boolean given to a label, a help, a title, a description, a caption, a placeholder or a language now reads as none, as null does; 0 stays a text. An empty string, or one of spaces, as a record with an empty column writes it, reads as none in the same way, so a strength meter with an empty label keeps its name. An empty placeholder still means no placeholder, as it does in HTML.

  • A word a component writes for itself, given false or an empty string, gives way to its own. Given false, a badge's dismissLabel left its dismiss button without a name, a radial progress's valueText wrote an empty aria-valuetext, a pricing table named its interval switch with nothing, and the beforeLabel and afterLabel of an image compare, a notification center and the addLabel of a filter builder drew empty labels, links and buttons. An empty string reads as none in the same way: a tier of a pricing table with an empty priceLabel no longer loses its price, and a data table with an empty emptyText says its own sentence when nothing matches.

  • An alert dialog without a description no longer points aria-describedby at nothing. The reference went out whenever describedby was not set, and only alert-dialog.description carries the id it names, so a dialog without one, such as the example of the keyboard confirmation on its own page, pointed at an element that never renders unless describedby="false" was written. Unset, the dialog now points at its description when it holds one; false and true work as before.

  • A price in another language than the page marks its amounts with lang. With a locale whose language is not the page's, the amount, the struck price, the delta and the unit price carry it, so a screen reader reads 1.234,50 € by German rules rather than as another number by English ones. The word "from" and the name "Previous price" stay in the page's language. A product card, a cart line, a cart summary and a tier of a pricing table show their prices this way, and an empty locale reads as the application's.

  • An icon that cannot be drawn no longer takes the page down from a menu or a navigation entry. In a request, an unknown icon alias or one whose icon set is not installed leaves its icon out and keeps the page, and a console or a test still fails on it. A dropdown item and submenu, a sidebar item and collapsible group, an app rail item, a menubar submenu, a context menu item and submenu, a command palette item and its search field and a floating action button drew the icon without that guard and answered with an error page instead.

  • Components in running text no longer put a space in front of the punctuation after them. A badge, a highlight, a hover card on a name, a status dot, a flag, a swatch, the label of a form field, a button and the components drawn as one wrote a line break after their closing tag, which the browser draws as a space, so a sentence written as "By Ann, today" read "By Ann , today". So did text, a heading and a tooltip written as a span. Each now ends with its own closing tag, and a line break inside the element stays as it was.

  • A badge or a toggle button with a tooltip stays inside its paragraph. The tooltip around it rooted in a block element, and the browser's parser closes a paragraph in front of one: the badge stood on a line of its own, and the rest of the sentence fell outside the paragraph. Both now root their tooltip in a span, as a status dot does, and so do the layer of an optimistic toggle button and the wrapper of a scroll to top button that the page shows and hides.

  • A field set with text fields in it shows its message once. A text field, a select or a picker named for the set's key, as emails[] is in a set named emails, repeated the set's message under itself and kept it when the set's error was turned off, and the rejected entry of one field, such as emails.1, showed under that field and again as the message of the whole group. Such a control now leaves the group's message to the set, which the <fieldset> points at, and shows the entries below the key that are its own, which the set no longer takes for the group's.

  • A field set named the way a form names its controls shows its message once. Laravel files the error of address[country] under address.country, and a set named address[country] read nothing from the error bag: each radio or checkbox inside showed the message under itself, once per option, and nothing described the <fieldset>. A field.set now reads its name in the spelling of the bag, as every control does, so address[country], roles[] and address.country each name their group.

  • A named slot that a condition leaves empty draws nothing. Livewire writes two HTML comments around every @if, so a slot whose content sits inside one arrives holding those comments when the condition is false. Components drew such a slot as soon as it was written: a sticky panel, a kanban column and a command palette showed an empty bar with its border and padding, a footer a rule above an empty row, a callout a margin over nothing, and a select or a combobox with no options turned itself off and showed an empty message for an empty slot that held nothing. They now draw a slot only when it holds more than comments, as the label and icon slots already did, and a swap shows its default slot when its off slot holds nothing. The same holds where such a slot stands in for another part: a tooltip whose content slot holds nothing shows its text rather than an empty panel, so the control it sits on keeps its description, a product card keeps its image beside an empty media slot, and a sidebar with a header and an empty footer, or the reverse, draws no empty band.

  • A call to action, a hero, a timeline item, a tour step and a brand bar take their text slots as props as well. title="…" on one of them became the element's tooltip and the heading was missing, as on a callout before, and description, eyebrow and tagline written as attributes were lost. Each now takes the text as a prop of the same name, and the slot wins when both are given.

  • An aria-label on a translatable input names it. It stood on the element around the languages, which has no role, so a screen reader read no name: the set of languages went unnamed, and a single language's field had no name at all. With more languages it now names the set while each field keeps its language's name, and with one language it names the field. An aria-labelledby goes the same way.

  • The help of a translatable input names the value it explains. With more than one language its question mark reported no field in its event, so a listener could not tell which value's help was asked for. It reports the value's name now, or the property it is bound to, or its id.

  • An aria-label given to a skeleton, the editor's toolbar, a tab list or a button group names it. These wrote a name of their own, "Loading", "Editor commands" or the one their label gave, and printed the given one beside it on the same element, where the browser keeps the first and drops the second. A role, a tabindex or a state written on a component that writes its own landed on one element twice in the same way. Each element now carries an attribute once: a given name wins, and so does the role of a container; the role of a menu item or a tab, its tabindex, and a state the component keeps stay the component's.

  • The − and + buttons of a number input follow a lock that comes after the page loaded. They looked at the field's lock only when its value changed, so a field that started disabled and was freed later kept both buttons disabled until the reader typed a digit, and a field locked later, by itself or by a fieldset around it, kept buttons that looked in use and did nothing. They now follow the field's disabled and readonly and every fieldset around it.

  • A date picker with range hands form, readonly, autocomplete and autofocus to its dates. They landed on the row around the two fields, where they do nothing: a range in a form elsewhere on the page was not sent, and a readonly range stayed editable. Both date fields carry them now, as the single date's field always did, and autofocus goes to the start date.

  • wirekit:doctor:a11y checks an icon-only button whose attributes hold a >. A value such as :attributes="new ComponentAttributeBag(['row' => $id])" ended the button's tag for the scan at the arrow, so a button with only an icon and no aria-label went unreported. A button group is no longer read as a button either.

  • A swatch in the label of a checkbox or a radio stands on the middle of the box. A swatch beside a name made the first line of the label taller than its text, and the box, which stands on the middle of the first line of text, stood above the swatch's middle. In the label a swatch now takes the height of the line and reaches equally above and below it.

  • The color of a swatch stands alone where its picture does not load. Given with an image, the color lies behind the picture: it shows through the picture's transparent parts and while the picture loads, and where the picture does not load it stands alone, where the picture's <img> drew the browser's mark for a broken picture over it. The picture is the background of the swatch's fill now. The swatch page said a picture stood in place of a color, and it and the swatch group page say where the color goes.

  • A y-axis label formatter a page sets for every chart through window.Apex stays in charge under ApexCharts 8. ApexCharts 8 lays a chart's own axis over the global one, so the label formatter the kit gives each axis hid the page's, on the axis and in the values of the tooltip. The kit sets none where the page set one, as it already did for the legend and the title of the tooltip.

  • wirekit:class-by-area traces a container variant that follows another variant. A class such as xl:@max-5xl/wk-table:hidden was listed as compiled CSS that no source emits, because only a class that opened with the @ was read as a container variant.

  • wirekit:class-by-area reads the classes a component hands through the attributes of a slot. A class list given to a slot's own attributes was not read, so its classes were listed as compiled CSS that no source emits.

  • A deploy no longer logs that text falls back to system fonts. The warning about a font that was never published came from every boot of the application, so a deploy wrote it from package:discover, which runs before wirekit:publish-fonts in the same Composer run and renders no page. It also said the text fell back to system fonts, which the package route prevents: fonts serves such a family through PHP. The line is written now when a page renders <x-wirekit::fonts />, once per family and hour, in the words of the comment it writes into the page: the family is served through PHP, and php artisan wirekit:publish-fonts makes it a static file.

  • The stylesheet of a font served through the package route keeps its URL from page to page. A family that was never published loads through PHP, and the URL fonts wrote for it carried the time of the request instead of the time the bundled file last changed. It changed every second, so the browser fetched the stylesheet again on every page, though the route lets it stay in the cache for a year. The URL now changes when an update brings a new file.

  • One Tailwind source per component builds a narrower stylesheet in a Laravel application. Tailwind reads the whole folder around a @source that lies in a directory git ignores, and vendor/ is one, so a single declared source read every source beside it and the stylesheet carried the utilities of the whole catalog. Each source is now a folder of its own under the same path, …/resources/tailwind/card.txt, so the lines already declared narrow the build as they are. wirekit:doctor now also reads the views that packages register and names a component that only a package renders: run it after the upgrade, because such a component needs a line of its own once the declaration narrows.

  • A spine-aware block keeps its padding with one Tailwind source per component. It takes its inline padding from WireKit::spinePadding(), which builds the class in PHP, and the source of spine-aware carried none of the four tier classes, so an application that declared it built no padding for it and its content met the edge of the page. The source carries all four, the full template scan generates them whatever other components spell, and php artisan wirekit:doctor names spine-aware.txt for a view that calls the helper while no declared source carries its classes.

  • A navigation menu in a navbar names its landmark apart from the bar's. Both named their landmark Main navigation when the page gave them no name, so a screen reader listed two navigation landmarks of one name in a row, and an accessibility checker reported them as not unique. A menu named by default inside a navigation landmark of the same name now takes the name Main menu, translated like the other, and keeps it when Livewire renders the page again. A name the page gives either one stays as it is given.

  • A tooltip ends where the control in its slot ends. Its trigger is a block, so a button in it stood on a line of text, and the line reached a few pixels below the button: the collapse toggle of a sidebar stood that much above the edge it was lined up with, and an icon button in a row stood above the row's middle. A control now stands at the top of the trigger's line.

  • The title of a modal stands on the middle of its close button. The header lines its children up at the top, so a one-line title stood about 4px above the middle of the button and read as sitting high. Its first line now moves down by half the difference to the button, and a title of several lines keeps the button in the top corner, on the middle of its first line.

  • The one-character hotkey of a command palette waits while single-key shortcuts are off. A palette on / opened through the reader's switch in the overview of keyboard shortcuts and through data-wk-single-keys="off" on a screen, which every other single key obeys (WCAG 2.1.4). A hotkey with cmd, ctrl or alt is not a single key and keeps working.

  • Charts group the thousands of their values. An ApexCharts chart wrote 278984,46 in its tooltip, in the cells of a treemap and on its y-axis, beside a page that wrote 278.984,46. The values are grouped the locale's way now, also where the chart sets no decimals of its own, value-grouping="false" writes them ungrouped, and a chart takes the marks of the application's own number format where it gave one.

  • A cut name in a compact cart line shows its whole wording in a tooltip. The name showed it only in the browser's title, which a touch screen and the keyboard never show, and a cut variant showed it nowhere. Both now show it in the kit's tooltip while they are cut, on hover, on keyboard focus and on a tap.

  • On a touch screen the controls of a cart line's press stepper take the touch size as their own. With stepper="buttons", − and + drew a 44px hit area around their square. At size="sm" it reached into the quantity between them, and in the compact layout at size="sm" or "md" the hit area of the remove control reached 2px into the + beside it, so a tap on the edge of + went to the remove control. −, the quantity, + and the remove control beside them now stand at least 44px tall and wide on a touch screen, and none of them reaches its neighbor. At the default size="lg" the stepper keeps its size, and the remove control of a compact line grows from 40 to 44px on a touch screen.

  • wk-fab-clearance keeps the end of a long page clear of a button back to the top over the floating action button. The room it reserves counted the floating action button alone, so at the end of a long page on a phone the button back to the top with above-fab covered the button at the end of the last row. While such a button is on the page, the room grows by its size and the gap it keeps, and it counts --wk-fab-lift as well.

  • A field bound with wire:model.blur or wire:model.change counts as unsaved. The unsaved hint left such a field out, as if it sent its value by itself, so the page could be left with the change in it and no question. Livewire 4 sends a binding by itself only with .live or .lazy. Without .live, .blur and .change decide when the page writes the value, and the value waits for the next request.

  • The words beside the label of an unsaved field never move the field or run past the label. In a narrow column they wrapped onto a line of their own with the label's last word and pushed the field below the fields beside it, where that word stood alone on its line they ran past the label's edge, and beside a switch they pushed the buttons next to it aside. Each label is now measured where it stands: the words while the field stays where it is and the label holds them, else a dot in the color of the unsaved line with the words as the label's title, else nothing. A label that does not stand above its field, around a switch or a single checkbox, shows no words. See unsaved hint.

  • An unsaved field says so to a screen reader on the field itself. The words beside its label stayed out of its name, and the unsaved hint only said that something was unsaved. While a field is unsaved, its description now names the same words, after the description it already had, and leaves it once the field counts as saved. A radio button, a checkbox of a list and an invalid field are not described, as they take no words.

  • An unsaved field in a drawer or a modal says so. A scope with x-wk-unsaved inside a dialog tracked none of its fields, since the dialog is placed at the end of the page, outside the element of the Livewire component it belongs to: a changed field was never framed, the unsaved hint stayed hidden, and .confirm did not ask before the page was left. A scope around a dialog did not count the fields in it either. Both count the dialog's fields now, as they stand where the dialog is written, and so do data-wk-unsaved-off, a wire:submit of a form in a dialog and a wirekit:saved with trigger.

  • The button that takes a chip out is a 24px target. The remove button of a pill in a multi-select, of a tag in a tags input and of a chip in a filter builder measured 18 x 18 px, too small for a finger beside the next chip (WCAG 2.2 SC 2.5.8). Each is now a 24px square with its glyph in the middle, and the chip keeps its height.

  • The browser's own clear button of a search field shows the pointer. On an input of type search without clearable, the cross the browser draws is a control, and the pointer now says so over it.

  • The close button of a dismissible badge is a 24px target. It was as large as its 12px glyph, half the 24 × 24 px of WCAG 2.5.8, and a row of removable filter chips is where a finger misses it. It now takes the 24px around the glyph and gives them back through its margins, so a badge of every size keeps its height and its width, and the glyph stands where it stood.

  • A translatable input reads as one field and stands on a field's height. Its legend was bolder than the labels beside it and 4px above the first field, where a field puts its label 6px above, so the primary language's field stood about 2px higher than an input in the same row. The legend is now drawn as a field's label, at a label's distance, and a message about the whole value stands under the fields, as under any field. field.set takes the same as as-field, for any set that stands for one field.

  • A translatable input without locales has a field to type in. An empty list of languages, the default, drew the label and no field, so the form sent nothing for the value and nothing said why. Without a language the input edits the value in the application's language, as one ordinary field, and a list whose every code is refused is reported.

  • A translatable input with a single language is an ordinary field. It drew a group with the language's code in a box before the field, a lone "DE" in every form of a shop with one language. With one language in locales it now draws its field as an input or a textarea does, with the label in sight and the same binding, messages and help, and shows the codes from the second language on.

  • A key listener and the attributes of a text field reach every field of a translatable input, and Enter on a chip only opens the chip's language. x-on:keydown.enter and the other key listeners of the page landed on the component's root, where they also heard Enter on a chip and ran the field's action, and autocomplete="off" or maxlength did nothing there. A form that joins the field to a form elsewhere on the page stayed there too, so that form received none of the languages. They are now handed to each language's field, as an input takes them; a listener on the window or the document stays on the component and runs once.

  • A translatable input marks an unsaved language at its code, and takes a sentence for one language. Inside x-wk-unsaved, a language's field took the unsaved line, but its label is visually hidden, so the words beside a label had no place. The code, which stands where the label would, now takes a dot in the unsaved color, and the field's description names the words. hints gives one language a sentence of its own under its field, :hints="['en' => 'Translated by machine']", and the field is described by it.

  • The counts of a pagination summary are grouped the locale's way, "of 10.514 results" in German rather than "of 10514".

  • A link to an element inside a closed collapsible card brings the element into view once the card stands open. It scrolled on the next tick, while the part that folds was still sliding open and the page around it as short as with the card closed, so an element far down a long card stayed out of view.

  • A click on the chevron of an open collapsible card closes it. The turned arrow was painted above the head's click area and took the click itself, so only the space beside and below it closed the card. A collapsible card that holds nothing but a footer draws one line under its head instead of two, and inside data-wk-touch the button in its head is 44px tall, as the triggers of accordions and collapsibles are.

  • On a phone, checkboxes and radios stand as far apart as on a desk. Since v2.66.0 their label grew to 44px under the coarse pointer of a touch screen, so the options of a field set, the legend above them and a checkbox between a sentence and a button stood visibly further apart on a phone than under a mouse. The label keeps its height now and answers a finger over an area above and below its text that moves nothing: at least 24px tall, and between two options of a field set reaching halfway to the next one, so a tap between them lands on the nearer one. Inside data-wk-touch the label is a 44px row, as before.

  • The box of a checkbox and the circle of a radio stand on the middle of the first line of their label, at every size. Both were aligned to the top of the label, so a small box stood above the middle of its text, and a swatch and a name in the slot stood below the box. A label that wraps keeps its first line beside the box. A small checkbox or radio is now as tall as its line, 2px lower than before; the medium and large sizes keep their height.

  • The theme controller with variant="select" draws the chevron of the select. Its arrow was the browser's, and a browser places that by its own rule: close to the box's edge in Chrome whatever the padding says, and a different glyph in each engine. The box now draws the select component's chevron, centered and as far from the edge as on every other select, and keeps the value clear of it.

  • An empty error on a form field reads the $errors bag as if no error were given. With an entry in the bag, a field given error="" was marked invalid and showed no message, and its description pointed at an error paragraph that never rendered. It now shows the bag's message with the invalid state, or neither.

  • A form field bound with wire:model and no name shows its validation error. Every field read the $errors bag under its name alone, so after a failed $this->validate() a field bound only with wire:model showed no message and no invalid state. Livewire files the message under the bound property, and a field without a name now reads it there: wire:model="form.email" under form.email, with any modifier. A name still wins, and a checkbox bound to the key of its field.set leaves the message to the group.

  • A field around a form control that shows the same error no longer repeats it. With name on both, the shape of the field page's own examples, a failed validation rendered the message under the control and again under the field, and both paragraphs carried the same id. The field now leaves its message out when the control inside already shows it, so the message appears once, attached to the control, and the field's hint gives way to it as it does to the field's own message. Around markup of the application's own, or a control that shows no message, the field shows it as before.

  • The hint of a field describes the form control inside it. The field rendered its hint below the control, and nothing pointed aria-describedby at it, so a screen reader never read it with the control. With a hint on the control as well, both paragraphs carried the same id. Every form control in a field now links the field's hint after its own description. While the field has an error, the error takes the hint's place and no hint is linked. When the control's own hint carries {name}-hint already, the field's takes {name}-field-hint, and a field without a name names its hint after the control. Markup of the application's own still references {name}-hint by hand.

  • The label of a field names every form control inside it. A segmented control or a swatch group in a field had no accessible name at all, and an interactive rating or a one-time code kept the name it gives itself, "Rating" or "One-time code": the label pointed at nothing, or at a hidden input. They now take the field's label as they take a label of their own. The groups are named by it, and the label of a one-time code points at its first box. With two fields of the same name on a page, the label of the second field named the control of the first for a number input, a password input, a phone, a time picker, a one-time code and a rating. Each label now names its own control.

  • A closed accordion item is no landmark. A closed item the browser's find in page can open kept role="region" and the name of its trigger. Its panel keeps its box while closed, so axe counted it in Chromium among the page's landmarks: a region with nothing to read, and the twin of any other region of the same name, which axe reports as landmark-unique. The panel now takes the role and its name when it opens and gives both up when it closes, through a Livewire update as well. The answers of a FAQ follow, since they are accordion items.

  • A required one-time code, range slider or tags input says so where the reader lands. Each wrote aria-required on the group around its parts, where ARIA does not allow it and axe reports a critical aria-allowed-attr, and the elements that take the focus did not say it at all. The attribute now sits on every box of the code, on both thumbs of the range and on the input a tag is typed into.

  • The required of a field reaches the form control inside it. The field marks its label with an asterisk that is hidden from assistive technology, and with required on the field alone no control said the field was required, so a screen reader never announced it. The field page's own example had that shape. A form control in a required field now carries aria-required="true". It never takes the native attribute from the field, so the browser checks an empty field only when the control is given required itself, as before.

  • The help of a field describes the form control inside it, as a control's own help does. The field showed its help in a tooltip beside the label, and its hidden copy, which a screen reader reads as the control's description, was only offered under {name}-help. A form control in a field with a label now links it after the field's hint. Beside a help of the control's own that carries the same id, the field's takes {name}-field-help, and a field without a name names it after the control.

  • readonly makes a one-time code, a tags input, a multi-select and an inline edit read-only. The attribute landed on a wrapper element, where it is no valid attribute and stopped nothing, so each of them stayed editable. A read-only code keeps every box in the tab order and refuses keys and pastes. A read-only tags input or multi-select keeps its values on the page and in the form, shows no remove buttons, and adds or removes nothing; the multi-select's list still opens to show the options. A read-only inline edit shows its value without a pencil and opens no editor. An autocomplete written on an inline edit now reaches its field as well.

  • A disabled group locks a combobox and an open color picker panel. Both draw their list or panel at the end of the document, outside the fieldset, so a fieldset disabled while one was open left it working: a click on an option of the open list still chose it, and the plane, sliders and swatches of the open panel still changed the color. A combobox without search, whose field is no native control, also stayed in the tab order and opened on a click or a key. While the fieldset is disabled, an option of an open list now picks nothing, a combobox without search leaves the tab order and says it is disabled, and an open color picker panel closes.

  • The required star of a translatable input stands where a field's label draws it. Its legend held the star in the row after its text, half a gap further from the words than the star of a field beside it. It now stands right after the text, and the legend says "(required)" to a screen reader, through the new required of the field set.

  • A keyboard shortcut on a button inside a disabled fieldset leaves the key alone. The shortcut registry read the button's own disabled, which stays false inside a disabled fieldset, so ⌘↵ in a dialog whose form a request had locked took the key and pressed a button that could not act, and the overview on ? listed the shortcut. Such a button now counts as disabled, as the browser counts it: its shortcut neither fires nor is listed until the fieldset is enabled again.

  • A click on a disabled option of an optimistic combobox chooses nothing. With optimistic, a click on an option went straight to the optimistic layer, so a disabled option was chosen and its value sent to the server, while the arrow keys and Enter passed it by. A click now chooses what Enter would: nothing on a disabled option, and the option's value otherwise, a falsy one included.

  • An optimistic checkbox or switch that cannot reach its Livewire component shows the value it holds. Outside a Livewire component, or with a bind that names no property, the layer refuses the press and warns in debug that the control will not change, but the box stayed as the browser had flipped it. It goes back to its value now. A text field keeps what was typed into it.

  • The reference of every component with optimistic explains the prop and names the file it needs. In every component that takes optimistic, the comment that explains it stood above optimisticArgs, so the reference the MCP server answers get_component with gave its text to optimisticArgs and none to optimistic. Each prop carries its own text now, and the one of optimistic says that the page loads wirekit-optimistic.js beside its bundle, without which no control calls its method.

  • Names and labels built from a slot or a markup label read as written, Tom's & Jerry included. A tooltip with a content slot named its trigger, a table that stacks its rows named its columns, a command palette item titled its line and an app rail module with a counter wrote its tooltip with Blade's escaping still in the text, so a screen reader, or the reader of a stacked table on a phone, met Tom&#039;s &amp; Jerry. The same reached the buttons that an input, a combobox, a password input and a kanban column name after a label given as an HtmlString, and the parts a field with such a label names. The first count of a textarea counter counted the references, 28 for 16 characters, until the page's scripts started, and a floating action button whose label holds its words lost that label as its name when the words held an apostrophe or an ampersand.

  • A form control given a list where it takes one value renders without it. old('email') returns a list when a request sent email[], and a rule such as string sends the reader back to the form with it. An input, a password input, a number input, a one-time code input, a phone field, a combobox, a segmented control, a date picker, a time picker, a color picker, an inline edit, a radio, a checkbox and a toggle threw a PHP error on it, and so did a multi-select, a tags input and a calendar given a list inside their list, so the page that should show the validation error answered with a server error. Each now reports the list through strict validation and, where strict validation only logs, renders without it.

  • The numbers a slider, a range slider, a progress bar, a character counter and a badge write read in the page's language. A German page read "Bereich: 0.2 bis 0.7" under a range slider, "0.3" in a slider's bubble and "0.5" under its mark, "12.5 / 100" beside a progress bar and "3 / 12500" under a textarea with a counter, beside figures the same page wrote as "12.500". A slider's values and marks now take the page's decimal mark, 0,3, and are never grouped, so a scale of years still reads 2026. A count is grouped as the pagination groups one, 12.500 in German and 12,500 in English: the readout of a progress bar, the character counter, and the badge of a sidebar item, an app rail module, a dropdown item, a tab, a bottom navigation item, an indicator, an activity row and a product card, the counts of a kanban column, a stage card and a reaction, an optimistic one included, and the count of new messages on the jump-to-latest button of a conversation. A badge that is no plain whole number, such as 99+, New or 007, stays as written, and an application that writes its numbers its own way gets its own marks in all of them.

  • A form with a phone field sends the number a reader typed when the page runs no script. Without JavaScript the form sent the number the page was loaded with, so a new or edited number never arrived, and nothing reported it. Until its script starts, the field now sends the number as typed and the country it stands on under its name, as name[number] and name[country]; with the script it sends E.164 alone, as before, and a wire:model still binds that value only. DialingCodes::fromField() reads either shape and returns E.164, so one line in front of the validation covers both, see Without JavaScript.

  • A form sent from a page that runs no script keeps the selection of a multi-select and the tags of a tags input. Their fields were written by the script, so such a form carried neither, and a server saving it stored both empty. The values the page was loaded with are now sent as well when no script runs; with one, the fields are the same as before.

  • A one-time code typed into an OTP input arrives when the page runs no script. Only the script joins the boxes into the code, so such a form sent an empty field and a verification step without JavaScript could never pass. Until the script starts, the boxes are now sent as name[], one entry per box, and implode('', (array) $request->input('code')) reads the code from either shape; with the script the form sends the code alone, as before, see Form Submission.

  • A form with a required combobox, multi-select, tags input, OTP input, rating, calendar or editor can be sent from a page that runs no script. These controls check required through a hidden stand-in that only the script fills, so without one the browser stopped every submit of the form, even when the field had a value, at a field nobody can see. The stand-in now takes part in the check only once the script runs; the server checks required either way, and with the script an empty field stops the submit as before.

  • A combobox with search shows its choice before its script starts. Its search field stayed empty until the script ran, so a page loaded on a choice showed the placeholder over it: a filter opened from a link read "All people" above a table filtered to one person. With free-input the field carries the name, so a form sent before the start, or from a page that runs no script, sent nothing for it. The field holds the chosen option's text, or with free-input the value, from the first render.

  • A label of a bottom nav and the name of a message's author show whole when they are cut. A long word in a tab of a phone's bar, or a long name beside the author's role and the time, ended in an ellipsis with nothing to read it whole by. The tab's label shows whole in a tooltip while it is cut, on hover, on the link's keyboard focus and on a long press, and the author's name takes a title, as the head of a message group does.

  • A slider, a password input and a filter builder follow a form reset. The browser resets a native field without telling the script, so a slider's bubble and its announced value kept the value the reader had left, and a password input's strength meter kept rating the password that was gone. A filter builder kept the filters the reader had set while the rest of the form went back. All three now return to what they started with, as the other form components already did.

  • The groups of a combobox list stand on an element that may carry them. Each group took the group role on a list item, which ARIA in HTML does not allow, so an accessibility check reported aria-allowed-role for every combobox with grouped options and for every combobox with server, whose list is grouped too. The role and the group's name now sit on the list inside the item, and the options still belong to their group.

  • A person drawn without a picture gets the same initials in every component. The attendees of an event calendar took the first character of the first two words, so "Ada Byron Lovelace" read "AB" there and "AL" in a message, and a name with an emoji or a character such as 𠮷 showed half of that character, drawn as a replacement character. A message, a team member and a testimonial took a word's first character as well, so "(Ada) Lovelace" read "(L". Every component now takes the first letter or digit of the first and the last word, as the options of a combobox already did, and the calendar reads the same rule in the browser. For those names the color an avatar derives from the initials changes with them.

  • An avatar's initials keep the accent of a name written decomposed. A name whose accent is a mark of its own after the letter, as a file name from macOS writes it, lost the mark: "Émile Zola" showed "EZ". The marks that combine with a letter stay with it, on the server and in the browser alike, and a vowel sign stays with the consonant it belongs to.

  • A caller's x-on:click on a component that listens to clicks itself runs beside the component's own. On a context menu item, a menubar item, a dropdown trigger, the close control of a modal, the trigger and the close control of a drawer, the confirm and cancel controls of an alert dialog, the trigger of a command palette or of the keyboard shortcuts overview and the toggle of a sidebar, a listener written with x-on:click stood beside the component's own of the same name, and the browser kept only one of them: a menu item written that way did nothing, or the menu stayed open. The same held for x-on:keydown on a dropdown panel, x-on:mouseenter and x-on:mouseleave on a navigation menu item, the pointer, key and focus listeners of a resizable handle, and x-on:click.prevent on an item of an interactive bottom navigation. Both run now, the component's first.

  • A usage meter and a radial progress write a percentage as the page's language does. A German page read (13%) beside a usage meter's figures and heard 13% from a radial progress, where WireKit writes 13 % elsewhere, as for the change of a price. The deprecated circular progress writes the value in its middle the same way.

  • A count WireKit writes takes its plural form under a regional locale spelled with a hyphen. Under de-AT, pt-BR, fr-CA or en-GB every count read in its first form, "3 Stern" for a rating's third star or "von 57 Treffer" in a pagination summary, and a count that changes in the browser kept that one form for every number. The form follows the locale's language now, as it did under de_AT. Localization says what the hyphen means for an application's own plural sentences.

  • A page in a language WireKit ships no catalog for writes its English counts in their English form. Under Polish, Russian, Czech or Arabic the English sentences WireKit falls back to read "5 item" or "1 items", and a count that changes in the browser read "21 item" under Russian. An English sentence takes its form by English now, and a sentence an application translates keeps its own language's rule. Localization describes the fallback.

  • A Portuguese count that reaches zero in the browser reads as the server writes it. A countdown, a scope switcher's result count or a textarea's character counter read "0 segundo" or "0 resultado" once the number changed in the browser, where the page itself wrote the plural.

  • A reading minimap's labels keep an emoji or a flag whole. A heading cut to fit its marker or its tooltip could end in half a flag, drawn as a replacement sign, or in the first part of a joined emoji, which showed another emoji. The cut falls between characters as a reader sees them now, and the same holds for a command palette's Backspace in the moment before its field has the focus.

  • A number the browser writes into a sentence follows the page's language. A number input that corrected a value on leaving the field said "Auf 2.5 gesetzt" on a German page, and the selection of a data table read "12500 Zeilen ausgewählt" beside a pagination that wrote "10.514". The value takes the page's decimal mark, ungrouped as the field shows it, and a count on the page is grouped as WireKit groups one elsewhere, as is the count of changes of a status matrix. A sentence only a screen reader hears keeps its digits, which every voice reads alike.

  • A search keeps a mark that makes another letter. The search of a combobox, a multi-select, a data table and a scope switcher folds accents away so munchen finds München, and it folded a Japanese voicing mark or a Devanagari vowel sign away the same way, so a search for a word found another one. In those scripts the mark stays now; Latin, Greek, Cyrillic, Arabic and Hebrew accents fold as before.

  • A review renders a date that names its offset. An ISO 8601 date with an offset or with Z, the form toISOString() and most APIs write, a date object with an offset and a Unix timestamp stopped the page with an exception, because the date was formatted in a zone PHP names by its offset. Such a date is written on the day of its own zone now.

  • A message renders a timestamp that is a plain PHP date. A DateTime or a DateTimeImmutable stopped the page with an error, where a Carbon date rendered. A date object of any class renders now, and a Carbon date keeps the element ids it had.

  • A Unix timestamp is written in the application's time zone. Handed to a message, a countdown, a review or an event calendar without a timezone, a timestamp was written in UTC, while the same instant as a string or from a model was written in the application's zone: in Berlin a message read two hours early, and after midnight a calendar opened on the previous day. A timestamp takes the zone a string without one is read in now. An application in UTC sees no change.

  • A number that rounds to zero is written without a minus. The zero line of a chart axis that steps in tenths or fifths read -0, or -0,0 in the decimals of its series, and a figure written on the server that rounds to zero from below read -0, -0 % or -0,00 €, as the change of a price just below zero did, colored as a price drop. Such a value reads 0 now, and one that rounds away from zero keeps its minus.

  • A color picker's panel stays open while an input method's conversion is abandoned in its value field. Writing Japanese, Chinese or Korean, a reader abandons a conversion with Escape, and the panel heard that Escape as its own and closed. The value field, the search of a filter builder and the value fields of its popover keep such a key to the input method now, as WireKit's other text fields do.

  • An id above Number.MAX_SAFE_INTEGER comes back from the browser exactly. The selection of a data table, a click on an event calendar's event or a map's marker and the events of a notification center handed a 64-bit id, such as a snowflake id, back as another number, because a script reads a number in JSON as a double. Such an id travels as its digits now, and every smaller id stays a number.

  • A backed enum is a value, as a model attribute cast to one hands it over. A select, a radio, a checkbox, an input, an inline edit, a multi-select and the default of tabs stopped the page with an error when an enum such as $post->status was the value. Each reads a backed enum as its value now, the way Blade writes one, and a pure enum as its name, and writes exactly what it writes for the same value as text.

  • A date or time field shows the date a model hands over. An input with type="date" or type="time" wrote a date object, such as an attribute with a date cast or old('due', $task->due_on), as 2026-03-15 00:00:00, which the browser's field drops: the field stood empty, and a form saved without touching it sent no date. A date and time written as text, such as 2026-03-15 14:30:00 from a column without a cast or the ISO 8601 that Laravel writes into JSON, emptied every date and time field the same way and left a calendar without a single day. Each field now receives such a value in its own form, with text read in the application's time zone, and a calendar that cannot read its start value opens on the current month. A time picker wrote the seconds of a date object, 09:30:45, which a field that counts minutes refuses to send; a time keeps its seconds only where step counts them now.

  • An event calendar puts a day from a model on that day. A day marker whose date came from a date cast stood on the day before in every time zone east of UTC, so a blocked holiday blocked the wrong day, and an all-day event from one stood on the day before for a reader west of the application. A plain PHP DateTime marker or event stood on no day at all. A marker's date and the ends of an all-day event are written as the day they name now, and the ends of a timed event as the instant they hold.

  • A chart labels a date on its date axis as the time it names. A day from a date cast stood on the day before on ApexCharts' axis, which is labeled in UTC, in every application east of UTC, and a plain PHP DateTime was no date at all. A date and time written without a time zone showed each reader another hour on that axis, and a date alone stood on the day before for readers west of UTC on an axis labeled in their own zone. The chart now writes each in the form the axis reads as the time it names, and the links of its points name the same day.

  • A date in a row of a data table shows as the date it is. A date object in a row reached the table's cells as 1990-12-24T23:00:00.000000Z, the day before for a date of an application east of UTC, and a plain PHP DateTime as [object Object]. It shows as PHP prints it now, in its own time zone, and sorts as the dates do.

  • A saved filter on a date field shows its day. A rule whose value was a date object read Created after 2026-12-24T23:00:00.000000Z on its chip, the day before for a date of an application east of UTC, or [object Object] for a plain PHP DateTime, and the date field stayed empty when the rule was edited. The value of a rule on a date field is written as the day it names now.

  • A number that is not finite no longer ends the page. A chart, a sparkline or a data table whose data held INF or NAN, as a rate divided with fdiv() by a base of zero gives, stopped the page with Inf and NaN cannot be JSON encoded. Such a number is written as null now, as JavaScript's own JSON.stringify() writes it: a chart draws a gap, a cell shows nothing. An editor given its document as an array writes one as null as well, as the browser writes it when the document is saved, where the render failed before.

  • A tool call shows its arguments when one of them holds a byte that is not UTF-8 or a number that is not finite. Arguments given as an array with a value from a Latin-1 source, INF or NAN showed no arguments block at all, without a word. The byte shows as U+FFFD now and the number as null, beside the other arguments, and arguments JSON cannot hold at all are named in the log in debug mode.

  • A numeric prop that holds NAN or INF no longer ends the page under PHP 8.5. PHP 8.5 warns when such a number is cast to an integer or written as text, and Laravel turns the warning into an exception. Heading levels, counts, sizes, delays and the other numeric props of the components read their value as a number first now, so NAN and INF take the default a text takes. That holds for the numeric props that default to nothing as well, such as the seconds of a tool call, the index of a wizard step or a date given as a timestamp, and for the offset of a popover. A stat and the count in a badge show such a value as PHP has always written it.

  • A prop given a fluent string from str() shows the text it holds. :label="str($field)->headline()" is an ordinary way to write a prop, and Blade's {{ }} writes such a value as its text, but an input's success, a tool call's name, a kbd's shortcut and other text props left it out, an editor wrote such a document into its field as a JSON string, which the next save kept, and an activity row's kind ended the page. Every component reads a fluent string as its text now. An HtmlString stays markup: a success message given as one is written as markup, as hint is, a map's attribution reaches Leaflet as the HTML it holds, and where a prop is text for a script or for a name, such as a countdown's expired-text, it is read as its text rather than written as [object Object].

  • A grid or a feature grid with a cols value it does not know falls back to one column in production. Outside debug mode a value such as 13 or a typo ended the page with a TypeError, where an unknown value is meant to be logged and replaced by the fallback, and the fallback then came out as a bare class 1 rather than grid-cols-1.

  • A Collection handed to a control that holds one value is reported, as an array is. A combobox, a select or a segmented control given $post->tags->pluck('id') wrote its JSON text, [5,8], as the value without a word, where an array was reported while developing and left out. Both arrive the same way now; a multi-select still reads a Collection as its list.

  • A display in a component's class takes the place of the component's own. class="hidden md:inline-flex" left a button on a phone's screen, class="hidden" left a badge there, and class="block" kept a stack a flex column: Tailwind writes the display utilities in the order of their names, so a component's own inline-flex came after hidden and won, as its own flex did after block. A display written without a breakpoint in front now replaces the component's own, so the element hides and comes back at the breakpoint the class names. A display under a breakpoint alone, as in class="md:hidden", leaves the component's own in place. See Customization.

  • A position in a component's class takes the place of the component's own. class="absolute top-2 right-2" left a tooltip around a copy button in the flow, and a dropdown or an avatar placed that way stayed where it stood, while absolute left a button back to the top fixed to the window: Tailwind writes the position utilities in the order of their names, so absolute and fixed came before a component's own relative and lost, and absolute before its own fixed. A position written without a breakpoint in front now replaces the component's own. See Customization.

  • A display or a position in a component's class reaches a component that writes its layout inline. A header, a data list and its items, a mixed chart, an inline sparkline, a page progress bar and a reading progress bar write their display or position into style, so their layout holds on a page whose stylesheet never scanned them, and a declaration there wins over every class: class="hidden md:flex" left a header on a phone, class="md:hidden" left a data list on a desktop, and class="absolute" left a progress bar fixed to the window. Where the class reaches for the display or the position, with a breakpoint in front or without, the inline declaration now gives way to it. See Customization.

  • A flat card, a transparent navbar and the create action of a scope switcher keep their own color over a personalized block in every spelling Tailwind accepts. Each takes the competing color out of the block it builds on, because two utilities for one property are decided by their order in the stylesheet. A color with an opacity (bg-white/80, text-black/60), the custom-property form (bg-(--brand)) and an arbitrary text color (text-[#333], text-[var(--ink)]) were not recognized and stayed, and bg-white/80 won over the flat card's own background. A background image, size or position written as an arbitrary value (bg-[image:…], bg-[length:…]) was taken out as if it were a color, and stays now. A table header given a font size or weight in class recognizes text-[1.25rem]/7, text-[clamp(…)] and font-(--weight) too, and pbs-* and pbe-* count as padding where a component takes a padding out.

  • A name given to a control reaches the control. aria-label or aria-labelledby on the confirm or cancel control of an alert dialog, a theme controller, the collapse toggle of a sidebar, a menubar menu, a navigation menu item with a flyout or a command palette trigger stood on the element around the control, which has no role and so takes no name: a screen reader read the control's own name, or none. The name goes to the button or the search field now, ahead of the control's own. A control a caller places in the slot of a confirm or cancel control is the caller's to name.

  • A toast region named with aria-label or aria-labelledby is a landmark. The name stood on an element without a role, where it names nothing; it now makes the region the named landmark that label makes it.

  • An aria-labelledby or aria-describedby on a drawer or an alert dialog names and describes the dialog. Both took only aria-label to the element with the dialog role; a reference or a description written on their tag stayed on the element around it, which has no role, and named and described nothing. They go to the dialog now, a reference ahead of the header or the title, and a description beside the dialog's own in one attribute. A modal wrote its describedby and a description on its tag as two attributes of the same name, of which the browser kept the first; it writes one. The controls that hand a name given on their tag to their button or field, such as the confirm and cancel controls of an alert dialog, a theme controller and a command palette trigger, do the same with aria-describedby.

  • Five tab stops draw the kit's focus ring. The control a modal or drawer close renders when its slot holds no control of its own, a replay button, the create action of a scope switcher and the search field of a command palette drew the browser's own ring or none, which follows neither --ring-wk-width nor --color-wk-ring. They draw the kit's ring now, and a map draws it as an outline inside its frame, where its tiles cannot cover it.

  • What starts hidden stays hidden on the first paint. A head of a table sorted in the browser showed its arrows up, down and unsorted side by side until the script ran, and for good without it, and a tree view showed the children of a collapsed node until the script folded them and moved the page. Both start hidden now.

  • A wire:key given to a submenu, an app rail item, a sidebar row or an action bar stands on the component's outermost element. Livewire tells the entries of a loop apart by the key on the outermost element of each, which is why a wire:key on a WireKit component keys that element. On a dropdown, menubar or context menu submenu, on an app rail item with a tooltip and on a sidebar row in a sidebar that collapses, the key landed on an element inside it, so Livewire matched those entries by their position, and an entry that left the list or moved could leave its state, such as an open submenu, to the entry that took its place. x-show and wire:show hid that inner element and left the outer one in the layout, where an x-show or wire:show on a WireKit component shows and hides the whole component. All of them go on the outermost element now. An action bar keeps its announcement for screen readers beside the bar, so a key, x-show or wire:show given to it goes on an element around both.

  • A modal with a long body scrolls the body alone. A visually hidden text below the body's fold, such as the "Loading" of a spinner or the hidden input of a file upload, was placed against the panel, because the body had no position of its own: the body neither clipped nor scrolled it, and the panel scrolled by its depth beside the body's own scrollbar. Every region of the kit that scrolls is now the positioned box for what it holds, so the same goes for the body of a drawer, the list of a notification center, a conversation, a scroll area and the other scrolling regions, where the hidden text could make the page or a box around them scroll. An absolutely positioned element that a page places inside one of them, with no positioned element around it within the region, is now placed against the region and scrolls with its content; see Positioned Content.

  • An event that names a component by a number reaches it. A modal, drawer or alert dialog, a collapsible card, an inline edit, a stream, a card stack, a lightbox and a command palette compared the name or key of an event with the one they held by type as well as value, so $this->dispatch('wirekit-modal-show', name: $order->id) sent a number that never met the name "5" from the attribute, and nothing opened, folded out or showed. An inline edit holding the number missed a saved event that named it as text and kept its saving state. A number and the text of its digits now name the same component in each of them, and the same holds for the id that addresses one app rail, one sidebar or one file upload among several.

  • A tab keyed by a number is selected. The tablist held the open tab as text and compared it with each key as PHP held it, so with keys from pluck('name', 'id'), years, or an enum of numbers as default or active, no tab was selected once the script ran, none was in the tab order and every panel stayed hidden until a click. Keys are compared as text now, and wirekit:tab-changed reports the key as text after a click as well.

  • A segmented control keyed by numbers marks its chosen segment before the script runs. With keys such as 1, 7 and 30 and the value written as an attribute, no segment was checked or the group's tab stop until the script ran, and an autofocus focused nothing. The markup compares keys and value as text now, as the script does.

  • A tab or a card stack part named with a hyphen shows its slot, and a name never stands in for a prop. A key such as api-keys drew an empty panel or part with either way of writing its slot, and in a card stack a part written as <x-slot:api-keys> ignored its place in order. A tab keyed like a prop of the tabs or a variable of their own showed that value instead of nothing, label the tablist's name, and the key tab was an error page. A slot named like a prop took the prop's place: tabs keyed active and archived selected no tab, a tab keyed items left the tablist empty, and a part named order dropped the order. Each slot is now found under its own name, either way it is written, and every prop keeps the value written on the tag.

  • A tooltip around a button that a toolbar's arrow keys reach leaves the button as it is. A control that tabindex="-1" leaves out of the tab order was read as a disabled one, so the tooltip wrapped it in a stop of its own: a second tab stop in the toolbar, announced as a button around the button and as unavailable. The tooltip now makes no stop around a control that takes the focus itself, and the control's own focus shows it, as for any other button.

  • A hidden input is never where the focus or a description goes. A tooltip around a small form, its @csrf token in front of the button, gave its description to the token, and the button had none. In a hover card or a navigation menu panel that starts with a form, the Tab key from the trigger stopped on the token, and Tab at either end of the panel left it for an unrelated place. An app shell whose navigation starts with a form opened it with the focus on the token. Each looks past hidden inputs now, and the hover card and the navigation menu also past a control their open panel hides, such as a button shown only under a condition.

  • A read-only combobox keeps its value. readonly stopped the typing, but the arrow keys, Home, End, Enter, the focus and the chevron still opened the list, a choice from it changed the submitted value, and the clear button emptied it. Nothing opens, chooses or clears a read-only combobox now, as with a read-only native field; it keeps its place in the tab order, and without search the trigger says aria-readonly="true".

  • A floating action button announces only the popup it opens. The trigger of a speed dial said aria-haspopup="true", which ARIA reads as a menu, over a group of ordinary links and buttons, so a screen reader announced a menu button and the arrow keys did nothing. It is a disclosure button now, with aria-expanded and no aria-haspopup. A single fab.button with href is a link that navigates, and it no longer announces a dialog unless haspopup names one; a button without href still announces a dialog by default. A haspopup outside the popups ARIA names is reported while debugging and announces a dialog.

  • A disclosure, a toggle, a tab or a switch says the state it starts in before the script runs. The open state of a collapsible, a collapsible card, an accordion item and a folding sidebar section, the selected tab, the view of an event calendar, the density of a data table, the switches of a status matrix, the buttons of the editor toolbar, the theme controller, the menus of a menubar and a navigation menu, the submenus of a dropdown and a context menu, and the triggers of a floating action button, a navbar menu, a notification center, a filter builder and a color picker were written only by the script, so the markup carried no aria-expanded, aria-pressed, aria-checked or aria-selected until it ran, and none where it never did. A reading progress bar had no aria-valuenow either. Each now writes the state it starts in beside the binding: the one the server renders where the server knows it, closed for a menu or a panel that opens later, and zero for the reading progress. A collapsible rendered open paints its panel with the page instead of once the script starts, and a remembered sidebar section says its stored state from the first paint, as its panel already showed it.

  • An error given as a list of messages shows its first. :error="$errors->get('email')" on a form control was an error page, where $errors->first('email') showed the message. Every form control now reads a list as its first message, and a segmented control reads the $errors bag under its name, or turns its error off with false, as the other controls do. See Error State.

  • A week start outside 0 to 6 is read as a day of the week. A negative week-starts-on on a calendar answered the page with an error, and a number above six shifted the grid of the calendar and of the event calendar once the script ran. Any whole number now counts around the week, so -5 starts on Tuesday and 7 on Sunday.

  • A placeholder or a list written on an inline edit reaches the field it opens. Both stood on the element around the component, so the field opened empty without its hint and offered none of its datalist's suggestions. They now go where maxlength and autocomplete already went: the placeholder to the text, number or long-text field, the list to the one-line fields.

  • An enum bound to a prop is read as the text it stands for, inside a list as well. <x-wirekit::badge :intent="$order->status"> with a status cast to an enum was an error page, and so was an enum in any prop that takes text, scope included, on nearly every component. Inside a list it was the same: an option's value or a whole list from Status::cases() in a combobox, a multi-select or the quick replies, and a tab's key in tabs, ended the page, and a swatch group left out a swatch whose value was one. A backed enum is now read as its value and a pure one as its name, as the value of a form control already was. See Variants & Intents.

  • A tooltip that is switched off is quiet for a screen reader as well. With disabled it still described its trigger with its text, and around a disabled control it still made a stop of its own that showed nothing. It now does neither, unless focusable-trigger asks for the stop, and a switch bound to data-wk-tooltip-disabled takes the description away and gives it back as it flips.

  • A tooltip no longer describes a control with the control's own name. Every button of the editor's toolbar, the links and the collapse control of a collapsible sidebar, the toggle of an expandable app rail and the status time of a message carried a tooltip whose words were their own name, as its description, so a screen reader read the name twice. Where the slot names its control with the tooltip's words, as its text or its aria-label, the tooltip now leaves the description out by itself, as describes does; a shortcut drawn after the words keeps it.

  • The statement an energy label or a tire label reads out stays inside the label. The visually hidden sentence beside each class was placed against the page on a desktop, and on every screen for a label without a link, because nothing in the label had a position of its own: a scroll container around the label did not clip it, and past the edge of one that scrolls sideways it widened the page. The label is now the positioned box for its statement, unless a class on it sets a position.

  • A disabled, required or readonly that reaches no form control is reported while debugging. The three are valid HTML, so they passed as attributes on every component, and on a component that gives them to no control of its own they landed on the element around its button or field and did nothing: disabled on an accordion item or a floating action button left the button enabled. The same held for wire:loading.attr="disabled", which on a multi-select or a file upload left the field operable while a request ran. Such a component now logs a warning that names the attribute, and StrictnessGate::controlAttributesWithoutAControl() gives a test the same answer. The attribute on a <fieldset> around a component disables every control inside it. An autofocus that reaches nothing that can take the focus is reported the same way.

  • A wire:model on a component that binds no value with it is reported while debugging. wire: passes as framework wiring, so a wire:model on a collapsible, an accordion or a card landed on the element around its content, where no field shows the property's value, and nothing said so. Such a component now logs a warning that names the binding with its property, and StrictnessGate::controlAttributesWithoutAControl() gives a test the same answer. The form components, a modal, a drawer and an alert dialog take it as before. A component that binds it in one mode only reports it in a mode where it binds nothing and names what is missing: a data table without selectable, a pagination without per-page-options, a readonly rating or an editor that cannot be edited.

  • A form around a command palette trigger sends nothing for its search field. The field had no name of its own, so it was named after its id, and a surrounding form sent the typed text as input-1. What the field holds goes to the palette, and the field now renders no name.

  • An empty name on a form control sends nothing, on every control. An input given name="" already rendered no name, so a form skipped it. A checkbox, a toggle, a radio, a password input, a time picker, a rating and a segmented control wrote name="", which HTML does not allow, and a range slider, a one-time code and a phone field built names from it that a form sent: [min] and [max], [], [number] and [country]. Each now renders no name.

  • A Brazilian Portuguese page reads Brazilian where the two varieties differ. A pt-BR page fell back to the European wording for strings its catalog did not carry: the password meter read "Palavra-passe fraca" beside "Mostrar senha", pagination "Seguinte", the typing indicator "está a escrever" and the sign-in buttons "Iniciar sessão". They now read "Senha fraca", "Próximo", "está digitando" and "Entrar com", and so do the other sections, views and dividers that were written the European way.

  • The French catalog addresses the reader as "tu" throughout, as the other catalogs address the reader informally. Among the sentences in "tu", some said "vous" or used the formal imperative: "Réessayez", "Votre navigateur écrit les dates…", "Cliquez sur une carte…". They now read "Réessaie", "Ton navigateur…" and "Clique…".

  • A shortcut that x-wk-shortcut cannot read is reported. It bound nothing without a word, so a typo such as cmd+shfit+s read as a shortcut that works. With APP_DEBUG on, the page of the overview of keyboard shortcuts warns about it in the browser console, as it warns about a shortcut bound twice.

  • A list without markers and a stack rendered as a ul or ol stay lists for VoiceOver on iPhone and iPad. WebKit treats a list without markers as plain text, and both rendered one without a role: type="none" removes the markers of a list, and Tailwind's preflight removes those of every ul and ol. A screen reader on iOS therefore announced neither the list nor the number of its items. Both now carry role="list", and a role on the tag replaces it.

  • A timeline row takes a date object for its time. Given PHP's own DateTime or DateTimeImmutable as time or datetime, a row ended the page. Given a Carbon, it showed 2026-10-25 12:00:00 as its text and wrote the same value without its offset as datetime, and a Unix timestamp stood in datetime as a bare number, which HTML reads as a year. A date object as time is now written as the page's language writes a day with a time of day and gives the row its datetime as well. In datetime, a date object or a timestamp is written with its offset, a string such as 2026-09 as it stands, and a text that names no moment is reported.

  • A date given only as a zone is reported. A countdown, a message, a review, a date separator, an event calendar and a timeline row read CET, UTC or a single letter such as x, which PHP takes for a military zone, as the current wall-clock time with that zone's offset put on it: an instant hours away from now. Such a text names no moment and is reported as an invalid date, as TBD is. now, tomorrow, +1 day and a day with its zone read as before.

  • An activity row and a usage meter take a date object as well. Given PHP's own DateTime or DateTimeImmutable as timestamp or resetAt, either ended the page, and given a Carbon, it showed 2026-10-25 12:00:00. An activity row now writes a date object as how long ago it was in the page's language, in a <time> that carries the moment; a usage meter writes it as the day it resets, "Resets Jan 1, 2027" in English. Text reads as before.

  • A month and a week field take a date object, as a date field does. An input with type="month" or type="week" wrote a Carbon given as value, min or max as 2026-10-25 12:30:00, which the browser drops, so the field stayed empty and set no limit, and PHP's own DateTimeImmutable ended the page. Both now write it in their own form, 2026-10 and 2026-W43, with the week and its year numbered by ISO 8601, and a binding to a date object warns in debug mode as it does on a date field.

  • A timezone takes a zone object. Given PHP's own DateTimeZone, as getTimezone() of a DateTimeImmutable returns it, a countdown, a message, a review and a date separator ended the page: their timezone took a zone name only. A zone object now names its zone, as a CarbonTimeZone already did.

  • A link under the heading of a card that folds and a button in its hints take their own click. A press anywhere on the head folds the card, and the area that answers it lay over the whole head: a link in the description slot, or the button of a dismissible badge in the hints, folded the card instead of answering. Both stand above that area now, and the words around them stay part of the head.

  • A range no longer tints the days of the previous or the next month that pad a calendar grid. They showed part of the range without its ends, and with two months the same days a second time. The range shows in the grid of its own month, in a calendar as in the dialog of a date picker or a datetime picker.

  • The text field of a color picker with popover reads a hex value typed without its #, as design tools copy one: B87334 arrives as #b87334 rather than being called invalid.

  • A list pasted into a tags input one entry to a line, or from the cells of a spreadsheet row, makes one tag per entry. The field split only at commas, so the browser ran the lines together into a single tag.

  • A phone number typed with the international prefix 00, or with 011 on the North American plan, picks its own country as one with a plus does. 0043 1 234567 on a field that stood on Germany was saved as +490431234567, a wrong number, where +431234567 was meant. Digits after a prefix that name no country bind nothing.

  • A file upload writes the size of each file as an attachment does, whole bytes and one decimal below 10, 512 B and 24 MB rather than 512.0 B and 24.0 MB, with thousands grouped and in the application's own number format where WireKit::formatNumbersUsing() sets one.

  • A number input with a step below 0.000001, or with a min or max that has more decimal places than its step, keeps the value it is given. It rounded every value to the places of the step alone, so step="0.00000001" turned every value into a whole number, and min="0.99" step="1" turned 1.99 into 2, which the browser then refused as a value off the step.

  • An empty component that is both required and readonly no longer stops its form from being sent, as a read-only native field does not. A multi-select, a tags input, an OTP input, a combobox, a datetime picker and a date picker or time picker with popover kept the empty value in the browser's check, so the form showed the browser's message on a field nobody could fill.

  • A link with target="_top" or target="_parent" on a page no frame holds asks in the kit's dialog before it leaves unsaved changes, as a link without a target does. Both name the same window there, and the question before leaving took them for another one, so only the browser's own prompt asked, without the list of what is unsaved. Inside a frame they still go to the browser.

  • A placement an overlay cannot place is reported under the name of the tag it was written on. A status dot, an app rail item, the scope switcher and the collapse toggle of a sidebar handed the word to the tooltip or popover inside them, and the report named that one instead. Each now checks it itself and hands on a word the overlay can place.

  • An entry of unavailable that names neither a day nor a time is reported. A calendar, a date picker with popover and a datetime picker left such an entry out without a word, so a closed day written as 2026-13-45 or 24.12.2026, or a closed time written as 25:00, stayed open for booking. It is now reported through strict validation, as a min or max that cannot be read is, and still closes nothing. An empty entry, white space and the zero date pass without a report.

  • A field named with a space, such as first name, links its own hint and help to the control inside it. The field built their ids from its name as given, so the control's aria-describedby named first, name-hint and name-help, ids no element carries, and a screen reader read neither. A card, a filter builder and a notification center given an id with a space build the ids of their parts without it, and a resizable panel takes its id without it, so its handle names it. Each run of whitespace becomes a hyphen, as it does in every input and picker; a root keeps the id it was given.

  • A range of checkboxes picked with Shift held leaves out a checkbox the reader cannot see. A box in a row that a filter hides, with x-show or hidden, or inside a closed <details> was ticked with the rows around it, so an action on the selection afterwards reached rows nobody saw. A checkbox hidden behind a label drawn in its place still counts through that label. A data table with selectable was never affected: it renders only the rows it shows.

  • The value under a cell of a treemap chart takes the decimals its series names, as its tooltip does. A cell kept the chart's value-decimals, so a series with two decimals of its own read 5 in its cell and 4,53 in its tooltip.

  • A preset of a calendar or a date picker that starts or ends on an unavailable day can no longer be picked. With today among the unavailable days, "Today" picked it and "Last 7 days" ended on it, where a click on that day picks nothing. A preset that only covers such a day stays open.

  • An animated stat and a countdown write their numbers in the application's own marks. With a format given through WireKit::formatNumbersUsing(), the count of an animated stat ended in the grouping and decimal marks of the locale, so the number the page showed changed its format once the count ended, and the leading unit of a countdown grouped the locale's way. Both write the application's marks now, as a chart does.

  • A second click on a link opens an accordion item, a collapsible card or a hidden part of a card stack that the reader closed after the first. A link to the fragment the address already names leaves the address as it is, so the browser fires no hashchange, and the page scrolled to the item while it stayed closed. The three read that click as well.

  • A button with formmethod="dialog" closes its dialog while a form with submit-once is held. After a download, which leaves the page as it is, the form holds every further submission back for its wait, and the Cancel button of a dialog around it was held back with them, so the dialog stayed open for ten seconds, or for good with a wait of 0. A submission that closes a dialog goes at any time, and the hold stays.

  • Without an icon package, a sidebar item, a collapsible sidebar group and an app rail item no longer show the icon's name beside their label.

  • A floating action button whose icon cannot be drawn shows its plus, where an unknown icon name in a request left an empty circle.

  • A prop that takes one value from a list reads a list as not given instead of ending the page. A size, a variant, an intent or another prop with a fixed set of values, given a list, a collection or an object without a text of its own, ended the page in most components, in a request too. Each now goes through strict validation: with debug on, a Pest test or an artisan command throws, and a request logs it and renders the component with the prop's default. A prop of the same name that takes a date or a list elsewhere, such as the max of a calendar or the columns of a data table, is read as before.

  • A field, a sidebar group, a mixed chart and tabs given a list where they take a switch or a number no longer end the page. The required of a field, the collapsible, open and force-open of a sidebar group, the wire-stream-cap of a mixed chart and the wrap of tabs report the list through strict validation and render with their default. The count of a reaction, the quantity of a cart line, the value of a radial progress and the threshold of a reading bookmark, given a list or an object, render with their default as well.

  • A prop that takes one value, such as scope, reads a list as not given instead of ending the page. The scope every component takes, the srcset of an image, the timezone of a date separator and other props for a single value ended the page when given a list, in a request too. Each now reports it through strict validation and renders without it, and an object such as a time zone or a date is read as before.

  • The optimistic layer shows its state on its own root, also when a button's click applies it. The root stayed idle while a request ran and the button kept optimistic after it settled, because the state was written on the element whose click applied it.

  • A table in prose fits a narrow column, also without attributes. A cell breaks a long word where its column is narrower than the word, so a table that a Markdown pipeline renders without attributes no longer pushes the page sideways on a phone. It stays a table.

  • A bulk selection whose binding writes every match selects the rows of the page, from a "Select all" in a toolbar or from a state the page loads with. The notice said every match was selected while the rows and the header checkbox stayed clear.

  • A map drawn with Leaflet that misses one tile writes no load-failure message. The hint that names a Content-Security-Policy block comes once no tile in view loaded, so a tile missing at the edge of a set that covers one area stays quiet.

  • An x-ref written on a part of a component reaches the $refs of the code around it, on the parts of a tabs, a tree view, a sidebar, a dropdown, a context menu, a menubar, a resizable group, a table and an app rail, and on the triggers of a drawer, the shortcuts dialog and a command palette. An item of a dropdown panel is included, though the panel is moved to the overlay root.

  • A range in a calendar draws one continuous band from end to end, square where it meets an end, where the days between the ends were separate rounded tiles with a gap between them. While the pointer previews an end, the band reaches the day under it, and that day carries a stronger tint than the band. The same holds in the dialogs of a date picker and a datetime picker.

  • A calendar with more than one month names each month once. The header named the first month above grids that each name their own, so that month stood twice, and in the dialog of a date picker or a datetime picker on a phone, which shows one grid, both names stood directly above one another. With several grids in view the header holds the arrows, and with one grid in view it names the month. A screen reader still hears the change of month, and each grid keeps its month as its name.

  • The grids of a calendar with more than one month fill a calendar that its container stretches, as one month does. In a flex flex-col or a grid the header ran across the whole width while the grids stood together at its start, or narrower than the card on a phone. Side by side the grids now share the width, and each takes a whole line once they wrap. A container that fits its content looks as before.

  • The panel of a notification center stays open while the keyboard moves through it on a phone. Opening it, the arrow keys in its filters and "Mark all read" moved the focus in a way that let the browser scroll the page, and a scroll that moves the bell closes the panel, so on a page with smooth scrolling it closed under the reader's keys and left the focus nowhere. The focus moves without scrolling the page now.

  • "Jump to latest" in a conversation moves the focus to the transcript. The button hides once the view reaches the newest message, which its own press brings about, and the focus it held fell to the page, so the next Tab started at the top of the page instead of in the conversation. A press with the keyboard, or with a pointer in a browser that focuses the button, leaves the focus on the transcript, where the arrow keys scroll on.

  • The arrow keys that open and close a branch of a tree view no longer send tree-node-select. Right and Left toggled the branch through a click on its row, and that click reached the tree's selection, so a page that opens a record on the event opened one whenever a reader unfolded a branch with the keyboard. A click, Enter and Space still select.

  • An OTP input announces a whole code corrected by overwriting a box. wirekit:otp-complete fired when the boxes filled up, which a full code never does again, so a code corrected the way the page recommends, one pass from the left without deleting, was never announced, and a page that checks the code on the event and leaves a wrong one standing never received the corrected one. The event now fires for every whole code that differs from the one announced last, never for the same code twice in a row, and once every box is empty the next code counts as a new attempt.

  • A row with x-wk-flash that starts leaving flashes and goes, also without a new key. The page on flashing a row that changed has the render that removes a row draw it once more with data-wk-flash="danger" and data-wk-flash-leave, but the flash played only on a new data-wk-flash-key, so a row that kept its key stood there without a flash until the next render took it out. Setting data-wk-flash-leave plays the flash now, and a leave that brings a new key along plays it once.

  • A click inside the open list of a combobox or a multi-select that chooses nothing keeps the list open. Both lists are moved out of the component to the overlay root, and the component read every press in them as one outside it, so a click on a disabled option, a group heading, the list's edge or one of its notes closed the list. A click outside the component and its list still closes it.

  • The note under the results of a combobox or a multi-select with server never stands empty in the list. For a moment between showing the row and writing its text, the row that says a newer search is still out or that the results were cut stood visible and empty, and in the combobox and the multi-select's panel it is an option, so an accessibility check met an option without a name. An empty row is out of sight and out of the accessibility tree now, and the notes are read out as before.

  • A panel opened again from its trigger while it is still closing stays open. The panels of a filter builder, a color picker and a notification center, the dialog of a datetime picker and those of a date picker and a time picker with popover close on a press outside them, and they counted their own trigger as outside. A panel opened again while it was still on its way out, as after a filter was applied and its chip removed, closed in the same click and left the focus nowhere. A press on the trigger is left to the trigger, which opens and closes the panel.

  • A confirmed save in an inline edit hands the focus back to the trigger. The trigger is shown on an animation frame, and the focus could be handed to it before that frame, while it was still hidden, so the focus stayed on the page and the next Tab started at its top. The editor now waits for the frame that shows the trigger, as it already does for the one that shows the field when it opens. The same holds after Escape and after a confirm that changed nothing.

  • An answer to an inline edit reaches only the editor that sent the value. wirekit:inline-edit-saved and wirekit:inline-edit-failed name an editor, and every editor of that name took them, so on a page that gives a name more than once an answer for one closed the others too, an editor an error had opened among them, and handed the focus to the last of them. An editor takes an answer now only while it waits for one, also after its save timeout gave up.

  • In a resizable layout of three or more panels, a handle follows a panel that another handle moved. The second handle sizes the middle panel, which a drag on the first handle resizes too, and it kept announcing the size it had written itself and stepped from there, so its next arrow key or step control set the middle panel back near its old size in one press. A handle now reads the size its panel has, announces it with the range that is left, and moves from it.

  • The hint above a live list rests below whatever pins itself to the top of the list's scroller, and a fresh row under it counts as out of sight. "New entries above" sat a fixed distance below the top of the viewport and lay under a strip of an announcement banner, a sticky navbar or another bar pinned there. It rests below the scroller's scroll-padding-top now, which a strip, every pinned bar of the kit and a bar of an application's own with data-wk-scroll-inset report, and in a panel of its own below the top edge of the panel, which a strip of the page does not cover. --wk-live-list-top on the list places it below a fixed bar that reports nothing. Where the page scrolls the list, a fresh row that landed under such a bar counted as seen and raised no hint; it counts as out of sight now, and the hint brings it out from under the bar. The reader's line stays where it was while entries arrive, also when such a bar changes its height in the meantime.

  • The hint of a live list brings its row into view in Safari and on iPhone and iPad. The hint scrolled to the fresh row and then moved the focus to it, and WebKit stopped the smooth scroll the moment the focus moved: the row stayed out of sight with the focus on it, where the page scrolls the list and in a region of its own. The focus comes first now and the scroll after it, and a row still wholly out of sight once the glide has had its time comes in without the animation.

  • wirekit:doctor scans the log files where the single and daily channels write them. The silent-typo scan looked only at storage/logs/laravel*.log, so with the daily files moved into a folder of their own, as a logrotate rule over storage/logs/*.log makes advisable, it skipped itself on every run and no rejected prop value surfaced. It reads each channel's path now and finds the daily files beside it under the names Laravel gives them.

  • The collapse control of a sidebar lines up with the item icons. In both states its glyph stood a few pixels left of the column of the icons: its placement never reached the column, and the glyph sat closer to the button's edge than an icon to its row's. Expanded it now starts where the icons start, collapsed it centers in the rail as they do, also on the footer band.

  • The icons of a sidebar's items and collapsible triggers center on their labels' letters. Inter sets its letters above the middle of the line, so an icon centered on its row read 1 to 2px low beside the label. The stylesheet lifts it by the new --font-wk-icon-nudge token, -1px for Inter, and keeps it there while the sidebar is collapsed, so folding moves no icon; any element that carries data-wk-icon-nudge takes the same offset. The amount suits Inter, so the theme presets with a font of their own, a sans set in fonts.sans and wirekit:install --font with any font but Inter set it to 0px.

  • A cell of a stacked table aligned to the end shows a label of its own. Such a cell always stood as the footer of the card, where no name shows, so its label never appeared. A label of its own now makes it a named line.

  • An alert that a Livewire render adds after an action is announced. Livewire inserts such an alert together with its text, and an inserted role="status" is not announced on its own, so the reader who had just submitted a form heard nothing of its result. WireKit's script keeps one polite status region on the page from the start and says the intent, the title and the message of each polite alert a render adds, without its actions or its dismiss button. A danger alert is still announced by its own role="alert", an alert inside a status region of the page's own is left to that region, and an alert rendered with the page says nothing on load.

  • wirekit:install --font changes the font of the components, not only of the body copy. The block it writes into resources/css/app.css set --font-wk-sans, --font-wk-serif or --font-wk-mono inside @layer base, and a declaration in a cascade layer loses to the unlayered tokens of wirekit.css whatever its specificity, so every component kept Inter beside text in the new font. The block now sets the token in a plain :root rule; running the command again replaces the old block between its markers.

  • A language named with a locale of the application, such as pt_BR, reaches lang as the tag HTML reads, pt-BR. The option of a select and of a segmented control, a field of a translatable input and the label of a checkbox and a radio wrote it as given, and HTML reads pt_BR as an unknown language, so a screen reader kept the voice of the page. A charset or a modifier, as in de_DE.UTF-8, is left out as well, and the language props new in this release read it the same way.

  • Tabs with findable start on the tab the server names. With active set to another tab than default, the tabs went back to the default tab as soon as the page started, in every browser that supports hidden="until-found": the panel drawn open for the default tab read like a panel the reader had found with the browser's search. The tab the server names now stays.

  • A sign-in button without href takes the type it is given. It wrote type="button" ahead of the attributes it was given, so a type="submit" came second and was ignored, and a form that starts the sign-in with a POST request sent nothing. Without a type it is a plain button, as before.

  • A sign-in button drawn by WireKit is as tall as a finger needs on a touch screen. Drawn at the medium control height, the button of Google, Apple, Facebook or PayPal stayed below the 44px a touch target takes under a coarse pointer. It reaches that height there now and keeps its size under a mouse, and the button image of a service keeps the size the service draws it at.

  • A floating action button, its actions and a profile drawn as a button take the type they are given, and a tab, a menu item, a submenu trigger, a command palette option, a close button, a sidebar toggle, a rail item and a toggle button keep type="button" without a second type beside it. Each view wrote its own type ahead of the attributes it was given, and the first of two equal attributes is the one that counts.

Documentation

  • The phone number recipe shows its field in the form its title promises, with the Livewire wiring wirekit:make writes, and the marketing landing page recipe describes the colors its sparklines are drawn in.

  • The marketing landing page recipe no longer stacks a small uppercase label over its sections or a row of invented customer logos under the hero. Each section opens with its heading and lede, wirekit:make recipe:marketing-landing-page writes its sections the same way, and the landing page with a table of contents names its brand without an emoji.

  • The alert dialog page shows how an action receives the typed phrase. wire:click="destroyEnvironment(typed)" on the confirm control sends null: the dialog's panel stands at the end of the page, outside the element that holds typed, and Livewire reads a name it does not find in the Alpine data around an element as a property of the component. The page shows x-on:click="$wire.destroyEnvironment(typed)", which Alpine evaluates with the dialog's data, and the phrase checked again on the server.

  • The card and liquid glass pages give a card's title a heading. Their examples wrote it as text in card.header, which gives its content the look of a heading and not its role, so a screen reader listed no heading for the card. Each title now stands in a heading, and the card page says so under Accessibility.

  • The mixed chart page says what wireStreamMode decides. It called it the handling of a malformed streamed point. It decides the cap: strict keeps each dataset at wireStreamCap points and drops the oldest first, stream lets the series grow without a bound, as on the chart page.

  • The brand page names :href="false" for a brand that is not a link. It said :href="null", which a prop reads as not given, so the brand kept its link to /.

  • The first example on the events page shows the tab it hears. It read a name from wirekit:tab-changed, which carries the tab's key as tab and its label as label, so its preview showed a dash after every click, and the sentence below it sent the event from Livewire with a name no tab reads. It now reads tab, and the sentence points at the way tabs are observed from a Livewire component.

  • The examples that listen to WireKit's events run under a Content-Security-Policy build. On the events, sidebar and tree view pages, a listener used optional chaining, an if statement or console, none of which Alpine's CSP build evaluates: on a page with Livewire's csp_safe or WireKit's CSP bundle, a copied listener did nothing and reported nothing. Each is an assignment, a && or a call now, and the integration page says where an example's own logic goes on such a page.

  • The slider, textarea and select pages name the Livewire 4 binding that sends. They recommended wire:model.change or wire:model.blur in place of .live, and without .live neither sends anything by itself. The binding that sends when a slider is released is wire:model.live.change, and the one that sends when the reader leaves a field is wire:model.live.blur.

  • The streaming examples on the shimmer and assistant message pages set their streaming state in a request of its own. Both turned $streaming on and off inside the request that streams, and a streamed request renders the component's state only when it ends, so the shimmer never moved and the message never showed its thinking state or aria-busy. submit() now sets the state and starts the stream with $this->js('$wire.ask()'), and the shimmer example gets the wire:stream target and the property its text streams into.

  • The assistant message example for announce="all" says where its event comes from. Its listener called flush() on stream-finished, which none of the examples dispatched, so a message copied from them never announced its answer. The streamed method now dispatches the event as its last step, and keeps the answer in its property, so the last render shows what was streamed.

  • The headless example on the countdown page binds the number rather than a template literal. A Content-Security-Policy build reads no template literal, so the copied example showed no number there, with no error anywhere. The words now stand in the markup, as in the component's own source, and the page says why.

  • Seven examples close their tags and run as pasted. On the pages of the accordion, the app shell, the table, the dropdown, the drawer, the command palette and the alert dialog, an example that showed one prop through an opening tag alone ended with a parse error when pasted as it stood.

  • The optimistic UI page explains optimistic-args. A control that repeats once per row, such as a checkbox in each line of a task list, tells its method which row it belongs to: the values of optimistic-args follow the new value in the call. The page every optimistic component links to did not say so.

  • The Leaflet & OpenStreetMap page sets the tile source once for every map, and its examples follow that setting. Each example set its styleUrl to the public OpenStreetMap tile server; now none sets one, so they draw the tiles a site configures, and a copied example loads from wherever the application's other maps load. The map page shows the settings for both engines, and the MapLibre page says that the demo style, its tiles and its fonts come from demotiles.maplibre.org.

  • The error-state example of the color picker no longer promises a named color in its hint, which no field of the picker reads.

  • The price page says that minor-units divides by 100 for every currency, and how to pass a currency whose minor unit is no hundredth, such as the yen or the Bahraini dinar.

  • The page on importing only some of the components shows the same path under a Content Security Policy: register() on the Alpine of Livewire's livewire.csp.esm.js, with @livewireScriptConfig in the layout, or on @alpinejs/csp without Livewire. The README of the bundles points to it and to the factories that wirekit:export-api-map lists for every component.

  • The calendar's range limits say that a range may cover an unavailable day. unavailable closes the days a range can start or end on, not the days between them, so a range that must not include one, a stay for example, is checked on the server.

  • The live example on overlay events reads back the event a modal sends when the reader closes it. It listened for wirekit-modal-close, the command a page sends to close a modal, which the modal never sends, so after a close its readout still showed the opening. It shows wirekit:modal-dismissed with the route the reader took now, and the page says which of the two events is a command and which a report.

  • The sidebar page says where its collapse toggle stands. It said that toggle="start" moves the control to the top, which it never did: with start and with end the control stands at the foot of the column, lined up with the item icons, and none omits it.

  • The accessibility example on the chart-mixed page gives the label to the chart itself. It wrapped the chart in a second role="img", which the chart page warns against: two nested images are one to a screen reader, and the chart's own name and the table beside it leave the accessibility tree. The example now passes aria-label to <x-wirekit::chart-mixed>.

Components

Every component this release changes, each linked to its page: Accordion · Action Bar · Activity Row · Alert · Alert Dialog · Announcement Banner · App Rail · App Shell · Aspect Ratio · Assistant Message · Attachment · Avatar · Badge · Band · Bento Grid · Blockquote · Bottom Nav · Branch Switcher · Brand · Brand Bar · Breadcrumb · Bulk Selection · Button · Button Group · Calendar · Callout · Card · Card Stack · Carousel · Cart List · Cart Summary · Center · Chart · Chart Mixed · Chat Marker · Checkbox · Clipboard Button · Code · Code Block · Collapsible · Color Picker · Combobox · Command Palette · Container · Context Menu · Conversation · Countdown · CTA · Data List · Data Table · Date Picker · Date Separator · Datetime Picker · Divider · Drawer · Dropdown · Editor · Empty State · Energy Label · Event Calendar · FAB · FAQ · Feature · Feature Grid · Field · File Upload · Filter Builder · Fit Text · Flag · Fonts · Footer · Form · Grid · Header · Heading · Hero · Highlight · Hover Card · Icon · Image · Image Compare · Image Editor · Image Gallery · Image Hotspots · Image Hotspots Editor · Indicator · Inline Edit · Input · Kanban · Kbd · Label · Lightbox · Link component · List · Live List · Logo Cloud · Main · Map · Mark · Menubar · Message · Message Group · Message Typing · Mockup · Modal · Multi-Select · Navbar · Navigation Menu · Notification Center · Number Input · OTP Input · Overflow Nav · Page Header · Page Progress · Pagination · Password Input · Phone · Popover · Price · Pricing Table · Product Card · Profile · Progress · Progress Radial · Prose · QR Code · Quick Replies · Radio · Range Slider · Rating · Reaction · Reading · Reorder · Replay Button · Resizable · Reveal · Review · Review Summary · Row · Scope Switcher · Scroll Area · Scroll to Top · Section · Segmented Control · Select · Shell Bar · Shimmer · Shortcuts · Sidebar · Sign-in Button · Skeleton · Skip Link · Slider · Spacer · Sparkline · Spine-Aware · Spinner · Stack · Stage Card · Stat · Stats · Status Dot · Status Matrix · Status Tiles · Step Marker · Stepper · Sticky Panel · Stream · Strength Meter · Structured Data · Swap · Swatch · Swatch Group · Table · Tabs · Tags Input · Team Section · Testimonial · Text · Textarea · Theme Controller · Ticker · Time Picker · Timeline · Tire Label · Toast · Toggle · Toggle Button · Tool Call · Toolbar · Tooltip · Tour · Translatable Input · Tree View · Unsaved Hint · Usage Meter · Video · Visually Hidden · Wizard