**Minor.** Keyboard shortcuts for a whole application: bound on the element they act on, by area, with sequences, an overview on `?` and a switch for the shortcuts made of one key. A value in several languages takes one component, with a chip per translation that says whether it is done. A date, a number or a text that is not what it claims to be no longer ends the page or shows as a wrong figure, a key held down acts once instead of with every repeat, and the triggers of overlays keep their ARIA through a Livewire update.

### Added

- **New [shortcuts](https://docs.wirekit.app/components/shortcuts) for a whole application.** `x-wk-shortcut="cmd+s"` on a button, a link or a field binds a shortcut that does what a click on the element does, or with `.focus` moves the focus to it. A shortcut belongs to the page, to the modal dialog its element sits in, or with `.global` to everywhere, and an open dialog hides the page's shortcuts. A sequence such as `g o` waits a second for its next key, and a key held down fires its shortcut once. A key is recognized by the character it types, so `?` and `/` work on a German or a French keyboard, and `cmd` is Cmd on a Mac and Ctrl elsewhere. A letter that a menu's type-ahead takes stays with the menu, and a single key typed into a field stays a character. `<x-wirekit::shortcuts />` opens on `?` with the shortcuts that apply at that moment, grouped by area and drawn in the platform's key names, and holds a switch that turns off the shortcuts made of one key (WCAG 2.1.4): `single-keys` hands it the choice an application stores, and `wirekit:shortcuts-single-keys-changed` announces each flip. `data-wk-single-keys="off"` turns those shortcuts off on a screen with a barcode scanner, and `<x-wirekit::shortcuts.trigger />` opens the overview from a button on a device without a keyboard. Each element with a shortcut carries `aria-keyshortcuts`, and with `APP_DEBUG` on a shortcut bound twice in one area is reported in the browser console.

- **New [translatable input](https://docs.wirekit.app/components/translatable-input) for a value in several languages.** `<x-wirekit::translatable-input>` shows the primary language's field first and, under it, a chip for every further language that says whether it is translated, missing or rejected by the server, in its accessible name as well as in its glyph and color. Up to `inline` further languages (2 by default) stand as fields of their own; with more, a chip opens its language and closes the one open before, and "Show all languages" opens every one in two columns. `wire:model` on the component binds each language to its key of one array, `name.de`, `name.en`, with its modifiers; an error filed under one language stands at that language's field and opens it, and an error under the value itself stands under the legend. A missing translation shows the primary text as its placeholder, every field carries `lang` with its code, `multiline` turns the fields into textareas, and without `wire:model`, `name` and `value` fill a plain form's `name[de]`, `name[en]`. `locales` takes a map of code to name, or a list of codes named in the reader's language through `intl`.

### Changed

- **The [fonts](https://docs.wirekit.app/components/fonts#a-family-per-page) component's `sans` takes a serif family as well.** `sans` sets the body face, the one every component's text is set in, and a shop or a tenant may set its text in a serif. A call such as `<x-wirekit::fonts sans="merriweather" />` now loads that family for the page and puts it in `--font-wk-sans` with its serif fallbacks, as `fonts.sans` in the config always could; before, the call was refused while developing and ignored in production. A monospace family is still refused there, and `serif` and `mono` keep to their own category.

### Fixed

- **A date that is no date is reported instead of ending the page.** A string Carbon cannot read, such as `"TBD"` or `"Oktober 2026"`, in the `until` of a [countdown](https://docs.wirekit.app/components/countdown), the dates of a [review](https://docs.wirekit.app/components/review), the times of a [message](https://docs.wirekit.app/components/message) or the date of a [date separator](https://docs.wirekit.app/components/date-separator) threw from inside the view, so one such value ended the whole request. It is now refused while developing like any invalid value, and in production it counts as a missing date. A date separator given a time zone PHP does not know reads its date in the application's zone the same way, where it also threw.
- **Text that is not valid UTF-8 no longer ends the page when a component hands it to Alpine.** A label, an option or a value from a Latin-1 column, or from a `substr()` that cut a character in half, made the encoder of every component configuration throw, while Blade's `{{ }}` beside it wrote the replacement character U+FFFD. The configuration now writes U+FFFD as well, as [structured data](https://docs.wirekit.app/components/structured-data) already did. An [editor](https://docs.wirekit.app/components/editor) given its document as an array now shows such a byte as U+FFFD, as it did for a document given as a string, where it ended the render before.
- **A [price](https://docs.wirekit.app/components/price) whose amount is no number is left out rather than shown wrong.** PHP read `"19,99"` with a decimal comma as 19 and a word such as `"on request"` as 0, so the price showed 19.00 or 0.00 without a word of warning, and with `minor-units` the same values ended the request with an exception. An amount, a compare-at price, a unit price or a delta that is no number, or no numeric string such as `"19.99"`, is now refused while developing; in production the price is left out for an amount, and only the part itself for the rest.
- **A [price](https://docs.wirekit.app/components/price) without an amount draws nothing instead of 0.00.** `null` or an empty string, a price on request for example, was read as 0. A [pricing tier](https://docs.wirekit.app/components/pricing-table) without a price showed 0.00 with its period, and an empty string in a [product card](https://docs.wirekit.app/components/product-card), a [cart line](https://docs.wirekit.app/components/cart-list) or a [cart summary](https://docs.wirekit.app/components/cart-summary) drew 0.00 beside its label, and with a compare-at price the product card announced a sale. A missing amount now draws no price and the tier no period, and the other three leave the row out as they do for `null`. A 0 still shows 0.00.
- **A [cart summary](https://docs.wirekit.app/components/cart-summary#shipping) says Free only for a shipping cost of zero.** A word in `shipping`, such as `"on request"`, and an empty string, such as a shipping method not chosen yet, read as 0 and showed the shipping as free. A value that is no number now goes on to the price, which refuses it while developing and leaves it out in production, and an empty string hides the row as `null` does.
- **A score that is no number counts as no rating rather than a wrong one.** PHP read `"4,5"` with a decimal comma as 4, so a [rating](https://docs.wirekit.app/components/rating), the stars of a [product card](https://docs.wirekit.app/components/product-card), a [testimonial](https://docs.wirekit.app/components/testimonial) or a [review](https://docs.wirekit.app/components/review) showed 4 stars instead of 4.5 without a warning, and a word as 0. Such a value is now refused while developing; in production the product card and the testimonial draw no stars, and the rating and the review count it as none.
- **An [event calendar](https://docs.wirekit.app/components/event-calendar) whose `date` is no date opens on today instead of staying empty.** The string reached the script as an invalid date, which threw in every day of the grid. The value is now refused while developing like any invalid one, and a date object opens on its own day rather than reaching the script with its time of day.
- **An [energy label](https://docs.wirekit.app/components/energy-label#an-older-scale) takes its scale with any dash between the two ends.** A scale written with the en dash a printed label shows, `A–G`, or with a minus sign or another Unicode dash was refused while developing and drew no arrow in production, though the value looked right. Every dash now reads as the hyphen, as does a space around it.
- **A Backspace held down in a [tags input](https://docs.wirekit.app/components/tags-input) or a [multi-select](https://docs.wirekit.app/components/multi-select) no longer empties the selection.** Held to delete the typed text, the key repeats past its end, and every repeat on the empty field removed another tag or value, the whole selection within a second. Only a press that begins on the empty field removes one now.
- **A [command palette](https://docs.wirekit.app/components/command-palette) hotkey held down toggles the palette once.** Every repeat of the held key opened or closed it again, so the palette flickered for as long as the key stayed down.
- **A [`modal.trigger`](https://docs.wirekit.app/components/modal#opening-a-modal-from-somewhere-the-modal-cannot-reach) with `for` and a [`drawer.trigger`](https://docs.wirekit.app/components/drawer#opening-a-drawer-from-anywhere) keep their ARIA when the Livewire component around them updates.** They lost `aria-haspopup` for good after the first update, and `aria-expanded` and `aria-controls` until an overlay opened again, so a screen reader stopped announcing that the button opens a dialog, and whether it is open. Livewire's morph took off the attributes a script had set, since the markup it compares the page with is built without that script. Both triggers now bind them.
- **The button in the trigger of a [dropdown](https://docs.wirekit.app/components/dropdown), a [popover](https://docs.wirekit.app/components/popover) or a [hover card](https://docs.wirekit.app/components/hover-card) keeps its ARIA when a Livewire component updates.** It lost `aria-haspopup`, `aria-expanded` and `aria-controls` the same way, and a screen reader stopped announcing that it opens a menu or a dialog. The three components put the attributes back after the update.
- **A [combobox](https://docs.wirekit.app/components/combobox) with a `hint` or an `error` draws its clear button and chevron in the middle of its field.** Both were centered on the field and the sentence under it together, so they stood lower by half of that sentence, on the lower edge of the field under a two-line hint. The field and what is drawn on it now stand in a box of their own. `select` and `password-input` already did, and no other field draws a button the same way.
- **The font route answers 404 for a path with a NUL byte.** `/wirekit/fonts/%00` reached the resolver as a real NUL byte, on which PHP's `realpath()` throws, so the route answered 500 and reported an error for every such request. A web server in front of PHP may refuse such a request itself, while `php artisan serve` passes it through.

### Components

Every component this release changes, each linked to its page: [Cart List](https://docs.wirekit.app/components/cart-list) · [Cart Summary](https://docs.wirekit.app/components/cart-summary) · [Combobox](https://docs.wirekit.app/components/combobox) · [Command Palette](https://docs.wirekit.app/components/command-palette) · [Countdown](https://docs.wirekit.app/components/countdown) · [Date Separator](https://docs.wirekit.app/components/date-separator) · [Drawer](https://docs.wirekit.app/components/drawer) · [Dropdown](https://docs.wirekit.app/components/dropdown) · [Energy Label](https://docs.wirekit.app/components/energy-label) · [Event Calendar](https://docs.wirekit.app/components/event-calendar) · [Fonts](https://docs.wirekit.app/components/fonts) · [Hover Card](https://docs.wirekit.app/components/hover-card) · [Input](https://docs.wirekit.app/components/input) · [Message](https://docs.wirekit.app/components/message) · [Modal](https://docs.wirekit.app/components/modal) · [Multi-Select](https://docs.wirekit.app/components/multi-select) · [Popover](https://docs.wirekit.app/components/popover) · [Price](https://docs.wirekit.app/components/price) · [Pricing Table](https://docs.wirekit.app/components/pricing-table) · [Product Card](https://docs.wirekit.app/components/product-card) · [Rating](https://docs.wirekit.app/components/rating) · [Review](https://docs.wirekit.app/components/review) · [Shortcuts](https://docs.wirekit.app/components/shortcuts) · [Tags Input](https://docs.wirekit.app/components/tags-input) · [Testimonial](https://docs.wirekit.app/components/testimonial) · [Textarea](https://docs.wirekit.app/components/textarea) · [Translatable Input](https://docs.wirekit.app/components/translatable-input)