Skip to main content
WireKit

WireKit v2.49.0

Minor release

Minor. This release is the output of one deep audit, and the thread running through it is that almost none of it could have gone red. A control that announces its state twice, a live region added to the page in the same breath as its message, a role the documentation promises and the template never emits, a string that reaches nine translation catalogs and not the tenth — each renders a healthy page, throws nothing, logs nothing, and is wrong for exactly the reader who has no other way to tell. The three worth reading first fail that way and are the most costly: a focus trap armed on a panel nothing could release, controls whose accessible name arrived only after Alpine had booted, and four components whose entire user-visible text reached no translator at all.

Added

  • <x-wirekit::row> takes wrap="responsive" — it wraps below the md breakpoint and holds a single line above it. A row of four or five items reads as one line on a laptop and runs off the side of a phone; the cure until now was to add wrap to each affected page, which fixes that page and leaves everyone else to rediscover it. A row that does not ask for it renders exactly as before.

  • Three components that used to render an empty frame now have a real empty state, so a list with nothing in it says so instead of looking broken.

  • <x-wirekit::data-table> gains a server mode that says when it is waiting, and its skeleton spacing now comes from tokens rather than from fixed values.

  • The body face is preloaded, so the font that decides the largest contentful paint is no longer two round trips deep.

  • <x-wirekit::password-input> takes autocomplete. The default is the sign-in case, which is the overwhelming majority of uses; a registration or change form passes autocomplete="new-password", and false opts out entirely for a field that is not a credential.

  • <x-wirekit::file-upload> takes capture, so a phone can be asked for the camera rather than the photo library. It could not be passed from the call site before: this input forwards named attributes and wire:model* only, never the whole bag, so writing capture yourself put nothing on the element.

  • <x-wirekit::map> takes reducedData. On respect — the default — a reader who asks their system to save data gets the marker list and a control to load the canvas anyway. Pass ignore where the canvas IS the content rather than an illustration of it: a route editor, a delivery-tracking view.

  • <x-wirekit::alert-dialog> takes closeOnConfirm, so a destructive confirm can close its own dialog without a hand-written click handler — which used to be the only way, and swapping one in silently removed the announcement.

  • <x-wirekit::button> takes wrapLabel. A label the application wrote belongs on one line; a label a user typed does not, and that is the case this opts into.

  • <x-wirekit::data-table> and <x-wirekit::filter-builder> take searchDebounce. The right value is a property of the backend rather than of the table: a local index wants it short, a rate-limited third-party search wants it long, and 0 disables the wait for anyone throttling on their own side.

  • <x-wirekit::progress> takes valueExpression, so a bar driven by Alpine state announces the value it is actually showing instead of a <div> nothing reads. Pass the property name — it resolves against the surrounding x-data through Alpine's scope chain.

  • <x-wirekit::ticker> takes trendLabel, which names the sparkline in the default slot. Opt-in and off by default, because a graphic that already carries its own name must not be given a second, worse one.

  • <x-wirekit::timeline>'s items take datetime. A <time> whose text reads "2 hours ago" needs the machine-readable stamp beside it; without one, nothing can resolve the entry to a point in time.

  • Three menu and tree surfaces take a name. <x-wirekit::dropdown.panel> and <x-wirekit::context-menu> take label, and so does <x-wirekit::tree-view> — without one a screen reader announces that a menu opened but not which. <x-wirekit::dropdown.item> takes role, for the case where a group of entries is one mutually exclusive choice rather than a list of actions.

  • <x-wirekit::combobox> takes hint, matching the other form controls.

  • <x-wirekit::structured-data> takes nonce, for a Content-Security-Policy that requires one on every script.

  • emptyText on <x-wirekit::status-tiles>, <x-wirekit::status-matrix> and <x-wirekit::image-gallery> — the three that used to render an empty frame. A caller who does not set it gets a sensible default rather than a blank box.

  • <x-wirekit::brand> and <x-wirekit::testimonial> take logoAspect (and brand a mobileLogoAspect), so a logo reserves its own space instead of shifting the layout as it loads. Left unset the images still carry a floor of one logo height, so the worst case is a square reservation rather than none.

Changed

  • Three built-in strings now spell the ellipsis the way the other six doSelect…, Add a tag… and Search commands…, instead of three dots. English was the only language reading the dotted form: every other catalog already rendered Auswählen …, Seleccionar…, Selecionar…, because the English file is the fallback every locale is merged over and its text is its key. Fixing the text meant moving the key.
  • A renamed key does not take your translation away. If you have overridden one of those three in your own catalog, WireKit carries your wording forward to the new spelling — in the prefixed form and the plain one — so nothing reverts to the defaults on upgrade. Writing the new spelling yourself wins over the carry-forward, which is how you finish the move. See Localization.

Fixed

Accessibility. Twenty-eight changes, and the shape they share is that the page looked correct in every browser.

  • An optimistic <x-wirekit::tags-input> announced its sentence template instead of its save state. With optimistic set, adding a tag spoke "Added :name", placeholder and all, and that replaced the "Saving" it should have said while the change was on its way to the server. It now says both things it was built to say: what happened to the list, and that the change is being saved. Without optimistic the control was unaffected.

  • A focus trap was armed on panels nothing could release. Six overlays hand the trap their panel as the last-resort target, and the library requires that target to carry a negative tabindex. On a panel whose content is plain text the trap swallowed every Tab and handed focus to an element that cannot take it, so the panel could not be left by keyboard at all.

  • Three surfaces got their accessible name only after Alpine booted, which means a screen reader arriving first announced an unnamed control: a tree, a theme menu and the selected pricing interval. The name is in the server-rendered markup now, and a teleported context-menu panel — which had none at all — was given one.

  • Three controls announced their state twice, or not at all, and five more announcements were wrong, truncated or absent. A live region only announces content that changes AFTER it is in the document; several of these added the region and its message together, which announces nothing.

  • Seven lists lost their list semantics when they removed their markers, so a screen reader stopped saying how many items there were.

  • <x-wirekit::pagination> named the same control two different ways depending on the variant: simple and mini took the arrow glyph into the accessible name while full did not. One component, two names for one control, decided by which variant a page happened to use. The glyphs are hidden from assistive technology now, so the name is the word alone in all three — and they are hidden rather than removed, which is the difference between repairing a name and quietly changing how a control looks.

  • Three components derived their element ids from their content, which collides the moment two of them render the same text on one page, and a command palette shared one listbox id between instances.

  • A brand that carried both a logo and children rendered a link with no accessible name. The children were dropped whenever a logo or a name was set — the documented use, a workspace switcher or a product badge, was exactly the case thrown away — and their presence had already suppressed the fallback label. They render alongside now.

  • A tree's selection event never fired for a mouse. tree-node-select was dispatched only from the Enter and Space bindings, and a leaf row carried no click handler at all — so the documented way to respond to a selection was silent for the gesture most people use. Both gestures go through one path now, and the roving tabindex follows the pointer.

  • Keyboard paths that the documentation promised and the code did not have: Home and End on the color picker's saturation plane, Shift+F10 on the context menu, Escape on the minimap tooltip, a reachable disabled segmented control, and a FAB that advertised a menu it did not implement.

  • Reduced-motion is now honored in both halves of the collapse rule, by the charts while the page is open, and by the glass extension, which answers the two settings that ask for no transparency.

  • A link that opens a new tab says so; an icon-only copy button, a map pin and an overlay close wrapper carry names and remain operable; an inert header no longer claims a sort; and every sparkline in the documentation either has a name or declares itself decorative.

Internationalization and right-to-left. Four components shipped user-visible English that reached no translator — the skeleton family's loading announcement, the map's two strings, the QR code's fallback name and its placeholder, and the trend direction on <x-wirekit::stat>. resources/js had no translator channel at all, so anything a script wrote was English by construction. Dates now let the locale decide the ORDER rather than only the names, prices isolate their numbers, filter-builder, data-table and reading-meta are translated, and a configured value no longer shadows the catalog. On the RTL side the scroll-shadow gradients and the pagination arrows mirror, and the vertical tablist draws on the inline axis.

Security. The theme script is encoded with the script-safe encoder; a lightbox embed validates its scheme and sandboxes its frame; a configured font fallback can no longer break out of its @font-face block; an orphaned FAQ question can no longer reach the next request's structured data; the minimap sanitizer is a fixed point rather than a single pass, and it strips the class it previously only claimed to; a docs page's frontmatter restriction is read in every spelling YAML allows; and the glass extension has a nonce path for the assets it emits.

Performance. Hot-path measurements moved off the event and onto the frame, sort keys and streamed text are derived once rather than once per comparison, two undebounced search boxes are debounced, the minimap coalesces its resize pass, two logos reserve their space, the charts stopped probing the DOM and creating a canvas per var() lookup, and a map no longer fetches tiles nobody asked for.

Charts. An advertised chart type that ApexCharts has never heard of is gone, a missing peer library now reports itself instead of looking like a styling bug, and a class rejected after construction is applied before it.

  • That missing-library panel was unreadable in dark mode. It painted a pale amber fill at half opacity under dark amber text, which composites to a readable pair on a light page and to 2.25:1 against whatever happens to sit behind it on a dark one. A translucent background has no contrast of its own — it has whatever is underneath — so the panel is opaque now and takes both colors from the theme, which measures 6.24:1 in light and 7.76:1 in dark. The same applies to the ApexCharts panel and to the placeholder a chart renders when no adapter is configured.

Forms. Ten controls accepted required and threw it away. Three ignored a failed validate, a hint was rendered where nobody could see it, an unnamed slider claimed another field's validation error, and the editor announces its invalid state in the document the server sends rather than only after hydration.

Components. Four components lost their Alpine state to a Livewire morph. Five tags could render one attribute twice, and the browser keeps the first. A link-styled button submitted the form it sat in; a highlight query destroyed the markup it was given; six icon buttons carried a glyph that contradicted their own accessible name; two shortcut chips reimplemented the kbd component instead of using it; a tooltip answered a different question than the one it was asked; and a kanban board pushed its scrollable width onto the page on a phone.

Tooling. get_tokens matched one of the four naming shapes the stylesheet ships, so 84 documented tokens were missing from the answer. get_component_examples returned nothing for a component its family page taught only inside a code fence. The MCP handshake accepted any protocol version, including an empty string. .wirekit-schema.json and wirekit:export-json were two loops building one manifest and had drifted apart. And wirekit:verify reused its counters between invocations.

Documentation

Three hundred and twenty-two pages were read against the tree rather than against each other, and the corrections below are the ones a reader would have acted on.

  • Copy-paste blocks that were incomplete while saying they were not. One recipe introduced its source block as the full view ready to paste and left out five sections; several blueprint pages described ARIA wiring their own preview does not emit, or a customization value that appears nowhere on the page. Each now either carries what it promised or says what it actually is.
  • A shell you could paste and not escape on a phone. The pasteable example on Shell Bar composed an application shell whose navigation moves into an off-canvas panel below lg and shipped nothing that opens it. The preview above it always had the handle; the abridged block beneath had dropped it, which is the copy anybody actually pastes.
  • Blocks that taught raw utility classes where a component already exists. A dashboard recipe rebuilt a two-pane frame, a filter column and a result count out of arbitrary-value classes reaching for design tokens. They compose row, stack and text now; what stays inline is the geometry no prop expresses.
  • Props tables that stated a default the component does not have, and one that omitted a prop entirely. The code is the authority in both directions.
  • The reading pass on the front pages. The documentation home and Getting Started had picked up a cadence of dashed asides that reads as machine-written; the sentences use the punctuation they actually wanted — a colon before a definition, a full stop between two statements, parentheses around an aside. The **Name** — Description convention in lists and tables is untouched, because that separator is structure rather than cadence.