Radio

One choice from a set, with a trailing indicator row.

Open Source

Installation

Radio ships with the library — install it once, then import:

import { Radio } from "@delacour/react-native-ui/radio";

Or copy the source into your project, to own and edit it:

bunx delacour@latest add radio

Usage

app/index.tsx
import { Radio } from "@/components/ui/radio";
import { useState } from "react";
import { View } from "react-native";

export default function Example() {
  const [plan, setPlan] = useState<string | null>("monthly");

  return (
    <View className="flex-1 items-center justify-center p-4">
      <Radio.Group accessibilityLabel="Plan" selected={plan} onSelected={setPlan}>
        <Radio value="monthly">
          <Radio.Label>Monthly</Radio.Label>
        </Radio>
        <Radio value="annual">
          <Radio.Label>Annual</Radio.Label>
        </Radio>
      </Radio.Group>
    </View>
  );
}

Variants

secondary fills the ring; primary leaves it hollow.

Variants and states
<Radio variant="primary" value="monthly">Monthly</Radio>
<Radio variant="secondary" value="annual">Annual</Radio>

Sizes

Sizes
Sizes
<Radio size="sm" value="sm">Small</Radio>
<Radio size="md" value="md">Medium</Radio>
<Radio size="lg" value="lg">Large</Radio>

Descriptions

There is no Radio.Description — compose a Text.Caption inside the row and it stays within the one tap target and inside the accessible name.

Per-option description
<Radio value="standard">
  <View className="min-w-0 shrink gap-0.5">
    <Radio.Label>Standard</Radio.Label>
    <Text.Caption>Arrives in three to five days.</Text.Caption>
  </View>
</Radio>

Trailing indicator

Write a Radio.Indicator last and the row spreads to push the ring to the far edge.

Custom indicator
<Radio value="standard">
  <View className="min-w-0 shrink gap-0.5">
    <Radio.Label>Standard delivery</Radio.Label>
    <Text.Caption>Arrives in three to five days.</Text.Caption>
  </View>
  <Radio.Indicator />
</Radio>

Inside a field

The group names no state of its own — isInvalid and isDisabled reach every ring from the enclosing Field.

<Field isInvalid={plan === undefined}>
  <Field.Label>Plan</Field.Label>
  <Radio.Group accessibilityLabel="Plan" onSelected={setPlan} selected={plan ?? null}>
    <Radio value="free">Free</Radio>
    <Radio value="pro">Pro</Radio>
  </Radio.Group>
  <Field.Error>Pick a plan to continue.</Field.Error>
</Field>

Reading the radio's state

Use useRadio() to read the settled state from a custom child, and useRadioGroup() for the group's own.

import { useRadio, useRadioGroup } from "@delacour/react-native-ui/radio";

API Reference

Anatomy

PartWhat it is
RadioOne option. A Pressable that selects its value in the enclosing group
Radio.LabelThe option's text, inside the tap target
Radio.IndicatorThe dot. Accepts custom children to replace the default glyph
Radio.GroupOwns the selected value and the shared axes

Radio

A radio is a Pressable, so feedback, haptic and the rest are inherited — see Pressable. The shared axes resolve group ?? own ?? field ?? default.

Prop

Type

Radio.Group

Owns which radio is selected. Extends ViewProps.

Prop

Type

A group renders no legend, and React Native has no aria-labelledby to tie it to a Field.Label above it — so pass accessibilityLabel or the group is announced without a name.

Radio.Indicator

The dot. Composed in when a radio holds none of its own; write it out to restyle it or to replace the glyph entirely.

Prop

Type

Radio.Label

The radio's text, wired as its accessible name. Extends React Native's TextProps.

Prop

Type

Exports

import {
  Radio,
  type RadioProps,
  RADIO_VARIANTS,
  RADIO_SIZES,
  RADIO_ORIENTATIONS,
  RADIO_DOT_SPRING,
  RADIO_DEFAULT_SIZE,
  RADIO_DEFAULT_VARIANT,
  RADIO_LABEL_TEXT_SIZE,
} from "@delacour/react-native-ui/radio";

On this page