Since React Native 0.84, how does a screen reader announce a Text with onPress, and when should you override that default role?
answer
- pressable Text gets a role
- onPress, onLongPress or responder
- only when no role is set
- not when disabled
- action, not navigation: role button
basics
~20 sSince 0.84, a Text with onPress or onLongPress that sets no role gets accessibilityRole 'link', so screen readers announce it as a link. Override with role='button' when pressing performs an action instead of navigating somewhere.
solid answer
~50 sBefore 0.84, pressable `Text` had no role, so a "Read more" or "Terms" text sounded like static text. Since 0.84, `Text` sets `accessibilityRole` to `'link'` automatically when it is pressable — it has `onPress`, `onLongPress` or `onStartShouldSetResponder` — is not `disabled`, and has neither `role` nor `accessibilityRole` set. That default fits text that navigates: "View menu", a URL, "Terms of service". It is wrong when the text triggers an action in place — "Show more" expanding a review, "Undo", "Retry" — where users expect a button; set `role="button"` (and `aria-expanded` for a toggle). Any explicit role wins, and a disabled `Text` does not get the link role. On Android a `Text` becomes focusable when it has a press handler; on iOS `Text` is accessible unless you pass `accessible={false}`. Expo SDK 57 apps run 0.86, so they have this behaviour too.
code
tsx · 17 linesimport { useState } from 'react';
import { Linking, Text, View } from 'react-native';
export function ReviewFooter({ body, siteUrl }: { body: string; siteUrl: string }) {
const [expanded, setExpanded] = useState(false);
return (
<View>
<Text numberOfLines={expanded ? undefined : 3}>{body}</Text>
{/* action in place: override the default link role */}
<Text role="button" aria-expanded={expanded} onPress={() => setExpanded(e => !e)}>
{expanded ? 'Show less' : 'Show more'}
</Text>
{/* navigation: the automatic link role is right */}
<Text onPress={() => Linking.openURL(siteUrl)}>Visit website</Text>
</View>
);
}go deeper
Recall that a Text with onPress is announced as a link by default in current React Native, and that role='button' changes it.
Explain the exact conditions — pressable, not disabled, no role set — and how Android and iOS decide whether the Text is focusable.
Audit pressable text after upgrading past 0.84, classify each as navigation or action, and update tests that asserted the old behaviour.
Use framework accessibility defaults as a baseline but define the component rules that decide link versus button across the product.
## The change React Native **0.84** made pressable `Text` announce itself. Its release notes: `Text` components with `onPress` or `onLongPress` handlers now automatically receive `accessibilityRole="link"`, so screen readers properly announce interactive text. Before that, inline tappable text had **no role**, and VoiceOver and TalkBack users could not tell "Terms of service" from the paragraph around it. ## The exact rule In `Text`, the role is computed from four conditions: 1. **It is pressable** — it has `onPress`, `onLongPress` or `onStartShouldSetResponder`. 2. **It is not disabled** — `disabled` is not `true`. 3. **`accessibilityRole` is not set.** 4. **`role` is not set.** When all four hold, the `Text` renders with `accessibilityRole="link"`. Any role you pass — even `role="none"` — replaces the default; the default never overrides you. | `Text` props | Announced role | | --- | --- | | `onPress` only | link | | `onPress` and `role="button"` | button | | `onPress` and `disabled` | no automatic role | | no press handler | no automatic role | ## When the default is right A **link** takes the user somewhere: another screen, a web page, a document. In a restaurant-review app that is: - "View full menu" opening the menu screen; - the restaurant's website address; - "Terms of service" in the review form's footer. ## When to override it A **button** does something in place. Announcing it as a link sets the wrong expectation: - "Show more" expanding a long review — `role="button"` with `aria-expanded`; - "Undo" after deleting a photo; - "Retry" after a failed upload; - "Mark helpful" on a review. ```tsx <Text role="button" aria-expanded={expanded} onPress={() => setExpanded(e => !e)}> {expanded ? 'Show less' : 'Show more'} </Text> ``` ## Link versus button: what users expect | | Link | Button | | --- | --- | --- | | Result | Goes somewhere: a screen, a page, a document | Changes something here | | Typical text | "View full menu", a URL, "Terms" | "Show more", "Undo", "Retry", "Mark helpful" | | After activation | The user expects a new context | The user expects to stay and hear a change | | State to add | Usually none | Often `aria-expanded`, `aria-disabled` or `aria-busy` | Getting this wrong is not fatal, but it misleads: a screen reader user who hears "link" and expects a new screen may not notice that the review above them just expanded. ## Why the default helps Pressable text is common in React Native apps because `Text` supports `onPress` directly, and a role on it was easy to forget. The automatic link role means that, after an upgrade, every such text at least announces that it is interactive, which is a large improvement over silence. The remaining work is correcting the minority that are actions. ## Focusability differs per platform Whether the `Text` is a separate focus stop is decided differently: - **iOS**: `Text` is accessible unless you pass `accessible={false}`. - **Android**: when `accessible` is not set, `Text` is accessible only if it has `onPress` or `onLongPress`. How a nested pressable `Text` inside a paragraph is focused and exposed is a separate grouping topic. ## Upgrade notes - Apps on 0.84 or later — including Expo SDK 57, which runs 0.86 — get the new role without code changes. - UI tests or snapshots that asserted no role on pressable text change on upgrade. - Audit pressable `Text` once: keep links as links, mark actions as buttons. ## Interview summary Say that since 0.84 pressable `Text` defaults to the link role when no role is set and it is not disabled; that links navigate and buttons act; and that setting `role="button"` is the fix for action text.
- In React Native 0.87, does a disabled Text with onPress get the automatic link role?No. The link role is applied only when the `Text` is pressable and `disabled` is not `true`, and only if neither `role` nor `accessibilityRole` is set.
- In React Native, how can pressable Text get the link role without onPress or onLongPress?Through `onStartShouldSetResponder`. `Text` treats any of `onPress`, `onLongPress` or `onStartShouldSetResponder` as making it pressable, so a text that claims the responder also gets the automatic link role unless a role is set.
saying these in an interview costs you the question
- Pressable Text is announced as a button by default
- The automatic link role overrides an explicit role
- Every Text gets the link role since 0.84
- Expo apps need an opt-in flag for the link role
- Link and button are interchangeable for screen reader users