Skip to main content
WireKit

WireKit v2.51.0

Minor release

Minor. An assistant's turn can show its work: a tool call has a component of its own, the variants a regeneration produced get a switcher, an answer names the sources it relied on, and the reasoning disclosure stays open while the model is still writing. The combobox becomes a picker that shows icons, avatars and descriptions, runs without search when a short list does not need it, and opens a panel as wide as its options. A light subtree can sit inside a dark page, and a dark panel on a light page now resolves every color WireKit derives rather than the ones computed for the page around it. And a field set can say what is wrong with its whole group, so a checkbox group's error no longer has to be wired by hand.

Added

  • A field set shows an error for its whole group, and the controls inside point at it. Pass the message as error, or give the set the name your validation uses: it reads the error bag for that key and for every entry below it, so a checkbox group bound to roles shows the error Laravel files under roles.1. The message renders once, in the hint's place, as a polite live region that follows announce-error and the surrounding form's announce-errors. Every checkbox, radio and toggle in the set carries aria-invalid and points at the message through aria-describedby, next to its own hint or error, and points at the set's hint while nothing is wrong. Radios that share the set's name no longer repeat the message under every option. A view compiled before this release keeps rendering the set the old way until its compiled views are cleared.
  • The component sandbox offers more of each component's props. A badge's surface, dismiss button and wrapping; a callout's icon, border and stripe; an alert's icon; a code block's copy button; a keyboard key's size; a heading's size, accent and tracking; a text's weight, alignment, truncation, line clamp and word breaking; a text link's variant, size, underline and external marker; and a button's label wrapping. It still leaves out what an open endpoint should not take, such as URLs, free text, icon names and personalization scopes.
  • A subtree can be light inside a dark page: put light on it. What is below the element resolves the light values again, the browser's own controls included, while the page around it stays dark. The class declares only the tokens that change with the mode, so a radius, a font or a spacing value you override in your own :root reaches into the subtree unchanged, and a reader's request for more contrast reaches it too. The mode-only utilities wk-light-only and wk-dark-only follow the island as well, and so do your own dark: utilities once your @custom-variant dark line is the one the integration guide shows. A light island in a dark page shows the markup, and the one kind of override that needs a second selector.
  • WcagContrast::conformance() tells you whether a contrast ratio meets WCAG AA or AAA for body text, large text or a UI component, the way a page labels a color pairing. UI components top out at AA, because WCAG defines no AAA level for them. WcagContrast::parseToLinearRgba() is available alongside it and returns the alpha channel with the color, including for the forms a browser returns from getComputedStyleoklab(…) and color(srgb …) — and WcagContrast::unmeasurableReason() tells you why a pair has no ratio.
  • The data table can freeze its first column while the rest scrolls. sticky-column holds the column that names the row — usually a name or a date — against the start edge, so a wide table stays readable once it has been pushed sideways. With selectable the checkbox freezes with it, because a checkbox that scrolls out from under its own row is worse than one that never moved. The frozen column is capped at --size-wk-table-sticky-column-max and wraps rather than taking the screen: on a phone an ordinary company name would otherwise claim two fifths of it, and every further character comes out of the columns you froze it to be able to read. It is positioned logically, so under dir="rtl" the column holds the right edge without a second rule, and hiding it through the column menu moves the freeze to whatever is first now. Off by default, and switchable for the whole application through config.
  • <x-wirekit::branch-switcher> steps between several generated answers. When a model regenerates, the earlier answer is not waste — it is the other option. Two arrows, a count, and an announcement that carries the whole sentence ("Showing response 2 of 3") into a polite region that exists before it has anything to say, because a region created together with its text is inert and a fragment appended to an existing one tells a screen reader's user nothing. The index is 1-based everywhere: in the label, in what wire:model hands the server, and in the current prop you write. It is a control rather than a container — you render the active variant, the switcher owns the index. The ends do not wrap unless you ask for loop, because a reader stepping through variants expects the last one to be the last one. and work while either arrow has focus, and an index whose variant no longer exists is clamped rather than refused, since a regenerate that drops one while the reader is on it is an ordinary race.
  • <x-wirekit::tool-call> shows one tool invocation an assistant made — the name, where the call stands, the arguments as readable JSON, and the result behind a disclosure. It composes primitives you already have rather than inventing a visual language: code for the name, badge and shimmer for the state, code-block for the arguments, collapsible for the result. A running call marks its block aria-busy and shimmers its label; a failed one opens its disclosure, because an error nobody sees is not reported. The state is in the badge's text as well as its color, so a reader who cannot tell the intents apart can still tell a finished call from a failed one. seconds replaces the state word with how long the call took — the application owns that number, and a value that is not one is ignored rather than rendered as "Took 0 s".
  • An assistant message shows the sources an answer cites, as numbered chips. Pass citations whatever your retrieval layer produced: titles, arrays with a label, an href and a snippet, or your own models. Each chip opens a popover with the passage the answer relied on and, where there is one, a link to it. An entry without a label is left out rather than drawn as a chip nothing names, the numbering follows what is rendered, and each chip carries its source in its accessible name, so a screen reader announces "Source 1, Rate limits" rather than a bare digit.
  • The reasoning disclosure of an assistant message follows the stream. It stayed collapsed, so a reader watching an answer being written saw no sign that the model was reasoning. While the message streams, the disclosure is open and its label shimmers with the incoming text; it closes when the stream ends. Pass reasoning-seconds and the closed label reads "Thought for 12 s". Your application owns that number, and a value that is not a number shows no duration rather than "Thought for 0 s".
  • ComponentRegistry::acceptedPropNames(string $name) returns every name a component's tag accepts — its @props and its @aware keys together. Use it when the question is whether Blade would do anything with an attribute, which is what a linter, an editor completion or an unknown-prop warning is really asking. It is deliberately a different answer from extractProps(): @aware resolves against the rendering component's own data before it walks up to the parent, so <x-wirekit::accordion.item variant="separated"> takes effect even though variant is not in that component's @props.
  • Every component in the manifest now lists the design tokens it reads. wirekit:export-json, the .wirekit-schema.json feed, the MCP catalog and wirekit:show --as=json carry a sorted tokens array per component, and each entry in sub_components carries its own: the custom properties declared in dist/wirekit.css that its template, the partials it includes, the helpers it calls and the stylesheet rules on the classes it renders read. It is derived from the component on every export, so it cannot fall behind the markup. ComponentRegistry::tokensOf(string $name) returns the same list.
  • The component manifest names the values each prop accepts and the type it takes. Every prop record in the manifest wirekit:export-json writes carries values, the list the component validates, and value_type, what the template proves the value to be, so a props playground or an assistant can offer a fitting control instead of reading the prose. A prop that answers to two names lists its values under each name in that name's own spelling: a card lists outline under surface and outlined under variant. ComponentRegistry::extractProps() describes the record.
  • Pagination turns the page inside a Livewire component. Every page control was a plain link, so in a Livewire component a click reloaded the page and lost whatever the component held outside the URL, such as a filter. With livewire, each control stays a link, and a click calls the component's gotoPage() for the paginator's page name, then scrolls back to the top the way Livewire's own pager does; scroll-to picks another target or turns it off. A component can also return wirekit::pagination.livewire from paginationView() and wirekit::pagination.livewire-simple from paginationSimpleView(), so $paginator->links() renders this pager too.
  • The theme script can load as a file, for a Content Security Policy that allows no inline script. @wirekitThemeScript wrote an inline script and nothing else, so under script-src 'self' without nonces the browser refused it, and the page painted light until the theme controller turned it dark. Set wirekit.theme.script to external and the same script loads from your own origin as wirekit-theme.js, still before the first paint. It ships in dist/ and publishes with the other assets.
  • A clickable card can show at rest that it leads somewhere. The pointer and the hover shadow were the only signs of a card with href, and a phone shows neither, so a clickable card looked like one with nothing behind it. With chevron, a card that links or runs an action carries a chevron at its inline end, mirrored in right-to-left pages and hidden from screen readers. It is opt-in, and a card that does neither never draws one.
  • Prose can size its type to its own column rather than to the viewport. A media query cannot see a column, so prose in a narrow sidebar on a wide screen kept the heading scale built for a whole page. With container, the wrapper becomes a size container and its scale tightens once the column is narrower than 30rem. It is opt-in because a size container no longer takes its width from its content, so prose inside a parent that shrinks to fit its content would collapse; the page names those parents.
  • The browser's find in page opens what it finds. A match inside a closed accordion item or a collapsed collapsible opens it, and with findable an inactive tabs panel does too. In a browser that cannot search hidden content, the panels stay hidden exactly as before.
  • A toggle button takes an icon, an icon per state, a tooltip, a color for its pressed state, a label per state and wire:model. icon shows one glyph in both states, and on-icon with off-icon swaps two. tooltip names an icon-only toggle, active-intent colors the pressed icon from the canonical intents, and on-label with off-label changes the visible text instead of announcing a pressed state. An icon-only toggle with no visible label, tooltip or aria-label throws in debug mode and is logged otherwise.
  • A tooltip can name its trigger without also describing it. describes links the tooltip to its trigger through aria-describedby and stays on by default. Set it to false when the tooltip repeats the trigger's accessible name, such as the aria-label of an icon-only button, so a screen reader does not announce the same words twice.
  • A combobox can run without search. :searchable="false" renders a select-only combobox whose keyboard follows the WAI-ARIA select-only pattern: the arrow keys, Home, End, PageUp and PageDown move through the options, Enter, Space and Tab choose one, Escape keeps the current choice, disabled options are skipped, and typing jumps to a matching option. It keeps the option model, the hidden input and the Livewire binding of the searchable combobox.
  • Combobox and multi-select options can carry an icon, an image or an avatar, a description, search keywords and a shorter label for the chosen value. An array option reads icon, image, avatar with initials, description, keywords and selectedLabel, one medium per option, and a list of plain options renders exactly as before.
  • Combobox and multi-select take placement and panel-width for their options panel. The panel always opened below the field at exactly its width, so a compact field with long options had no readable panel. placement accepts the placements a dropdown accepts, and panel-width takes trigger, the default, auto for the width of the widest option, or a CSS length. The panel never gets narrower than the field or wider than the room its placement leaves, and both props have application-wide defaults in config.

Changed

  • Prose with preset="reading" sets long-form text in a serif. The preset used to open up the leading only. It now uses --font-wk-serif: the family you name in wirekit.fonts.serif, or the reader's own platform serif when you name none, so no font request leaves the page, and the reading measure counts characters of that serif. If you use the preset and want its leading without the serif, the page shows the one rule that keeps it.

Fixed

  • A collapsible, combobox or navigation menu stops animating its chevron and panel for a reader who asked for less motion. Their moving parts sat outside WireKit's reduced-motion rule, so a chevron still turned and a collapsible still slid open. Each root now carries a marker class, wk-collapsible, wk-combobox and wk-navigation-menu, listed in the public CSS API; keep it if you re-template one of them.
  • The component sandbox offers every intent a badge and every size a button accepts. accent and md-compact worked on the components and could not be picked in the sandbox's prop editor.
  • A select whose chosen value is longer than the field no longer makes the page scroll sideways in Safari. Safari and every browser on iPhone and iPad counted the cut-off rest of the value into the width of the page, so a phone could scroll sideways over nothing you could see.
  • A modal or alert dialog trigger can take the layout of the container it sits in. The trigger slot sat in a box of its own, so a button there kept its natural width in a stack that stretched its neighbors, and no class on the component could change that. Add class="contents" to the component and the trigger follows the surrounding layout. A page that does not add it looks exactly as before.
  • A table or data table wider than the screen no longer lets a visually hidden label stretch the page. A label such as the hidden heading of an actions column is positioned absolutely, and the table's scroller was not its containing block, so on a phone the label sat outside the scroller and widened everything around the table. The scroller contains it now.
  • A page that declines more contrast with data-contrast="no-preference" keeps that choice inside a .dark panel. When the operating system asked for more contrast, a dark panel on such a page still raised its hint text and borders, while the page around it rightly did not. The refusal on <html> now reaches every panel that switches mode.
  • A .dark panel on a light page resolves the colors WireKit derives from its tokens against itself. Soft alert text, inline code, tooltips, the reading components and the navigation rail kept the colors computed for the light page, near-black text on a dark panel and a white rail inside it, because a CSS custom property is computed where it is declared and those were declared for the page alone. They are declared for the dark class as well now, and a dark panel inside a rail switches too. What the class carries besides tokens has the placement that still matters for the page.
  • A segmented control no longer animates its segments for a reader who asked for less motion. Its segments carry a transition that WireKit's reduced-motion rule only reached when something around the control happened to opt in; the control now opts in itself.
  • wirekit:doctor:a11y --theme-contrast no longer overstates the contrast of a translucent color. A half-transparent text color was measured as if it were solid, which can report a pass the reader never sees. It is now laid over its background first, as a browser draws it, and a translucent background is skipped with that reason instead of being measured as solid.
  • A segmented control whose options scroll now says so. On a narrow screen the bar scrolls on its own, and with a later option selected it opened scrolled to that option: the first options hidden and the next one cut mid-word, with nothing on screen saying more lay before them. A soft shadow now marks the edge the options continue past, and it follows the scroll. It is positioned logically, so a right-to-left page gets it on the side its options continue toward.
  • A table with sticky-column caps its frozen column and holds it against the start edge in right-to-left pages. A table with a frozen column takes its content's width so that it can scroll, which kept every cell on one line: at phone width a comparison's frozen column took 334 of 369px. The cells of that column are now capped at --size-wk-table-sticky-column-max, min(40vw, 16rem) by default, and its header may wrap. The column was also pinned to the left edge, which under dir="rtl" is the far end of the row; it follows the start edge now, and left-to-right pages render as before.
  • wirekit:doctor:props no longer reports an @aware key written on a tag as an unknown prop. It asked for the declared props alone, so a correct call read as a typo while the runtime warning over the same render stayed quiet.
  • wirekit:doctor:a11y no longer reads markup quoted in a comment as markup. A comment explaining why a role="img" carries a name was reported as a role="img" without one. Comments are blanked before the rules run — in place, so every finding still reports the line it is on.
  • The app rail shows its module names in a phone drawer it has to itself. Below the shell's breakpoint the rail kept its icon-only form inside the drawer, where a tooltip never appears because a tap follows the link first. Alone in the drawer it now presents its names, expandable or not; beside a module column it keeps its narrow form, which is what fits a phone there.
  • The app shell's phone drawer has a close control of its own. A header placed in the content column keeps its navigation toggle where the open drawer covers it, so a second tap on the toggle landed on whatever the drawer drew in that spot, such as a brand link that navigates away. The drawer now shows a close button there, first in its tab order, where focus lands when it opens, and its columns start below it. Escape and a tap on the backdrop close it as before.
  • Canceling an alert dialog returns focus to the button that opened it, even when the dialog names a focus-return-to target. The named target won on every close, so a keyboard user who backed out of a delete landed on it, typically the list heading, instead of on the row they came from. Cancel, Escape and a backdrop click now return focus to the trigger while it still exists. After the confirmation the named target still comes first, because the re-render is about to remove the trigger's row.
  • A chart draws its default series in colors a reader can make out on a light page. WCAG asks 3:1 of a graphic that carries content, and in light mode most of the default series fell short; the first one, standing in for the gray default accent, measured 1.67:1 on white. Where a color token is gray or falls under 3:1 against --color-wk-bg, the chart now draws that series in a darker shade of the same hue, and the token stays as it is for everything else that reads it. The sixth to eighth series switch shade with the mode as well. --color-wk-info now defaults to #0891b2 in light mode, so the token itself holds 3:1 on white. The default palette already cleared the bar in dark mode and looks as before.
  • A date or time input centers its text in WebKit, the engine behind Safari, and an empty date field no longer reads like a set one. WebKit placed the text of a date, time or datetime-local field against the top edge, 8px above the middle of a 40px field, because the field was given a block layout and WebKit centers that text only in a row layout. The separators between the parts of a date or a time now take the placeholder color: WebKit fills an empty date field with today's date in a gray a page cannot restyle, and with the separators in the text color the empty field looked like a date somebody had set.
  • Pagination renders previous and next for a paginator from simplePaginate() instead of throwing. Such a paginator knows whether another page exists but not how many there are. The default full variant asked it for a total and simple asked it for the last page, so rendering failed with an exception. It now gets the previous and next controls, the same step down a cursor paginator already got, and the documentation no longer says the simple variant works with it.
  • A timeline entry shrinks with a narrow container, so a long line scrolls inside it. The content area of a timeline item could not become narrower than the widest line it held. A line that cannot wrap, such as a tool call's arguments, pushed the entry past its container and nothing scrolled: on a 360px phone the documented conversation of tool calls reached 28px past its column in Chromium and 35px in WebKit.
  • WireKit components inside prose look the way they do anywhere else. Prose's element rules reached into the components nested in it: a button link came out underlined, a code block wrapped instead of scrolling and gained a second padding and background, table cells lost their own padding and gained borders, and a blockquote lost its inset. The elements a component renders are now skipped by those rules, while text you place in a component's slot keeps prose styling. Add not-wk-prose to a block of your own to leave all of it unstyled.
  • A slider's first and last mark labels stay inside the slider. Every mark label was centered on its position, so the first label reached half its width past the start of the track and the last one half its width past the end, which cut the last label off at phone width. The labels at the ends now align with the ends of the track, and a label in the middle stays centered.
  • A multi-select or tags input is as tall as an input beside it on a touch screen. The text field inside each component's frame took the 44px touch target on top of the frame's own padding and border, so on a phone a multi-select measured 58px and a tags input 54px next to a 44px input. The frame takes the target now, and nothing changes on a desktop.
  • A collapsible given an id no longer renders that id on two elements. The root and the panel both carried it, so aria-controls pointed at an id two elements shared. The root keeps your id, and the panel takes it with -panel appended.
  • A slot whose condition turned out false counts as empty again. Livewire wraps every @if in two HTML comments, and a slot holding only those comments counted as filled: a consent checkbox rendered with no text and no accessible name, and other components lost their fallback label or default button, or kept room for content that was not there. Every component that decides whether a slot is empty now ignores those comments.
  • Slot content that a component uses as text no longer carries Livewire's markers. A textarea filled inside a condition showed <!--[if BLOCK]><![endif]--> around its value, and the same markers reached the label of a chat marker, the tooltip of an app rail item and the answer an FAQ item hands to search engines.
  • A required checkbox shows its star when its label comes from the slot. The star was drawn only for a label passed as a prop, so a required consent checkbox with a rich label showed no mark.
  • The Minimal preset keeps its panels and helper text readable in dark mode, and Aurora's recipe for blue informational text holds in both modes. Both set colors in :root alone, which on a page with the dark class outranks WireKit's dark defaults: card text in the Minimal preset measured 1.04:1 in dark mode, and Aurora's informational text 2.85:1. Each block now carries the matching dark values.

Documentation

  • The animations page explains view transitions with Livewire. wire:transition already starts a transition, skips it for a reader who asked for less motion and while a modal dialog is open, so the page shows how to name your elements and how to handle one whose shape changes, rather than adding a second driver that would race it.
  • The range slider, chart, streaming chart, command palette and tabs pages show a Livewire class and its view in one tabbed code block. Each tab is titled after its file, so two PHP files no longer appear as two tabs that both read php.
  • Theming lists the guaranteed contrast pairs as a table of role, foreground token, background token and requirement, with one row per button intent and the tinted callout and alert surfaces in rows of their own. The statements are the same as before.
  • The AI tooling page shows how an agent finds the machine-readable surfaces from any documentation URL: the Markdown source a page answers with for Accept: text/markdown, the Link header every page sends, and the catalog at /.well-known/api-catalog.

Components

Every component this release changes, each linked to its page: Accordion · Alert · Alert Dialog · App Rail · App Shell · Assistant Message · Attachment · Avatar · Band · Blockquote · Bottom Nav · Branch Switcher · Brand · Brand Bar · Breadcrumb · Button · Calendar · Callout · Card · Center · Chart · Chat Marker · Checkbox · Code · Code Block · Collapsible · Color Picker · Combobox · Command Palette · Container · Context Menu · Countdown · CTA · Data Table · Date Picker · Date Separator · Divider · Drawer · Dropdown · Editor · Empty State · Event Calendar · FAB · FAQ · Feature · Field · File Upload · Filter Builder · Footer · Grid · Heading · Hero · Highlight · Hover Card · Image · Image Compare · Image Gallery · Inline Edit · Input · Lightbox · Link component · List · Logo Cloud · Map · Menubar · Modal · Multi-Select · Navbar · Navigation Menu · Notification Center · Number Input · OTP Input · Pagination · Password Input · Popover · Pricing Table · Product Card · Profile · Prose · Radio · Range Slider · Rating · Reading · Replay Button · Row · Scope Switcher · Section · Segmented Control · Select · Shell Bar · Shimmer · Sidebar · Skip Link · Slider · Sparkline · Stack · Stage Card · Stat · Status Matrix · Status Tiles · Stepper · Sticky Panel · Stream · Swap · Table · Tabs · Tags Input · Team Section · Testimonial · Text · Textarea · Theme Controller · Time Picker · Timeline · Toggle · Toggle Button · Tool Call · Toolbar · Tooltip · Tour · Tree View · Visually Hidden