skip to content

Since React Native 0.84, how does a screen reader announce a Text with onPress, and when should you override that default role?

level: middleimportance: should knowfreq 25%

answer

  1. pressable Text gets a role
  2. onPress, onLongPress or responder
  3. only when no role is set
  4. not when disabled
  5. action, not navigation: role button

basics

~20 s

Since 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 s

Before 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 lines
tsx
import { 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

for a junior

Recall that a Text with onPress is announced as a link by default in current React Native, and that role='button' changes it.

for a middle

Explain the exact conditions — pressable, not disabled, no role set — and how Android and iOS decide whether the Text is focusable.

for a senior

Audit pressable text after upgrading past 0.84, classify each as navigation or action, and update tests that asserted the old behaviour.

for a principal

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