Skip to main content

WireKit v2.64.0

Minor release

Minor. A component keeps working when a Livewire update renders it with new values, a URL written into a component tag as href="{{ $url }}" keeps every query parameter, and the components that keep their value out of sight send it under wire:model.blur and wire:model.change, as a rating, a range slider and a calendar now send the value just chosen. A field's label names the combobox, the date picker or the multi-select inside it, and buttons and fields show their focus ring under the minimal, retro-terminal and brutalist presets. Glass looks like liquid glass, in four levels from clear to opaque. Info has tokens of its own, a code block can wrap its lines, a one-time code can fill its width, and x-wk-scroll-into-view brings a result into view when an action makes it appear far from its button. A form says when it holds unsaved changes and can ask before the reader leaves, a modal or a drawer asks before the reader discards what they typed into it, every field can explain itself in a tooltip from a question mark beside its label, a select with nothing to choose says what is missing and where it is created, a toggle can name its state in words and take a color for each state, a click on a range slider's track brings the nearer handle there, a resizable handle takes clicks on two step controls, a kanban card moves with two clicks in reorder mode, an app rail item can say what its counter counts, and the entries of an overflow nav take a class block of their own. A search field in a page's header opens the command palette with what was typed, a page header can stay at the top and set itself apart once the content scrolls under it, text keeps to a reading width, and a data list can set each label above its value. A field you tab to, or a fragment link's target, comes to rest beside a pinned bar instead of under it. A click inside a dialog reaches the page's own listeners, Back returns to where a page was left from an open overlay, the open list of a combobox or a multi-select closes when Tab moves on and a choice made with the mouse leaves the focus in its field, containers pad evenly, an invalid field stays red under the pointer and in its focus ring, a link button sits flush with its sentence, the clear button of a field stands as deep in its frame as an icon, and the knob of a switch and a counter on the current rail module stay visible.

An input and a textarea count their characters as the reader types, and a combobox, a multi-select and a segmented control take the language of an option, so a list of languages names each one in its own voice. Every tab stop the kit renders draws the kit's focus ring, and three classes put that ring on a tab stop of your own. A menubar, a navigation menu and a popover close when a press outside them ends rather than when it begins, and a phone field's country picker, an OTP input's first box and an attachment's upload state reach a screen reader under their own words.

Security

  • The font route serves only what lies inside the font directory. It compared the resolved path with the directory's own path, so a file beside the directory whose name begins with fonts, such as resources/fonts-extra/, would have passed as if it were inside. The package ships no such file, so nothing outside the font directory was reachable. The route now compares against the directory and a separator, and the asset routes make the same check.

Added

  • A modal or a drawer asks before the reader discards what they typed, and does so by default. Once the reader has changed a field in it, Escape, a click beside it and its close button first ask "Discard your changes?" in the reader's language, and it stays open unless they agree. A field x-wk-unsaved marks as unsaved counts as a change too. A Cancel button and a close your page makes ask nothing, and neither does an overlay the reader has not changed, one your server filled after it opened included. :confirm-discard="false" turns the question off for one overlay, 'confirm-discard' => false under components.modal or components.drawer in the config for all of them, discard-question sets your own question, and data-wk-discard-ignore leaves a field such as a filter box out.
  • Every field can explain itself with help. A small question mark beside the label of an input, a select, a textarea, a checkbox, a toggle or any other field with a label shows the text in a tooltip on hover and on keyboard focus, and Escape hides it. A screen reader hears the same text as the field's description. The button sits beside the label rather than inside it, so the field's name stays the label's text, and a row with help is as tall as one without, so fields side by side keep their controls on one line. With a visually hidden label there is no button and the text still describes the field. In a field wrapper the help's id is offered for the control in the slot, as the hint's is. A click on the question mark, and Enter or Space on it, dispatches wirekit:field-help with the field's name and label, so a page can open its own modal with the whole explanation; Livewire hears it with #[On('wirekit:field-help')]. A field without help renders as before.
  • An input and a textarea count their characters as the reader types: counter. The count shows under the field, "12 / 60" with a maxlength and the number on its own without one, and it moves with every key in the browser, with no request to the server, so a count under a field no longer needs wire:model.live. A string instead of true is the sentence the counter shows, with :count, :max and :remaining. A screen reader hears the count in words once typing pauses, and past the limit how far past it the text is. It runs under Alpine's CSP build.
  • A combobox, a multi-select and a segmented control take the language of an option: lang. A list of languages names each one in its own language, "Deutsch" or "日本語", and with the option's lang a screen reader says it in that language's voice instead of the page's. The combobox and the multi-select read it from the option array, beside label, and the multi-select carries it to the pill and the list of chosen values too. The segmented control takes an option as an array with label and lang, the shape a select already takes. A lang that is not a language tag throws an InvalidArgumentException that names the option.
  • A search field opens the command palette and hands it what is typed. <x-wirekit::command-palette.trigger> sits in a page's header; a click, Enter or the Down arrow opens the palette, and so does typing, with the text in the palette's own field. Keys typed while the palette opens arrive too, so a word typed in one go arrives whole, and text an input method is still composing goes when the composition ends. The show event takes query as well: $dispatch('wirekit-command-palette-show', { query: 'invoice' }) opens the palette searching for it, with the cursor after the text, and reports the query at once.
  • Text keeps to a reading width with measure. measure="default" stops a paragraph at about 65 characters and measure="wide" at about 78, as on prose, so an introduction under a page's title does not run across a wide column. Without the prop, text runs as wide as its container, as before. The description of a page header keeps to the wide measure on its own.
  • A page header can stay at the top: sticky. It sticks to the top of the box the page scrolls in, below an announcement banner strip when there is one, with as much space above its content as below it. Once it rests there and the content scrolls under it, a bottom border fades in and sets the two apart; in the flow of the page it draws none, and under reduced motion the border appears without fading. The resting state is data-wk-stuck on the header, for a style of your own. --wk-page-header-rest-gap sets the space above and below, and --wk-page-header-top the line it sticks to, so a header in a box with a padding of its own can cover that padding. Without sticky the header is unchanged.
  • A field you tab to, or a fragment link's target, comes to rest beside a pinned bar instead of under it. A sticky navbar, header, toolbar, shell bar, brand bar, page header or announcement banner, a bottom navigation and a floating action bar tell the page, or the box they stick in, how much of its edge they cover, and it keeps that much clear as scroll-padding. Tabbing back up a form under a sticky navbar used to leave fields entirely under the bar, and tabbing down past a bottom navigation hid them behind it. Put data-wk-scroll-inset on a bar of your own for the same (keeping the focus clear of a pinned bar); a scroll-padding you set yourself wins.
  • A click or tap on a range slider's track brings the nearer handle to that point. Dragging was the only way to set a value with a pointer. The handle that moves takes the focus, so an arrow key fine-tunes from there, and the track takes the click across a band as tall as a handle's target although it is drawn 8px tall. The click that ends a drag does not count, so releasing a handle over the track never moves the other one.
  • A resizable handle resizes with a click as well as a drag. Two step controls beside the grip move it a few pixels per click, little enough that the control is still under the pointer for the next one. They show while the handle is hovered or focused, and always on a touch screen. The handle keeps its keyboard model, and the controls take no focus.
  • A card on a sortable kanban board moves with two clicks. Each sortable column has a Reorder button in its header. Pressed, a click picks a card up and the next click puts it in another card's place, in its own column or, on a cross-column board, in another one. A drag and the keyboard were the only ways to move a card, and a pointer that clicks but cannot drag had none. The move reports once, through the same event a drag sends, and while the mode is on a click does not reach the links and handlers inside a card.
  • Glass in four levels: wk-glass-clear, wk-glass-regular, wk-glass-frosted and wk-glass-opaque. Set on the glass element or on any ancestor, such as <html>, a level decides how much of the backdrop the liquid glass shows, from the clearest pane to the theme's opaque surface. Six --glass-wk-* tokens carry each level's fill, rim, light, edge and shadow for light and dark, and a rule in your own stylesheet overrides them without editing the published file.
  • Glass answers Increase Contrast. Under prefers-contrast: more, which Safari reports, a glass surface turns opaque with a strong border.
  • A form says when it holds unsaved changes: x-wk-unsaved and the unsaved hint. Put x-wk-unsaved on a form or a card. Every field inside it with a deferred wire:model that differs from the value its Livewire component last received is framed in --color-wk-border-unsaved, a blue beside the red of an error and the green of a success. A field with a prefix, a clear button or pills takes it on the frame around them, and the frame is drawn as an outline as well, so it shows under a theme whose border width is 0px and the field does not move. <x-wirekit::unsaved-hint> beside the save button says so in the reader's language, as a polite live region. x-wk-unsaved.confirm also asks before the page is left with an unsaved field: the browser's own question on a reload or a closed tab, and the hint's question before a wire:navigate visit. A save that redirects is not held, a step through the history is not held, and an invalid field keeps its error border. wirekit:doctor:a11y --theme-contrast checks the new border at 3:1 against the field.
  • A select says what is missing when it has nothing to choose: empty-text, empty-href and empty-action. A select filled from your records showed only its placeholder until the first record existed. With empty-text and no option, it is disabled, the sentence below it says what is missing, and empty-href adds a link to the screen that creates it, named by empty-action. The select names that line in aria-describedby, so a screen reader on the field hears why it is empty. The empty slot takes your own control instead. With an option in options or in the slot, nothing changes.
  • A toggle can show its state in words and take a color for each state. on-label and off-label write a word beside the switch, after it and its label or before it with state-position="start". The word follows the click at once, before anything is saved, and a screen reader hears the switch's own state instead. on-intent and off-intent color the track with accent, success, danger or neutral, so "green on, red off" is on-intent="success" off-intent="danger" rather than a replaced track.
  • badge-label on an app rail item says what its counter counts. A dot in the narrow rail says that something waits, and digits beside a name say how many, but neither says what. The sentence, such as "3 vouchers wait for review", shows in a tooltip on hover and focus in every mode, under the name where the name is a tooltip too, and it becomes the link's description for a screen reader, so the bare count leaves the name.
  • Info has tokens of its own: --color-wk-info-tone, --color-wk-info-fg and --color-wk-info-hover, beside --color-wk-info-text. Every info surface reads them: an alert, a callout, a toast, a badge, a banner, a progress bar, a map pin and an info button among them. They alias the accent's tokens, so nothing changes until you set them. When your accent reads as success, a green brand for one, give info a color of its own: every informational notice follows, and success stays green. An alert, a callout and every toast also name their intent in data-intent. On a feature chip, intent="info" follows the info color while the older tone="soft" keeps the accent's tint.
  • Three classes draw the kit's focus ring on a tab stop of your own: wk-focus-ring, wk-focus-ring-offset and wk-focus-ring-inset. A link, a button or an element with tabindex="0" that you write yourself takes the ring the components draw, on :focus-visible only and from the ring tokens: at the edge the way a link draws it, past a band of --color-wk-ring-offset the way a button draws it, for an image or a swatch, or inside the box for a scroll region. See design tokens.
  • A result that an action makes appear far from the button comes into view. Put x-wk-scroll-into-view on a form that opens at the top of the page, a key shown once or a message above a long list, and render a data-wk-scroll-key that is new with every result: the region comes into view and takes the focus when it appears and whenever the key changes, and a page that loads with it does not move. Under reduced motion it jumps instead of gliding. See bringing a result into view.
  • A code block can wrap its lines: wrap. It breaks long lines instead of scrolling them, and since nothing scrolls then, the block is no tab stop of its own. Without wrap a long line scrolls inside the block, which stays keyboard-reachable as before. A page of wrapped examples saves a keyboard reader one press per block.
  • A copy tells the page. The code block's copy button and the clipboard button dispatch wirekit:copied once the write has succeeded or failed, with event.detail.succeeded and the block or the button as the target. The event bubbles, so one listener on document counts every copy on the page, a second copy with the same result included.
  • A one-time code can fill its width: justify="stretch" on the otp-input. The cells share the control's width, so a PIN row ends flush with the sentence above it and the button below. Every cell stays square and grows from the touch target, or from its size when that is larger, up to twice its size; where every cell has reached that, the row sits centered. With group, each group takes its share by its number of cells and still breaks at its boundary.
  • The list item of an overflow nav entry has a class block of its own: item. It holds the item around an entry's link and its action in the rows, so a personalization can frame the two as one, as a tab bar of open records does, and reach the current entry through its data-wk-overflow-current attribute. It is empty by default, and an entry with an action sets its link and its button in a line, which a closure receives as the vendor block.

Changed

  • wk-glass looks like liquid glass. It was a milky surface, a 72% white wash under a 20px blur that hid its backdrop. It is now the regular level of the liquid glass: a 6px blur, a thin fill, and an edge that catches the light, so the backdrop comes through softly. For the previous look, add wk-glass-frosted, which keeps its blur and fill and gains the edge. The published stylesheet does not update itself: once any edits you made to it live in your own stylesheet as tokens, run php artisan wirekit:glass install --force.
  • Containers pad evenly. The body, header and footer of a card, an alert, a callout, an alert dialog, the header of a drawer, an action bar, an announcement banner, the panel of an accordion, a stat, a stage card, an empty state and a sticky panel have the same padding on every side now, the value their sides already had, so a button at the end sits as far from the bottom edge as from the side. They grow a little taller, and nothing moves sideways. A component's block can still be personalized to the flatter padding, and the theming guide gives the pair for your own containers.
  • The current link of an overflow nav is set in --font-wk-selected-weight. The token follows the heading weight until a theme sets it, so nothing changes by default, and a theme that marks a choice without bold no longer has to replace the whole link block. The link's fill still marks it, and under forced colors, which replace the fill, it is framed in the system highlight color.
  • The MCP server answers with an example from each page documented under a component. The worked examples of the command palette include its search field and a remote search, and the map has examples on both of its engines, where it had none.

Fixed

  • A component keeps working when a Livewire update renders it with new values. Many components, among them the modal, the dropdown and the combobox, went on acting on a copy of themselves that nothing displayed once an update changed a value they were rendered with, such as the server's answer to an optimistic choice in a combobox rendered with :value="$country": the combobox cleared its field when the answer arrived, and its arrow keys stopped moving the active option. Affected since 2.57.0.
  • A URL with more than one query parameter reaches its target when you write it into the tag as href="{{ $url }}". A link component, a button, a card, a dropdown item and the other components that take a URL as a prop wrote such a value escaped twice, so the browser followed ?a=1&amp;b=2 and your application read a parameter named amp;b: a signed link lost its signature and answered 403. The same happened when a component of your own passed its attributes on to one of them. A URL bound with :href was always right. The src of an avatar, an image and a flag had the same fault.
  • wire:model.blur and wire:model.change reach the server from every component that keeps its value out of sight. Livewire 4 applies both modifiers on the bound element itself, and the phone field, the rating, the segmented control, the one-time-code input, the editor, the calendar, the color picker in its popover, the range slider, the filter builder, the status matrix, the notification center, the pricing table's interval toggle and the data table's selection keep their value in a field the reader never focuses. None of them sent it with .blur or .change, and the number input lost a value changed only with its steppers. Each now fires change when a value is committed and blur when focus leaves the component, as a native field does.
  • A rating and a range slider bound with wire:model send the value just chosen. Both told wire:model about a change before the value had reached the field it reads, so a rating sent the star chosen before the last one, and a range slider stayed one step behind every key press and every move of a drag. That held for wire:model and for wire:model.live. A plain form was not affected.
  • A calendar bound with wire:model sends the date chosen. Livewire 4 hears wire:model only on the element that carries it, and the calendar carried it on its outer element while the date came from a field inside it, so neither wire:model nor wire:model.live ever reached the server. A plain form was not affected.
  • A combobox or multi-select bound with wire:model keeps its choice when the server changes its options. The options and the value were part of the expression the combobox and the multi-select start from, so a Livewire render with a new list reset them to that expression: the choice went to nothing, and wire:model.live sent the empty value back, replacing the choice on the server too. Both now travel on attributes of their own, and the choice stays while the list takes the new options.
  • A modal, a drawer and an alert dialog bound with wire:model follow the property when the server changes it. After the first render, setting the property to true opened nothing, and setting it to false, as a save action does to close its modal, left the open drawer or alert dialog standing. Both directions work now. A close made this way announces no dismissal, as the modal page describes.
  • An x-model on a command palette or a map is dropped, and a warning says so, as on the other components that hand no value over. On a palette that renders in place the binding received the text of its search field, and on a map nothing.
  • A click inside a modal, a drawer, an alert dialog or a command palette reaches the page. Each of them stopped every click inside its panel, so a listener your page delegates to the document heard nothing from inside them, and a dropdown, popover or combobox open inside one stayed open when you clicked elsewhere in the dialog. A click beside the panel still closes the dialog, and a click inside still does not. The clear and toggle buttons of a combobox and the remove button on a multi-select pill let their clicks through as well.
  • A vertical swipe that starts on a row of a table or a data table scrolls the page. The region a table scrolls sideways in could also scroll up and down by the few pixels the cells' line boxes can leave under the table, so on a touch screen it took a vertical swipe that began on a row, moved by those pixels, and the page stayed where it was. A mouse wheel over a row did the same. The region scrolls only sideways now; with sticky-header it still scrolls the rows under the header.
  • Removing a chosen value from a multi-select leaves the focus in the field. A press on a pill's remove button took the focus away with the pill, so the next Tab started again at the top of the page. The focus now moves to the pill that takes the removed one's place, to the new last pill, or to the search field once nothing is chosen, from the keyboard and with the mouse. The list of options stays closed through a removal: it opens when you tab or click into the field, type or press an arrow key. The remove buttons of layout="list" already kept the focus, except with optimistic set, and now keep it there too.
  • The open list of a combobox or a multi-select closes when Tab moves the focus on. Both closed their list on a click outside and on Escape only, so a reader going through a form with the keyboard left an open list behind each of these fields, and two multi-selects in a row left two lists standing. Tab in the field now closes the list and chooses nothing, and the focus moves on as before.
  • A choice made with the mouse in a combobox leaves the focus in its field. An option cannot take the focus, so the press handed it to the page, and in Chrome and Edge the next Tab started again at the top of the page. A press on the list with a mouse or a pen no longer moves the focus, in the combobox and in a multi-select with optimistic. A tap is left as it was, so the on-screen keyboard still closes over the choice.
  • The panel of a notification center and the popover of a filter builder close in Safari when Tab leaves them. Both close when the reader tabs past their last control or back past their first, and they found those ends in a list that counts buttons and links. Safari passes buttons and links by unless it is told otherwise, so Tab left the panel from an earlier control, and the panel stayed open over the page. A Tab that carries the focus out now closes the panel in every browser, and the focus goes on from the bell or the trigger.
  • Characters typed right after the command palette's hotkey reach its search. The palette's field takes the focus a moment after it opens, and keys typed in that moment were lost, or landed in the field of the page that had the focus. They now go to the palette's search, Backspace included.
  • Back returns to where you were after a link in an open drawer, modal or alert dialog leaves the page. While one of them is open the page is held still, and a plain link or form inside it left the page in that state, so the browser remembered the top of the page and Back went there. They close as the page is left now, which gives the position back first, and the command palette and the lightbox do the same. A link that opens a new tab leaves them open.
  • A range slider counts its steps from min under the pointer as well. On a slider from 5 with a step of 10, a drag set 10, 20 and 30 while the arrow keys set 5, 15 and 25, and the minimum itself could not be reached with a pointer. A drag and a click on the track now land on the grid the keys walk.
  • A range slider with a decimal step writes the value the step means. A step of 0.1 set 0.30000000000000004 where 0.3 was meant, by pointer and by keyboard alike, and that number showed in the badge above the handle, was announced and was submitted with the form. Values are now rounded to the decimal places of the slider's own numbers.
  • A resizable handle ends a drag where the pointer was released. A pointer move takes effect in the next screen frame, and a release that came before that frame dropped it: a drag lost its last stretch, and a quick one did not move the handle at all.
  • A card lifted in a sortable kanban column looks lifted. A card lifted with the keyboard to be moved looked exactly like a focused one, and a focused card wore the browser's own outline. A focused card now wears the focus ring, and a lifted card a dashed outline in the accent with a raised shadow.
  • A menubar, a navigation menu and a popover close when a press outside them ends, not when it begins. They closed as soon as the button went down beside them, so a reader who pressed by mistake and slid back onto the open menu lost it anyway (WCAG 2.2, 2.5.2). They now close on the release, and only when it happens outside too, as the browser's own popovers do; a touch that turns into a scroll closes nothing. The scope switcher, the overflow nav and the source popovers of the assistant message follow the popover. A click on another control still closes the popover and leaves the focus on that control.
  • A navigation menu panel opened from the keyboard stays open in Chrome. It could close itself a moment after Enter when the mouse pointer happened to rest near the bottom left of a short page, without the mouse being touched.
  • Every star of an interactive rating is at least 24 by 24 pixels. At size="sm" the stars were 16 pixel buttons 18 pixels apart, below the minimum target size of WCAG 2.2. Each small star is a 24 pixel button now, with the icon in its middle; the medium and large stars were that size already.
  • The "+N more" button of an event calendar month cell is 24 pixels tall. It was one line of small text 18 pixels tall, right under an event pill. A day cell that shows it grows by the 6 pixels the button needs.
  • A page header with actions and no description is as tall as its title. It took the height of its tallest button, so the content below started further down than under a header without actions. While the actions share the title's line they now sit centered on the title and add nothing to the header's height; once they wrap under it, the header lays them out as before.
  • A module of the icon-only app rail is read once. Its tooltip shows the module's name and also described the link with it, so a screen reader said the name twice. The tooltip no longer describes the link.
  • An invalid field stays red under the pointer, and an invalid checkbox or radio is red at all. The hover border of an input, a textarea, a select, a password input and a time picker outranked the error and success borders, so a field turned gray exactly while the reader pointed at it to correct it; it now belongs to the resting state alone. An invalid checkbox or radio kept its gray resting border even with the pointer away, because the error border lost to it; the box now takes one border for its state.
  • An invalid field shows a red focus ring. An input, a textarea, a select, a password input, a time picker and a number input in their error state drew the resting focus ring when they took the keyboard focus: each carried the resting ring color and the error one, and the stylesheet put the resting one last. Each state now names one ring color, and an input with a prefix or suffix draws the error and success rings on its frame as well. A field in its success state showed its green ring before, but only because of how the two colors happened to sort.
  • A text or a heading that keeps a user's line breaks starts at its first line. Their templates set the content on a line of its own, indented, so with whitespace-pre-line an empty line stood above the text, and with whitespace-pre-wrap its first line was indented as well. The content now sits directly between the tags.
  • An input, a select and a textarea line up in a row with align-fields on their own. The row's three tracks for labels, controls and messages were taken only by a field wrapped in field; an input with its own label, hint or error landed on the middle track as a whole, so a button beside it stood a label higher and moved when an error appeared. They now hand their label, control and message to the tracks themselves, with an empty placeholder where one is missing, and leave them to a field around them.
  • The distance below a horizontal tab bar is a knob. Set --wk-tabs-panel-gap to the distance between your cards, in your theme or on one tab set, and the first card sits as far from the bar as two cards from each other. Unset, the distance is --padding-wk-y-md, as before, and the panel block no longer has to be replaced for it, which also took the vertical layout with it.
  • A sidebar row and an app rail module can be the current location rather than the current page. current="location" on an active entry writes aria-current="location", for a row that leads the section the page lies in, and current="true" is there too; page stays the default. A screen reader no longer announces "current page" on an entry whose link leads elsewhere. The highlight is the same for every value, under forced colors as well.
  • Taking the last file out of a file upload empties the bound property. Livewire ignores a change that brings no file, so after the reader removed the last file with its remove button the earlier upload stayed on the property, and the next save stored a file the reader had taken out. The component now sets the property to null, or to an empty array on a multiple upload.
  • A message from a sender without a picture shows their initials. The avatar of a message, a message group and a typing indicator fell back to the same gray silhouette for everyone although it had the name, so every sender without a picture looked alike. It now shows the first letter of the first and of the last word of the name, in the color an avatar with from-initials gives those letters, and initials in the author record replaces them. A screen reader still hears the name.
  • A data list with layout="stacked" sets each label above its value, and layout="grid" goes to one column on a narrow screen, as the documentation says. A stacked item stood side by side like the default layout, so a long value became a right-aligned paragraph beside a label wrapping in a third of the row; its value now starts under the label, from the same edge. A grid kept its two columns on a phone; below a 40rem screen it now has one.
  • The clear and copy buttons of an input stand as deep in the frame as an icon in front of the value. They kept 10px to the frame's edge while an icon in the leading or trailing slot keeps 12px, so a field with a search icon in front and the clear button behind sat 2px off center. The buttons keep 12px now, and two of them stand 12px apart.
  • A button with surface="link" sits flush with its sentence. It kept the horizontal padding of its size, 12px on each side at the default size, beside the link's own p-0, because the padding of one side outranks the padding of all four. A link button now takes neither the size's padding nor its fixed height, and keeps its text size.
  • A popover with padded="false" has no padding. The panel kept its 12px, so did the panel of every scope switcher, which asks for an unpadded one. The padding now comes out of the panel's classes, also out of a block you personalized.
  • An icon sized with a class takes that size. A height or width utility in an icon's class now replaces the icon's own size on that axis. Before, class="h-4 w-4" beside the default h-5 w-5 rendered at 20px, while a larger utility won only by sorting later. The size prop stays the way to size an icon. The icons in tabs, the theme controller and the scope switcher are 16px, as written.
  • The avatar placeholder of a skeleton is round. skeleton.avatar drew a square with rounded corners, because the radius of its text lines outranked the circle's.
  • A strip announcement banner is as tall as --wk-strip-height. The banner's vertical padding stayed beside the strip's py-0 and made it 32px tall against a 27.5px token at the default text size, while every sticky bar below a strip is placed at the token's height.
  • The create action of a scope switcher is in the accent color, which sets it apart from the rows above it. The text color of the rows outranked it.
  • A number input takes a width from class. A width utility on a number input now replaces its own w-16. A narrower one, such as w-12, lost to it before.
  • The knob of a switch stands out from its track when it is off. The neutral track of a toggle and of the theme controller's switch was filled with --color-wk-border, and the knob, which is what shows whether the switch is on, stood at 1.3:1 against it in light mode and 1.42:1 in dark. The track is filled with --color-wk-border-strong now: the knob reaches 3:1 against it and the track 3:1 against the page in both modes, as WCAG 1.4.11 asks of a state indicator. A toggle with on-intent="neutral" takes the same fill when on.
  • A counter on the current module of an app rail with tone="accent" stays visible. On the accent tone the current module's pill and the fill under the pointer are --color-wk-accent-fg, and so was the counter, so a counter on either sat in its own color and disappeared. A counter on a filled item now takes --color-wk-rail-badge-on-fill, with its digits in --color-wk-rail-badge-fg-on-fill: on the accent tone they invert with the label, and on every other tone they repeat the counter the column shows. Set them like the other rail roles to give that counter a color of your own.
  • The Livewire simple pager of the pagination works with a cursor paginator. A component whose paginationSimpleView() returns wirekit::pagination.livewire-simple ended its request with a stack overflow at the first links() of a cursorPaginate() result, and so did a <x-wirekit::pagination :paginator> bound to a cursor paginator inside such a component: Blade escapes a bound paginator by rendering it through its default view, and Livewire had made that view this pager. The pager now renders once.
  • The page-size choice of a pagination outside Livewire waits for its Apply button. Picking a size sent the form at once, and on Windows and Linux the arrow keys change a closed select's value, so every key loaded the list again and put the reader at the top of a new page. The form shows its Apply button now and sends from it alone; bound with wire:model, the choice works as before.
  • The label of a field names the control inside it, and a click on it reaches that control. The label pointed its for at the field's name, the id only a plain input carries, while the combobox, the multi-select, the tags input, the date picker, the file upload, the color picker, the slider, the range slider, the editor and the inline edit give their input an id of their own. So the visible label named nothing and a click on it did nothing: a combobox in a field had no name at all, and a multi-select or a tags input was announced by its placeholder. Each of them now takes the field's label as its name, through for or, where there is no input a label can name, by reference. A control with a label or an aria-label of its own keeps that name, and a for you pass to the field keeps your wiring. A click on the label of a range slider, its own or a field's, focuses the first thumb.
  • The first box of an OTP input carries the words of its label. The label points at the first box, and that box was named "Digit 1 of 6", so no control answered to the label's words and a reader who uses speech input reached nothing by saying them. With a label, the first box is "Verification Code, digit 1 of 6" now; the other boxes, and a field without a label, keep their names.
  • Every tab stop the kit renders draws the kit's focus ring. A link component, a brand, a reaction, a tooltip trigger that is a stop of its own, a scroll area, a tab panel, the copy button of a code block, the links in prose, a card that links or is a button, an attachment that is a link and the link cells of a data table showed only the browser's own ring. That ring follows neither the ring tokens nor a preset, and in Safari it stands at about 2:1 on a light surface.
  • Buttons and fields show their focus ring under the minimal, retro-terminal and brutalist presets. Those presets set shadow tokens to none, and a button or a field writes its shadow and its focus ring into one box-shadow, where none is valid only on its own: the whole declaration was dropped, and the element drew nothing when it took the keyboard focus. The preset pages, php artisan wirekit:theme and the first example on the theming page say 0 0 0 0 transparent now, which looks the same. If you copied one of those blocks, change none to 0 0 0 0 transparent in --shadow-wk-sm, --shadow-wk-md, --shadow-wk-lg and --shadow-wk-none.
  • A filled surface points the focus ring at its foreground. On the accent and status fills of an announcement banner, a CTA and a hero, the ring had the color of the fill in the default preset, so the banner's dismiss button showed no ring at all. These surfaces now draw the ring in their foreground color, without an offset band.
  • Focus rings read their width and offset from the tokens everywhere. The dismiss button of an announcement banner, a row of an attachment group, a conversation, a skip link and the floating action button drew a 2px ring at a 2px offset whatever the preset said.
  • The focus ring of the image compare handle stands out on any picture. It is drawn in two bands now, the page's color beside the handle and the ring outside it, where a ring of one color vanished on a picture of that color.
  • The code inside a code block shows its focus ring. The block's frame clips to its rounded border, and the code it holds, which scrolls and is a stop for the Tab key, drew its ring outside its own edges, where the frame cut it away: in Safari nothing on screen changed when the keyboard reached the code, in Chrome and Edge a few pixels at its corners did, and under a filename or a copy button a thin line was all that was left. The ring is drawn inside the code's edges now and shows on all four sides, in a tool call, in prose and in a code mockup as well.
  • The rows of a sidebar that scrolls and the links in a shell bar's strip show their whole focus ring. The sidebar's scrolling list and the shell bar's strip clip what overflows them, and the rows and links filled them edge to edge: a focused row showed a line above and below it and nothing at its sides, and a link in the strip showed two brackets around its text. Both leave room for the ring now, as much as a button's ring reaches with its offset, without moving anything, and a row the keyboard scrolls into view stops short of the edge, so its ring stays in view. A shell bar with padding="none" has no padding to lend its strip, so there the room is above and below the links only, and in the foot of a folded sidebar the room stays inside the strip, which makes the control in it narrower by that much. This holds for a sidebar with a header or a footer, the sidebar of an app shell, and whatever you put in those slots.
  • The field of a number input shows its whole focus ring. The increase button sits flush against the field and was drawn over the right side of its ring, which read as a bracket around the number. The focused field now rises above both buttons.
  • The first card of a sortable kanban column shows its focus outline at the top. The column's card list scrolls and clips, and its first card sat flush with the top, so the outline a focused card wears, a little apart from the card, lost its top edge. The list leaves room for it now without moving the cards.
  • A kanban column and a conversation keep the focus ring of what they hold whole. The board cut the ring of its first column, and a conversation cut the ring of a control the focus scrolled to its edge, and its own ring in a pane that clips.
  • A focused tab shows its whole focus ring. A row of tabs scrolls sideways when it is too wide for its column, and the bar clips what reaches past it: the ring of a focused tab, drawn around the tab, lost its top and bottom, and on the first tab its start side as well, which left a single vertical line beside the label. The ring is drawn inside the tab now, in every variant and in a bar built from tabs.list and tabs.tab, and on the filled selected tab of the bordered bar in the color of its label, so it stands out from the fill.
  • The selected tab of an underline tabs bar shows its underline. Its 3 pixel edge in the accent color lost to the transparent edge every tab carries, so the selected tab differed from the others in its text color alone. Horizontal and vertical bars both draw it again; the pills and bordered variants are unchanged.
  • A hover card opened from the keyboard leaves its trigger's focus ring in view. The card was placed against the line around its trigger rather than the button in it, and opened less than a pixel below the button, over the lower edge of the button's focus ring. It keeps its full distance from the button now, below it and above it.
  • The scroll region of a table shows its whole focus ring. The region drew its ring inside its edges and beneath its own cells, so the header row covered the ring's top edge on every table, a striped or footer row its bottom edge, a frozen first column its left side, and a sticky header kept the top covered while the rows scrolled. The ring is drawn above the cells now, on all four sides, while the region has the keyboard focus.
  • A context menu trigger that the keyboard reaches shows its whole focus indication, whatever it wraps. When the trigger holds no control of its own, its wrapper takes the focus, and its ring was cut off at both sides when the wrapper filled a table with rounded, clipped corners, as the example with table rows does. The wrapper marks the focus with an outline set just inside its edges now, which stays visible in a clipped table and above content that has a background of its own.
  • A tooltip around a button, a link or a field is no second tab stop. The tooltip made its trigger a stop by default, so around a control the reader met an unnamed stop before the control itself. focusable-trigger now reads the slot: around a control the control is the stop, around an icon or a word the trigger still is. true and false keep their meaning. Around a control the control carries the tooltip's description, and keeps it when a Livewire update renders the tooltip again.
  • The list of a combobox, a multi-select, a scope switcher and a command palette is no tab stop of its own in Chrome. Chrome makes a list that scrolls a stop, so Tab from a field that was the last stop on the page could land on a list that was closing.
  • A chart takes no tab stop inside its hidden drawing. ApexCharts 7 navigates a chart's data points with the keyboard, and in Chrome its drawing stayed a tab stop inside the part hidden from screen readers. The component leaves that navigation switched off.
  • A carousel's Tab order is Previous, slides, Next. Both arrows came after the slides, so Tab went to the slides and then back to the arrow on their left. The arrows stand where they stood.
  • A refracting glass surface turns opaque for a reader who asked for no transparency. Under reduced transparency, forced colors and in print, wk-glass-refract stayed transparent with its displacement layer in front of it wherever the extension's script had prepared the page for refraction, because the refraction's own rule outweighed the reader settings. Each setting now reaches it.
  • A name given to a component reaches assistive technology as written. Many components took their aria-label out of the attributes and escaped it a second time, so a name with an apostrophe, an ampersand or a quote, bound from your data or written with entities, was read as Tom&#039;s orders &amp; returns: among them the chart, the editor, the drawer, the alert dialog, the progress bar, the rating, the tags input, the file upload and the two thumbs of a range slider. Each reads the name as the text it stands for now.
  • A radio and a checkbox put a lang on their label. It went on the field, while the words sat beside it in the label, so a label in another language than the page was read in the page's voice.
  • wire:model.live.blur sends when the reader leaves, and wire:model.live.change when they choose, on the combobox, the multi-select, the tags input and the branch switcher. These four bind on their outer element, which never received a blur or a change, so a choice bound with .live.blur was sent only with the next request, and an updated hook meant to validate on leaving never ran.
  • The list of a combobox is named after its label. It carried an empty aria-label unless you passed one, so a combobox with a visible label opened a list without a name. It takes its name from the label now, or from the field around it, or from your aria-label.
  • The country picker of a phone field has a name of its own. It took the field's label, so a screen reader heard "Phone number" for the country and again for the number, and a field without a label left the picker unnamed. It is "Phone number, country" now, or "Country" without a label, translated in every language file the kit ships.
  • An attachment announces a change of its upload state. When your component moves state to done or error in a Livewire update, a screen reader now says "Uploaded" or "Upload failed" without the reader moving to the card.
  • An open notification center stays open when a notification arrives from the server. Its list was part of the expression the notification center starts from, so every render with a new notification, such as one a poll brings, reset it to that expression and closed the panel the reader was looking at. The list now travels on an attribute of its own, and the panel keeps its state while the new notification appears in it.
  • An event calendar stays on the week or month the reader moved to when the server changes its events. The events, the day markers, the view and the date were part of the expression the event calendar starts from, so a render with a changed event, such as one saved from a modal, reset it to that expression and sent the reader back to the week it opened on. All four now travel on attributes of their own: new events appear where the reader is, and a view or a date the server changes still moves the calendar.
  • A component that Livewire renders again with new values stops repeating itself. The watchers a component sets up when it starts piled up with every such render, so an opening of a combobox announced itself once more after every answer from the server. Only the watchers of the latest start act now.
  • An open dropdown, popover, menubar menu or color picker stays at its trigger when Livewire updates the page. An update removed the panel's placement while it stayed open, and nothing put it back: measured on a refresh, a dropdown moved from 8px below its trigger to 144px, a menubar menu to the end of the page and a color picker 1773px down. A popover that holds only text also dropped the focus to the page. All four keep their place now, and the popover keeps the focus.
  • Enter in a combobox chooses only from an open list. On a closed list it chose the active option anyway: the first option in a field nobody had touched, or the suggestion a reader had just dismissed with Escape, neither of them seen. Enter now opens a closed list at the current choice and chooses nothing, as the select-only combobox and the multi-select already did. With optimistic, nothing is sent until a choice is made, and a choice made with Enter closes the list, as a click does. The country list of the phone field behaves the same way.
  • A field whose id carries a dot, a colon or brackets is no longer talked over when an optimistic save fails. The optimistic layer stays quiet wherever the field's own error message is already being announced, and it found that message through a selector written from the field's id as it stood. A field named form.email, the name a Livewire form object's messages carry, gets the id form.email, and #form.email-error reads as an id and a class and matches nothing, so the generic "Could not save" was announced over the field's own message. On a date picker named with brackets the selector was invalid, and the failure stopped before anything was announced. The selector now escapes the id the way the browser's CSS.escape() does.
  • A menubar labeled in Chinese, Japanese, Korean, Hebrew or Thai opens one menu at a time, and a scope switcher's groups named in those scripts each carry their own name. Both took an identity from a slug of the text, and such text slugs to nothing: every menu of a menubar shared one name, so a click opened them all, and every group of a scope switcher shared one id, so each group was announced with the first group's name. Text without a slug, symbols included, is counted now.
  • A short event in the event calendar's week grid shows its time in the tip once enlarged text spacing pushes the time past the block's edge. With a larger line height or letter spacing, the time under a short event's title ran past the bottom of its block, and a time with a descender, such as "9:00 p.m." in Mexican Spanish or "9:00 μ.μ." in Greek, lost the descender. The tip that names a cut-off title now also opens for such a block, and it carries the time with the title.
  • The menu of the theme controller is named after what it does and the mode it shows. Its trigger shows the current mode, such as "System", and was named "Dark mode" alone: speech input could not reach it by the word on it, and a screen reader never heard the mode. It is named "Dark mode, System" now, after the control's label and the mode.
  • Links and other accent text on a callout or an alert reach 4.5:1 in the dark mode of the Cupertino preset. Callouts and alerts tint their surface with a color, and on those tints Cupertino's dark accent text, blue-400, measured as low as 3.82:1. It is blue-300 now, 6:1 on the accent callout. If you copied the preset into your stylesheet, change --color-wk-accent-content in its .dark block to var(--color-blue-300). The guaranteed contrast pairs now include accent text on these surfaces.
  • A chart's aria-label follows a Livewire update. Since 2.56.0 a chart takes the new labels and series of a Livewire update, and its name stayed the one it first rendered with: a name that summarizes the data, as the chart page asks for, went on describing the period the page loaded with while the chart showed another. The name now travels with the data and changes with it.
  • The tooltip of an ApexCharts chart reads enabledOnSeries, inverseOrder and hideEmptySeries, and the value and name formatters set for every chart. With library="apexcharts", the tooltip WireKit draws for a chart listed every series in their order and wrote each value as it came, whatever these options said: a tooltip limited to one series listed both, inverseOrder changed nothing, and a value of zero kept its row under hideEmptySeries. A tooltip.y.formatter or a yaxis.labels.formatter set through window.Apex wrote the values of ApexCharts' own tooltip and not of this one, and a tooltip.y.title.formatter the names in front of them. It reads all six now, as ApexCharts does, and writes what a formatter returns as text. valueDecimals, valuePrefix and valueSuffix come first where a chart sets them.
  • A scope switcher row whose address is only a fragment, such as #, no longer reloads the page. Livewire turns every same-origin link into a navigation, so choosing such a row fetched the page again and left the reader at its top without the focus. A fragment stays with the browser now, in the rows and in the footer action; a real address still navigates with wire:navigate.
  • A map draws with a library that lands on window after it has rendered. A map looked for MapLibre or Leaflet when it started and afterwards waited only for registerMapEngine(), so a library whose script ran later, as one can on a page reached through wire:navigate, was never used: the marker list stayed, the canvas did not come, and the console said no library had been found. A map now keeps looking for its library the way a chart does and draws the moment it arrives, and the console hint no longer speaks for a library that is there.
  • The calendar button of a wide date field sits at the end of the field in Chrome and Edge. In a date, time or date-and-time field wider than its value, as a grid column or a full-width form gives it, the button stood right behind the value and the rest of the field was empty. The field laid its parts out in a row, and Chrome and Edge shrink the box that holds the value and the button to their width in a row. The field is a grid now, which stretches that box across it. Safari draws no button inside the field, and its value sits where it did.
  • The wirekit:doctor:a11y linter reads a view whose @php blocks share one statement. Blade compiles every block of a view into one file, so a try may open in one block and close in the next. The linter checked each block as PHP of its own, stopped on such a view with a parse error and reported nothing for the whole directory it was given. It reads those views now, and still skips the comments inside them.
  • An editor mounts a factory that arrives after it has started. An application that loads its editor only on the pages that have one defines window.wirekitEditor from a script that, after a wire:navigate visit, runs after Alpine has started the editor. The editor looked once and stayed a plain textarea with a console error. It waits now the way the charts and the map wait for their library: the content the server rendered stays until the factory arrives, the textarea and the error come a few seconds after the page has loaded if it does not, and a factory that arrives even later still takes over a textarea nobody has typed into.
  • A read-only editor without a factory keeps showing its document. With :editable="false" there is no form field to fall back to, and the fallback hid the content the server had rendered anyway, so the reader saw an empty box. The content stays now, and the console still reports the missing factory.
  • Escape folds a reading spine that hover or focus opened. The expanded list of a reading spine lies over the text beside it, and it could only be closed by moving the pointer or the focus away. Escape folds it now with both left where they are, and it stays folded until they leave and come back. A spine the page keeps open lets the key pass.
  • A command palette with a remote source no longer says "nothing found" before it has searched. The query goes out 300 ms after the last keystroke, and until your host answered, the empty state showed under the results of an older input and its status region read it out after every keystroke. A palette with a loading slot, or one whose host has reported loading once, now keeps the empty state for the answer to the newest input. A palette without a remote source, and a host that only reports failures, behave as before.
  • The label of an inline edit with your own control names it. With the editor slot the label pointed its for at an element the slot never renders, so it named nothing and a click on it did nothing. It now names the part of your control that takes focus when the editor opens, unless that part names itself, and a click on it focuses the control while the editor is open.
  • A click on the label of an open inline edit keeps what was typed in Safari. WebKit took the focus from the field when its label was pressed, and the editor read that as leaving: it closed and dropped the draft, in Safari and in every browser on an iPhone. The editor stays open now, and the click puts the focus in the field.
  • A click on the label of an editor focuses it. The label pointed its for at an element that cannot take a label, so the click did nothing. It focuses the editor now, and a click while you are writing leaves the focus where it is.

Documentation

  • The scroll area page says how much room a control at its edge needs for its focus ring. The area clips whatever reaches past its edges, and a focus ring reaches a little past the control it surrounds, so a button flush against the edge loses that side of its ring. The page header example with a header resting at the top of a scroll area gives its button that room now, on the sides only, so the header still rests at the top.
  • Every tab stop an example writes shows the kit's focus ring. The scroll regions of the toolbar and context menu examples use a scroll area, the consent links of the checkbox and the sign-in link of the brand bar use the link component, and the examples' own links, buttons and scroll regions draw it with these classes.
  • The images in the examples carry their width and height. The first example on the image page showed an image whose place was only known once it had loaded, and a copy of it would have done the same; the page says now that the browser reserves the box from the two values before the file arrives. The logo cloud and card examples and the logo cloud and marketing landing page blueprints carry them too.
  • The range slider shows how to bind a stored range with wire:model. Its example bound wire:model alone, which starts the thumbs at the ends of the scale, follows no range the server sets, and replaces the stored range on the first move. It now passes the range to min-value and max-value as well and says why.
  • The slider page says what wire:model.live sends during a drag. It said a drag fires a change on every step and sends a request per step. A drag fires input on every step and one change at release, and Livewire sends a .live field once its input pauses: a quick drag sends once, a drag that stops along the way sends at each stop. The page now points to wire:model.change for sending only at release.
  • The phone field, calendar and segmented control pages pass the bound value as :value beside wire:model, as the rating and color picker pages already did. These components keep their value in their own state, seeded from value: bound without it they show their default instead of a stored value and do not follow a value the server sets. The pages now say so, and the pricing table's example binds the same way.
  • The aspect-ratio page tells you to give a column that holds a box min-width: 0. In a row or a grid of columns, Safari and every other browser on iOS keep such a column at the width it had before the window narrowed, so turning a phone upright left the columns of the page's own examples wider than the screen. The examples carry min-width: 0 now, and the page says when a layout of your own needs it. Columns from the grid's cols prop need nothing. A column with a caption under its box gets a floor that carries the caption instead, min-width: 8rem in the example, so the caption no longer runs past a phone's screen.
  • Two further-reading links point at pages that exist again. The one-time-code input linked the Web OTP page Chrome has moved, and the icon page linked an Authoring Practices pattern that does not exist; it now links the img role in WAI-ARIA 1.2.
  • The label page no longer lists --font-wk-body-weight as the label's weight. A label is medium weight, and the token never reached it: the weight beside it decided. The page and the component now say the same.
  • The multi-select page describes its open list as it is. It said a chosen option shows a check and the selected weight in the list, while a chosen option leaves the list and shows as a pill in the field, as it always has; every row of the list says aria-selected="false". The mark is gone from the markup, and with it the option-selected and option-check class blocks and the empty column each row kept for the check, so a long option label has that room now. --font-wk-selected-weight no longer names the multi-select among its readers.
  • The word inside a sentence that opens a hover card keeps its focus outline. The example reset its button with all: unset, which also took away the outline a keyboard user finds it by. It now resets the background, the border, the padding and the margin by name, and sits in the sentence as before.
  • Three examples work for every reader. The tabs example with a purple accent reads in dark mode, the reveal example with a click trigger plays by keyboard, and the stacked hero example keeps its heading outline.

Components

Every component this release changes, each linked to its page: Accordion · Action Bar · Alert · Alert Dialog · Announcement Banner · App Rail · App Shell · Assistant Message · Attachment · Avatar · Badge · Bottom Nav · Branch Switcher · Brand · Brand Bar · Button · Calendar · Callout · Card · Carousel · Chart · Checkbox · Clipboard Button · Code Block · Collapsible · Color Picker · Combobox · Command Palette · Context Menu · Conversation · Countdown · CTA · Data List · Data Table · Date Picker · Drawer · Dropdown · Editor · Empty State · Event Calendar · FAB · Feature · Field · File Upload · Filter Builder · Flag · Header · Heading · Hero · Hover Card · Icon · Image · Image Compare · Inline Edit · Input · Kanban · Label · Lightbox · Link component · Liquid Glass · Map · Menubar · Message · Message Group · Message Typing · Modal · Multi-Select · Navbar · Navigation Menu · Notification Center · Number Input · OTP Input · Overflow Nav · Page Header · Page Progress · Pagination · Password Input · Phone · Popover · Pricing Table · Product Card · Progress · Progress Radial · Prose · QR Code · Radio · Range Slider · Rating · Reaction · Reading · Resizable · Scope Switcher · Scroll Area · Segmented Control · Select · Shell Bar · Sidebar · Skeleton · Skip Link · Slider · Spinner · Stage Card · Stat · Status Matrix · Status Tiles · Step Marker · Sticky Panel · Strength Meter · Table · Tabs · Tags Input · Team Section · Testimonial · Text · Textarea · Theme Controller · Time Picker · Toast · Toggle · Toggle Button · Toolbar · Tooltip · Tour · Unsaved Hint