Skip to content

fix(Range): bind form aria attributes on thumbs instead of root - #431

Merged
IgorShevchik merged 1 commit into
mainfrom
sync/nuxt-f3c2ac2
Aug 18, 2026
Merged

fix(Range): bind form aria attributes on thumbs instead of root#431
IgorShevchik merged 1 commit into
mainfrom
sync/nuxt-f3c2ac2

Conversation

@IgorShevchik

Copy link
Copy Markdown
Collaborator

Port of nuxt/ui@f3c2ac21 (nuxt/ui#6768). Fourth of six in the queue, after #428.

The defect

useFormField's ariaAttrsaria-invalid and aria-describedby — were spread onto SliderRoot, which renders no role. Assistive technology therefore read an invalid slider as valid, and never reached the error text. The widget is the thumb, and the thumb is what carries role="slider".

-    v-bind="{ ...rootProps, ...ariaAttrs }"
+    v-bind="rootProps"
...
-        <SliderThumb … :aria-label="…" />
+        <SliderThumb … :aria-label="…" v-bind="ariaAttrs" />

Both thumb sites — the tooltip-wrapped one and the bare one. The defect was ours in full: Range.vue:120 carried the identical spread.

This is the commit that was nearly dropped

Upstream calls the component Slider; here it is Range. That rename is recorded in PORTING.md §1 as of #423 — which exists because this commit was first reported as a no-op, on the grounds that no Slider component exists anywhere in src/ and 249 ledger entries never mention one. Both true; the search was by name, and the name is the one thing that changed.

Only the wrapper is renamed. Every reka-ui export inside the file keeps its own name, so SliderRoot and SliderThumb needed no rewriting and the diff is upstream's line for line, uib24ui aside.

Tests

Upstream's case, with the guard renamed to name === 'Range'.

FormField.spec.ts keys describe.each off __name, so the block is reachable only if that value is exactly Range. A guard that never matches is a test that passes without running, so this was checked in the reporter output rather than assumed:

✓ |nuxt| … > FormField > Range integration > binds aria attributes on the thumb

Two mutations, each failing a different assertion — which is what shows both are load-bearing rather than one covering for the other:

mutation fails on
full revert — attributes back on the root, off the thumbs expected undefined to be 'slider' — the invalid element is the root
half revert — attributes on root and thumbs to have a length of 1 but got 2 — duplicated rather than moved

Why no snapshot moved

Worth stating, because a silent snapshot suite normally means a change did nothing. Here it means the opposite.

Range.spec.ts renders the component standalone, outside a FormField, so useFormField yields an empty ariaAttrs — moving an empty object from root to thumb changes no rendered attribute. The 20 role="slider" occurrences in those snapshots come from reka-ui and never carried aria-invalid at all.

The snapshots could not have caught this defect. That is precisely why the fix is pinned by a FormField-level test instead.

Verify (CI=true)

lint · typecheck · test · build — all green. Tests 6708 passed | 6 skipped across 294 files.

No docs:generate — the commit touches no docs/, per §6.

Ledger

f065438d reconciled with #428 and squash c602ea06. cursorf3c2ac21, entry added.

Remaining: cf5f15e3 and f6d188bd — both showcase entries for sites built with nuxt/ui, so the content half is a no-op here; f6d188bd also widens the screenshotOptions schema in docs/content.config.ts, which is worth taking.


Generated by Claude Code

Port of nuxt/ui@f3c2ac21 (#6768).

`useFormField`'s `ariaAttrs` — `aria-invalid` and `aria-describedby` — were
spread onto `SliderRoot`, which renders no `role`. Assistive technology
therefore read an invalid slider as valid and never reached the error text.
The widget is the thumb, and the thumb is what carries `role="slider"`, so
the attributes move there — both sites, the tooltip-wrapped one and the
bare one.

Upstream calls this component `Slider`; here it is `Range`, recorded in
PORTING.md §1 since #423 — which exists because this very commit was first
reported as a no-op on the grounds that no `Slider` component exists. Only
the wrapper is renamed: every reka-ui export inside the file keeps its own
name, so `SliderRoot` and `SliderThumb` needed no rewriting and the diff is
upstream's line for line apart from `ui` -> `b24ui`. The defect was ours in
full.

The test is upstream's with the guard renamed. `FormField.spec.ts` keys
`describe.each` off `__name`, so the block runs only if that value is
exactly `Range` — checked in the reporter output rather than assumed, since
a guard that never matches is a test that passes without running.

Two mutations, each failing a different assertion. Reverting fully fails
the role check (`expected undefined to be 'slider'`); leaving the
attributes on the root as well as the thumbs fails the count (`length of 1
but got 2`). Both halves are load-bearing.

No snapshot moved, which here means the opposite of the usual. `Range`'s
own spec renders the component standalone, outside a `FormField`, so
`ariaAttrs` is empty and moving an empty object changes no rendered
attribute — the `role="slider"` occurrences in those snapshots come from
reka-ui and never carried `aria-invalid`. The snapshots could not have
caught this defect, which is why it is pinned by a FormField-level test.

Also refreshes `.sync/dep-parity.json` and corrects §6, which this port
caught contradicting itself. The rule said to refresh the snapshot "when a
port touches a manifest"; the guard requires the snapshot's cursor to equal
the ledger's, always. The guard is right and the prose was wrong: a
snapshot one commit behind has not been compared against current upstream,
so if a bump landed in between, this fork still matches the stale file, the
guard stays green, and the drift is real — the exact failure the mechanism
exists to catch. §6 now says to refresh on every port that advances the
cursor. Here that rewrote one line and left all 148 versions identical,
which is itself the evidence that nothing drifted.

Verify (CI=true): lint · typecheck · test (6708 passed, 6 skipped, 294
files) · build — all green. No docs:generate; the commit touches no docs/.
@IgorShevchik
IgorShevchik merged commit 6ac1671 into main Aug 18, 2026
1 check passed
@IgorShevchik
IgorShevchik deleted the sync/nuxt-f3c2ac2 branch August 18, 2026 06:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants