mui / mui/base-ui

Slider: VoiceOver announces stale value when navigating with VO cursor (blur/focus cycle fires before value update)

Open Beginner friendly
#5,296 5 comments 0 reactions 0 assignees View on GitHub
component: slider
Dominant language
TypeScript
Stars
10.9k
Forks
543
Avg merge
1d 20h
Merged PRs (30d)
101

Description

## Summary

When a VoiceOver user navigates to a `Slider` using the **VO cursor** (Control+Option+Arrow) instead of Tab, the screen reader announces the **previous value** instead of the new one after pressing an arrow key to change the value.

[Screen recored](https://app.screencast.com/Wj1zwyfjub04T?conversation=ya6oUlBRQQ5gutwVWZuR4h)

## Root cause

When the slider input does not have `:focus-visible` (which is the case after VO cursor navigation — VoiceOver doesn't trigger `:focus-visible` the same way Tab does), Base UI's internal `onKeyDown` handler does a **blur → focus** cycle to activate `:focus-visible`:

```js
if (!matchesFocusVisible(input)) {
input.blur(); // VoiceOver reads aria-valuenow HERE (stale)
input.focus({ focusVisible: true }); // refocus
}
handleInputChange(event); // aria-valuenow updated HERE — too late
```

VoiceOver reads `aria-valuenow` at the moment of the `blur` event — before `handleInputChange` has run — so it always announces the old value.

## Steps to reproduce

1. Open any page with a `` component (single or range)
2. Enable **VoiceOver** on macOS (Command+F5)
3. Navigate to the slider using **VO cursor** (Control+Option+Right arrow) — do **not** use Tab
4. Enter slider interaction mode: **Control+Option+Shift+Down arrow**
5. Change the slider value: **Control+Option+Right arrow** (or Left)
6. Listen to what VoiceOver announces

**Expected:** VoiceOver announces the **new** value (e.g. "51")
**Actual:** VoiceOver announces the **previous** value (e.g. "50")

> Note: navigating with **Tab** works correctly because Tab sets `:focus-visible`, which skips the blur/focus cycle entirely.

## Environment

- `@base-ui/react` v1.0.0 (and latest)
- macOS Safari + VoiceOver
- Range slider (two thumbs) and single-thumb slider both affected

## Suggested fix

Update the value (`handleInputChange`) **before** performing the blur/focus cycle, so `aria-valuenow` reflects the new value at the time of the blur event — which is when VoiceOver reads it.

```js
handleInputChange(event); // update value FIRST
if (!matchesFocusVisible(input)) {
input.blur();
input.focus({ focusVisible: true });
}
```

Or alternatively, skip the blur/focus cycle entirely when an assistive technology is detected (e.g. via the `Navigator.userAgent` or a focus-visible polyfill approach).

Contributor guide

Open the contributing guide

Research direction

Locate the Slider implementation's internal onKeyDown handler and handleInputChange logic, then reproduce the issue with VoiceOver using VO cursor navigation rather than Tab. Ensure the value update occurs before any blur/focus cycle, and verify that single and range sliders announce the new value on macOS VoiceOver.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
accessibility, frontend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.