A React Native restaurant-review app renders its star rating as five unlabeled Pressables; how would you describe it to VoiceOver and TalkBack with role, label, state and value?
answer
- five nameless stops, no rating heard
- option A: radiogroup of labelled radios
- option B: one slider with a value
- value text: '3 of 5 stars'
- role describes; behaviour still needed
basics
~20 sFive unlabeled Pressables are five nameless focus stops that never speak the rating. Expose either a radiogroup of labelled radios with checked state, or one slider-role element whose accessibilityValue text reads '3 of 5 stars'.
solid answer
~50 sAs written, each star is focusable (Pressable is accessible by default) but has no text, so no name, no role and no state: the user hears five meaningless stops and never learns the current rating. Two sound designs exist. **Radio group**: wrap the stars in a `View` with `role="radiogroup"` and `aria-label="Rating"`, give each star `role="radio"`, `aria-label="3 stars"` and `aria-checked` for the selected one; it works with plain double-tap and suits an editable rating. **Single adjustable element**: make the row one element with `role="slider"`, `aria-label="Rating"` and `accessibilityValue={{ min: 1, max: 5, now: rating, text: `${rating} of 5 stars` }}`; it is one focus stop and reads the value, but needs increment and decrement accessibility actions to be operable. A read-only rating on a review card is simplest as one element with a label such as "Rated 4 of 5 stars" and no interactive role.
code
tsx · 21 linesimport { Pressable, Text, View } from 'react-native';
type Props = { rating: number; onChange: (n: number) => void };
export function StarRatingRadios({ rating, onChange }: Props) {
return (
<View role="radiogroup" aria-label="Rating" style={{ flexDirection: 'row' }}>
{[1, 2, 3, 4, 5].map(n => (
<Pressable
key={n}
role="radio"
aria-label={n === 1 ? '1 star' : `${n} stars`}
aria-checked={n === rating}
onPress={() => onChange(n)}
>
<Text>{n <= rating ? '\u2605' : '\u2606'}</Text>
</Pressable>
))}
</View>
);
}go deeper
Recall that each interactive star needs a label and a role, and that the current rating must be spoken, not only drawn.
Explain the radio-group design with checked state and the single-element design with accessibilityValue and its text override.
Choose between the designs for editable, multi-rating and read-only cases, and make sure an adjustable role comes with working increment and decrement actions.
Define the rating component once for the product, with its accessibility contract tested on both screen readers, so no team rebuilds five nameless stars.
## What the naive version sounds like ```tsx <View style={styles.row}> {[1, 2, 3, 4, 5].map(n => ( <Pressable key={n} onPress={() => setRating(n)}> <StarIcon filled={n <= rating} /> </Pressable> ))} </View> ``` Each `Pressable` is **accessible by default**, so VoiceOver and TalkBack stop on each star. But: - there is **no text**, so there is no name to read; - `Pressable` sets **no role**, so nothing says the star can be activated; - **no state or value** exists, so the current rating is never announced; - filled versus empty is shown only by the icon. A blind reviewer hears five meaningless stops and cannot tell whether they gave two stars or five. ## Design A: a radio group A rating picks exactly one of five options, which is what a radio group describes. 1. The container: `role="radiogroup"` and `aria-label="Rating"`. 2. Each star: `role="radio"`, `aria-label` of "1 star" to "5 stars". 3. The selected star: `aria-checked={n === rating}` on each star. Strengths: every star is operable by a plain **double-tap**, the selected option is announced as checked, and no custom accessibility actions are needed. Weaknesses: five focus stops per rating, which becomes tedious on a form with several ratings (food, service, value). ## Design B: one adjustable element Treat the row as **one control whose value moves in a range**: | Prop | Value | | --- | --- | | `role` | `'slider'` (the `accessibilityRole` equivalent is `'adjustable'`) | | `aria-label` | `'Food rating'` | | `accessibilityValue` | `{ min: 1, max: 5, now: rating, text: `${rating} of 5 stars` }` | The `text` field overrides the numbers, so the reader says "3 of 5 stars" instead of a bare number. Strengths: one focus stop, the value is read immediately. Weakness: the role **only describes** the control. VoiceOver generates the **increment** and **decrement** accessibility actions when the user swipes up or down on an adjustable element, and current TalkBack versions generate them from its adjust gesture; nothing changes until the component declares and handles those actions and makes the stars themselves non-focusable. That operability work is a separate subject — custom actions and grouping — but a design that sets the role without it ships a control that sounds adjustable and is not. ## Read-only ratings On a review card the stars only display a score. There, the right answer is **one non-interactive element** with a label: `<View accessible aria-label="Rated 4 of 5 stars">`. No `Pressable`, no role that promises interaction. ## Choosing | Situation | Choose | | --- | --- | | Single editable rating, simple team | Radio group | | Several ratings in one form | Adjustable element with actions | | Display only | One labelled, non-interactive element | ## Common mistakes - **Labelling the icon, not the Pressable** — the focus stop is the `Pressable`, so the label must be on it. - **Encoding the rating in colour only** — filled versus empty stars are invisible to a screen reader. - **Setting `role="slider"` on the row while leaving each star focusable** — the user meets six stops, one of which claims to be adjustable. - **Forgetting to update the value** after a change, so the reader keeps saying the old rating. - **Mixing designs** — `radio` stars inside a `slider` container confuse both readers. ## Verifying - Turn on **VoiceOver and TalkBack** and rate a restaurant without looking at the screen. - Check that the **current rating is spoken** on focus and after each change. - Check that the icon's filled state never carries information that the label, state or value does not. ## Interview summary Diagnose the missing name, role and state first, then offer both designs with their trade-offs, and point out that `role="slider"` needs increment and decrement handling to be more than a label.
- Why does role='slider' on a React Native star row not let VoiceOver users change the rating by itself?A role only changes what is announced. VoiceOver's swipe-up and swipe-down on an adjustable element are delivered as increment and decrement accessibility actions, and the component must declare and handle them to change the rating. Without that, the row sounds adjustable but does nothing.
- In React Native, how should a read-only star rating on a review card be exposed?As one accessible, non-interactive element with a label such as "Rated 4 of 5 stars", and the star icons not separately focusable. No `Pressable` and no interactive role, because the user cannot change it.
saying these in an interview costs you the question
- Filled star icons tell screen reader users the rating
- role='slider' alone makes the stars adjustable
- Each star only needs a label, not a selected or checked state
- A read-only rating should keep five pressable stars
- accessibilityValue.text is ignored when now is set