Environment
- Operating System: Darwin 25.6.0
- Node Version: v22.15.1
- Nuxt Version: - (Vue 3.5.35 + Vite 6, @nuxt/ui in Vue mode)
- Package Manager: pnpm@10.29.3
- @nuxt/ui: 4.8.1 (also verified against v4 @ 6bd1dfc and v4.10.0)
- reka-ui: 2.9.8
Is this bug related to Nuxt or Vue?
Vue
Package
v4.x
Version
v4.10.0
Reproduction
The documentation examples reproduce this directly — no sandbox needed. Turn on a screen reader (VoiceOver Cmd+F5, or NVDA/JAWS on Windows) and Tab to the sliders on these pages:
Expected: a name supplied by the consumer on the first, and reka-ui's Minimum / Maximum and Value 1 of 3 on the second. Inspecting the role="slider" element in devtools shows the same thing without a screen reader.
The relevant source is two lines: https://github.com/nuxt/ui/blob/v4/src/runtime/components/Slider.vue#L136-L138
Description
Slider sets a static aria-label on the thumb, which is the element reka-ui renders with role="slider":
<SliderThumb ... :aria-label="thumbs === 1 ? 'Thumb' : `Thumb ${thumb} of ${thumbs}`" />
Slider.vue#L136-L138 — both the tooltip and non-tooltip branches
This produces three distinct problems.
1. A single-thumb slider can never be given an accessible name.
Its name is always Thumb, and every route to changing it is closed:
UFormField does not help. useFormField's ariaAttrs returns only aria-invalid and aria-describedby — no aria-labelledby. The field's label[for] targets the id on SliderRoot, a plain <span> that is neither a labelable element nor the element with role="slider", so it contributes nothing to the accessible name.
- Attribute fallthrough does not help.
<USlider aria-label="Volume" /> lands on SliderRoot, which has no role and is not focusable, so it is never announced.
- The
ui prop does not help — it accepts tv classes only.
Note that reka-ui deliberately leaves room for the consumer here: SliderThumbImpl resolves the label as $attrs['aria-label'] || getLabel(...), and getLabel() returns undefined for a single thumb. The wrapper's static string takes precedence over $attrs and closes that door.
2. For range sliders, the hardcoded label is less accessible than the primitive's default.
reka-ui's getLabel() returns ['Minimum', 'Maximum'] for two thumbs and Value {i} of {n} for three or more. Slider overrides all of them with Thumb {i} of {n}. On the most common range-slider use case — a price or date filter — this replaces two meaningful names with two meaningless ones. I don't believe that was the intent of #5313, which was aimed at the nameless single-thumb case, but it is the current behaviour.
3. aria-valuetext cannot be set.
Neither Slider nor reka-ui's slider sets aria-valuetext, and because $attrs cannot reach the thumb it cannot be supplied from outside either. Any slider whose raw number is ambiguous — durations, frequencies, unitless 0–1 ratios — announces a bare 5 where 5 milliseconds is what carries the meaning. The APG slider pattern calls for aria-valuetext in exactly this situation.
Impact
The control remains operable — keyboard interaction and value announcement are correct — so this is an identification failure rather than an operation failure. It is mild when a single slider sits alone on a page and the user is in browse mode, where the visible label can be inferred from reading order. It degrades sharply when:
- Tabbing through a form, which skips static text, so the visible label is never spoken.
- Several sliders share a screen, at which point every one of them announces identically and nothing distinguishes them.
- Focus returns after a modal or re-render, leaving no way to tell which control is focused.
- Voice control is used (Dragon, macOS/iOS Voice Control), which targets controls by accessible name. "Click Volume" fails; the only working phrase is "click Thumb", which is unguessable and ambiguous across multiple sliders.
Where a visible text label is present, this is a failure of WCAG 2.5.3 Label in Name (Level A) — the accessible name does not contain the visible label text. 1.3.1 and 4.1.2 are also implicated.
One consequence worth flagging: because Thumb is a syntactically valid name, automated tooling reports these sliders as passing. axe's aria-input-field-name rule catches a nameless slider but cannot detect a meaningless one, so the current behaviour makes the problem invisible to the checks most teams rely on.
Scope
This is distinct from #6749 / #6768, which move ariaAttrs from the root onto the thumb. That is a correct fix for a real adjacent problem, but since ariaAttrs carries no aria-labelledby, the accessible name is still Thumb after it merges. The three problems above are unaffected by it.
Additional context
Proposed direction (brief — happy to follow whatever shape you prefer):
Forwarding $attrs to the thumb and only applying the built-in label as a fallback would address all three problems at once, restore reka-ui's Minimum/Maximum defaults, and add no new API surface. An explicit thumbLabel prop, or adding aria-labelledby to useFormField's ariaAttrs, would each solve problem 1 alone. I'm glad to open a PR for whichever direction you'd prefer.
Prior art: #5313 introduced this and flagged it as provisional at the time —
This is not the best solution and I'm open to suggestions on how to best handle this. The aria-label should be customizable on a case by case basis so we should also add a prop to handle this or bind the related attributes from $attrs.
The PinInput precedent cited in that thread holds for PinInput, whose hardcoded name describes the control's actual purpose. A slider's purpose is caller-defined by nature, so a fixed name can't describe it.
Context for the report: this is what pushed us off USlider in an accessibility-first educational app — four sliders (threshold, strength, speed, frequency) that would all have announced as "Thumb" with units dropped. We fell back to a native <input type="range">, which gives correct naming and aria-valuetext for free but loses the design system styling. We'd much rather be using the component.
Logs
No response
Environment
Is this bug related to Nuxt or Vue?
Vue
Package
v4.x
Version
v4.10.0
Reproduction
The documentation examples reproduce this directly — no sandbox needed. Turn on a screen reader (VoiceOver
Cmd+F5, or NVDA/JAWS on Windows) and Tab to the sliders on these pages:Expected: a name supplied by the consumer on the first, and reka-ui's
Minimum/MaximumandValue 1 of 3on the second. Inspecting therole="slider"element in devtools shows the same thing without a screen reader.The relevant source is two lines: https://github.com/nuxt/ui/blob/v4/src/runtime/components/Slider.vue#L136-L138
Description
Slidersets a staticaria-labelon the thumb, which is the element reka-ui renders withrole="slider":Slider.vue#L136-L138— both the tooltip and non-tooltip branchesThis produces three distinct problems.
1. A single-thumb slider can never be given an accessible name.
Its name is always
Thumb, and every route to changing it is closed:UFormFielddoes not help.useFormField'sariaAttrsreturns onlyaria-invalidandaria-describedby— noaria-labelledby. The field'slabel[for]targets theidonSliderRoot, a plain<span>that is neither a labelable element nor the element withrole="slider", so it contributes nothing to the accessible name.<USlider aria-label="Volume" />lands onSliderRoot, which has no role and is not focusable, so it is never announced.uiprop does not help — it acceptstvclasses only.Note that reka-ui deliberately leaves room for the consumer here:
SliderThumbImplresolves the label as$attrs['aria-label'] || getLabel(...), andgetLabel()returnsundefinedfor a single thumb. The wrapper's static string takes precedence over$attrsand closes that door.2. For range sliders, the hardcoded label is less accessible than the primitive's default.
reka-ui's
getLabel()returns['Minimum', 'Maximum']for two thumbs andValue {i} of {n}for three or more.Slideroverrides all of them withThumb {i} of {n}. On the most common range-slider use case — a price or date filter — this replaces two meaningful names with two meaningless ones. I don't believe that was the intent of #5313, which was aimed at the nameless single-thumb case, but it is the current behaviour.3.
aria-valuetextcannot be set.Neither
Slidernor reka-ui's slider setsaria-valuetext, and because$attrscannot reach the thumb it cannot be supplied from outside either. Any slider whose raw number is ambiguous — durations, frequencies, unitless 0–1 ratios — announces a bare5where5 millisecondsis what carries the meaning. The APG slider pattern calls foraria-valuetextin exactly this situation.Impact
The control remains operable — keyboard interaction and value announcement are correct — so this is an identification failure rather than an operation failure. It is mild when a single slider sits alone on a page and the user is in browse mode, where the visible label can be inferred from reading order. It degrades sharply when:
Where a visible text label is present, this is a failure of WCAG 2.5.3 Label in Name (Level A) — the accessible name does not contain the visible label text. 1.3.1 and 4.1.2 are also implicated.
One consequence worth flagging: because
Thumbis a syntactically valid name, automated tooling reports these sliders as passing. axe'saria-input-field-namerule catches a nameless slider but cannot detect a meaningless one, so the current behaviour makes the problem invisible to the checks most teams rely on.Scope
This is distinct from #6749 / #6768, which move
ariaAttrsfrom the root onto the thumb. That is a correct fix for a real adjacent problem, but sinceariaAttrscarries noaria-labelledby, the accessible name is stillThumbafter it merges. The three problems above are unaffected by it.Additional context
Proposed direction (brief — happy to follow whatever shape you prefer):
Forwarding
$attrsto the thumb and only applying the built-in label as a fallback would address all three problems at once, restore reka-ui'sMinimum/Maximumdefaults, and add no new API surface. An explicitthumbLabelprop, or addingaria-labelledbytouseFormField'sariaAttrs, would each solve problem 1 alone. I'm glad to open a PR for whichever direction you'd prefer.Prior art: #5313 introduced this and flagged it as provisional at the time —
The PinInput precedent cited in that thread holds for PinInput, whose hardcoded name describes the control's actual purpose. A slider's purpose is caller-defined by nature, so a fixed name can't describe it.
Context for the report: this is what pushed us off
USliderin an accessibility-first educational app — four sliders (threshold, strength, speed, frequency) that would all have announced as "Thumb" with units dropped. We fell back to a native<input type="range">, which gives correct naming andaria-valuetextfor free but loses the design system styling. We'd much rather be using the component.Logs
No response