Repository navigation
v1.3.0
Overview
🚀 Features
- createCombobox: commit free-text values in non-strict mode (#383)
- createPlugin: opt plugins into Vue DevTools with
devtools: true(#948) - Tour: add createTour and Tour compound for guided walkthroughs (#178)
- createVirtual: keep the window aligned when the scroll parent is not the list (#1046)
- Dialog: opt a dialog out of the global Scrim with
scrim(#961) - Kanban: add Kanban compound component over createKanban (#1012)
- Otp: add Otp compound component over createOtp (#519)
- Virtualizer: add Virtualizer compound component over createVirtual (#788)
- forms: expose AtomExpose on control roots and focus() on groups (#935)
- Input: export parseValidateOn from the Input barrel (#1037)
- Menu: add a headless menu for a button, a right-click, or a menubar, including submenus (#1038)
- Pagination: fit uneven buttons to the container and grow when it widens (#1040)
- Select: name the activator from a visible label (#1055)
- Select: select an option by typing its text (#1058)
- useFocusTrap: confine Tab and Shift+Tab to a root element (#939)
🔧 Bug Fixes
- Menu: a repeated typeahead letter still accepts the next character (#1056)
- Otp: accept a full autofilled one-time code (#1011)
- components: declare missing update:model-value emits so Vue DevTools can track Root v-models (#948)
Minor Changes
-
#383
ff80dd2Thanks @johnleider! - feat(createCombobox): commit free-text values in non-strict modeA non-strict combobox (the default) now accepts typed text that matches no registered option. Confirming with Enter, Tab, click-outside, or blur mints a ticket for the typed value, selects it, and emits it through
v-model;strictkeeps the constrained behaviour and discards unmatched text on confirm. Adds acommit()action to the combobox context. Escape still cancels. Virtual focus reads the option element from the registered ticket (el), notdocument.querySelector. -
#948
0184058Thanks @johnleider! - feat(createPlugin): opt plugins into Vue DevTools withdevtools: trueInspector snapshots unwrap refs and class getters. Logger and storage supply an
inspecthook so method-only contexts still show state. Storage gainskeys(). -
#178
8302695Thanks @johnleider! - feat(Tour): add createTour and Tour compound for guided walkthroughsHeadless step sequencer with activator targeting, form-gated next, and enter/leave/completed handlers. The Tour compound adds highlight, anchored content, and keyboard chrome on top. createTourPlugin provides one app-level tour. The app owns the catalog and loads one tour at a time.
-
#1046
1a5cbceThanks @johnleider! - feat(createVirtual): keep the window aligned when the scroll parent is not the listmarkerdrops content above the list out of the scroll position.itemKeykeeps a measured height on the item when the array is prepended, filtered, or reordered.scrollToplaces the window before it writes the scroll position, retries when the browser clamps that write, and runs a call made before the scroll element exists. PassownElement: falseto leave an externally owned scroll element unstyled. -
#961
3b3734eThanks @johnleider! - feat(Dialog): opt a dialog out of the global Scrim withscrimDialog.ContentandAlertDialog.Contentacceptscrim(defaulttrue). Passfalsewhen you own the backdrop so an app-level<Scrim>does not double-dim the overlay. -
#1012
bec776dThanks @johnleider! - feat(Kanban): add Kanban compound component over createKanban (#1012)Adds
Kanban.Root,Kanban.Column,Kanban.List,Kanban.Item,
Kanban.Instructions, andKanban.LiveRegionfor boards of movable items in
columns. Each Column binds its item array withv-model; a drop writes the new
order back and Root emitsmoveonce with{ value, from, to, fromIndex, toIndex }.
Items move by pointer drag or by keyboard: Space / Enter picks up, arrow keys
and Home / End move a previewed drop target (RTL-aware, skipping columns that
are disabled or whoseacceptrefuses the item), Space / Enter drops, and
Escape cancels without touching the board. The pending target is exposed as
data-targeton the column and list anddata-drop="before" | "after"on the
neighbouring item; every step is announced through the live region. Columns
gate incoming moves withaccept(value, from, index)and freeze with
disabled. -
#787
18eb798Thanks @sridhar-3009! - feat(Otp): add Otp compound component over createOtp (#519)Adds
Otp.Root,Otp.Item, andOtp.HiddenInputfor one-time-password /
verification-code entry. Root bridges v-model, exposes arole="group"with a
locale-driven default accessible name, and auto-renders a hidden input when
nameis set. Each Item is a single character box (autocomplete="one-time-code")
that auto-advances on input, moves back on Backspace of an empty box, supports
arrow-key movement, and distributes pasted or autofilled text across boxes.
@completefires when the code fills. Reject-and-retry lives oncreateOtp's
onCompleteoption.
Root slots anitemsarray derived fromlength(same pattern as Rating) so
boxes are iterated once instead of repeating the count inv-for. -
#788
f990bbbThanks @sridhar-3009! - feat(Virtualizer): add Virtualizer compound component over createVirtual (#788)Adds
Virtualizer.Root,Virtualizer.Item, andVirtualizer.Spacerfor
rendering large lists. Root renders a keyboard-focusable scroll container and
slots only the visible window (plusoverscan), typed fromitems. Place a
Spacerbefore the rows and aSpacerwithedge="end"after them to reserve
the off-screen range; setas(for exampleliinside aul) when adiv
cannot sit there. Each Item measures its own border box, so variable-height
rows with padding or borders need no manualresize()call.@top/@bottom
(withtop-threshold/bottom-threshold) drive infinite scroll.
scrollTo(index, options)andreset()come from the default slot or a
template ref on Root, which also exposes the containerelement— handy for
scroll controls outside the list or auseVirtualFocuscursor.Virtualizer imposes no role — pass
roleon Root and Item, and
aria-posinset/aria-setsizeon Item, to match your content (for grids,
aria-rowcounton Root andaria-rowindexon Item). When a focused row
scrolls out of the window, focus moves to the scroll container instead of
falling to the page.renderlessis not supported; usecreateVirtual
directly for full control over the markup. -
#935
8670f30Thanks @johnleider! - feat(forms): expose AtomExpose on control roots and focus() on groupsCheckbox.Root,Radio.Root,Switch.Root,Toggle.Root, andButton.Rootnow expose the rendered host viaAtomExpose.element, so a template ref can call.focus().Radio.Group,Checkbox.Group,Switch.Group,Toggle.Group, andButton.Groupexposefocus(options?: FocusOptions)which focuses the selected item, or the first if none is selected. -
#1037
edf2af9Thanks @sridhar-3009! - feat(Input): export parseValidateOn from the Input barrelparseValidateOnand itsParsedValidateOnreturn type are now public, so wrappers that re-implement thevalidateOnsurface can reuse the parser instead of copying it. NumberField and@paper/bulmanow use the shared parser. -
#1038
0621413Thanks @johnleider! - feat(Menu): add a headless menu for a button, a right-click, or a menubar, including submenusAdds
Menu.Root,Menu.Bar,Menu.Activator,Menu.Context,Menu.Content,
Menu.Item,Menu.CheckboxItem,Menu.RadioGroup,Menu.RadioItem,
Menu.Separator,Menu.Sub,Menu.SubActivator, andMenu.SubContent.
One compound covers a button, a right-click, or a menubar, with first-party
submenus. A click, a right-click, or a pointer switch opens with no item
highlighted. Arrow keys and pointer hover move the highlight. Light-dismiss of
a submenu closes that layer and leaves the parent menu open.
Docs -
#1040
4047a65Thanks @johnleider! - feat(Pagination): fit uneven buttons to the container and grow when it widensPage buttons can have different widths. The row shrinks when the rendered buttons overflow and grows back when the container widens or the current page is shorter.
createOverflowacceptsprobe: truewhen the count that fits is not mounted yet. Rendercapacity, measure that row, and callsettle(). -
#1055
1f5719cThanks @johnleider! - feat(Select): name the activator from a visible labelSelect.ActivatoracceptsariaLabelledbyand renders it asaria-labelledby. Pass the id of an existing label element when the control's text is the selected value. IflabelandariaLabelledbyare both set,aria-labelledbyis used andaria-labelis omitted. -
#1058
2f59b6bThanks @johnleider! - feat(Select): select an option by typing its textPrintable characters match option text. Characters typed within one second accumulate, and a repeated letter cycles. A single select updates the value and leaves the list open or closed as it was. The matcher is
useTypeahead. -
#939
3cf3360Thanks @Haviles04! - feat(useFocusTrap): confine Tab and Shift+Tab to a root elementA
<dialog>opened withshowModal()is trapped by the browser. Anything else — aDialog.Contentrenderedas="div", a drawer, a command palette — had to hand-roll containment.useFocusTrapis that loop, once:const panel = useTemplateRef<HTMLElement>("panel"); useFocusTrap(panel, { active: isOpen });
Tab and Shift+Tab wrap at the first and last tabbable descendant, focus moves into the root on activate, and the previously focused element gets it back on deactivate. Pass
initialto choose where focus lands (orfalseto skip autofocus),restore: falseto leave focus alone on release, andonEscapeto opt into Escape handling — the trap never closes anything itself. Nested traps: the last connected trap owns Tab; the last connected trap that hasonEscapeowns Escape.The boundary is computed the way the browser computes tab order, not by element identity: a radio group counts as one stop (the checked member if it is otherwise tabbable, otherwise the first otherwise-tabbable member), and focus parked on a script-focusable
tabindex="-1"descendant past the last stop still wraps rather than walking out.aria-disabledcontrols stay in the tab order per APG, so they remain real boundaries.The keydown listener is bound to
documentin the capture phase rather than to the root, so focus that escaped the subtree — a backdrop click that blurred to<body>, a stray programmaticfocus()— is recovered on the next Tab instead of leaving the trap permanently dead. Only the boundaries are intercepted, so a nested widget keeps its own Tab handling. Containment pierces open shadow roots; discovery does not, which is documented along with the<iframe>limitation.Give the root
tabindex="-1"if it can ever hold zero tabbable descendants — the trap focuses the root as a fallback, and v0 does not write attributes onto elements it did not render.
Patch Changes
-
#1056
ede941eThanks @johnleider! - fix(Menu): a repeated typeahead letter still accepts the next characterRepeating a letter moves to the next match. A different letter before the pause searches from that one letter.
A click listener on a menu item runs before the item's own action, including when the item is disabled.
-
#1011
09e971dThanks @johnleider! - fix(Otp): accept a full autofilled one-time code (#1011)A code the browser inserts in one edit (SMS autofill, a password manager) fills every box. Dropping text onto a filled box replaces from that box instead of shifting the digits already there.
-
#948
0184058Thanks @johnleider! - fix(components): declare missing update:model-value emits so Vue DevTools can track Root v-models