> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twenty.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Radio

> Render one option in a mutually exclusive radio group.

export const StoryEmbed = ({storyId, title, height = 120}) => <>
    <Tabs>
      <Tab title="Light">
        <iframe title={`${title} (light)`} src={`https://storybook.twenty.com/iframe.html?id=${storyId}&viewMode=story&globals=colorScheme:light`} width="100%" height={height} loading="lazy" allow="clipboard-write" style={{
  border: 0
}} />
      </Tab>
      <Tab title="Dark">
        <iframe title={`${title} (dark)`} src={`https://storybook.twenty.com/iframe.html?id=${storyId}&viewMode=story&globals=colorScheme:dark`} width="100%" height={height} loading="lazy" allow="clipboard-write" style={{
  border: 0
}} />
      </Tab>
    </Tabs>
    <a href={`https://storybook.twenty.com/?path=/story/${storyId}`}>
      Open in Storybook
    </a>
  </>;

`Radio` represents one option inside [RadioGroup](/ui/primitives/input/radio-group). Give each option a distinct `value` and a visible label. The group owns the selected value.

<StoryEmbed storyId="ui-input-radio--display" title="Radio example" height={140} />

## Usage

```tsx theme={null}
import { Radio, RadioGroup } from 'twenty-ui/primitives/input';

export const ContactMethod = () => (
  <RadioGroup
    name="contactMethod"
    defaultValue="email"
    aria-label="Preferred contact method"
  >
    <Radio value="email">Email</Radio>
    <Radio value="phone">Phone</Radio>
    <Radio value="post" disabled>
      Postal mail (unavailable)
    </Radio>
  </RadioGroup>
);
```

The `size` prop accepts `sm` or `md` and defaults to `sm`. Use the same size for options in a group.

## Visual choices

Compare selected and disabled states, sizes, and the default and card appearances. Use a labeled group of options for an actual choice.

<StoryEmbed storyId="ui-input-radio--catalog" title="Visual choices for radio" height={380} />

## Complete parts

`Radio.Root` and `Radio.Indicator` expose the installed Base UI parts without the styled assembly. Compose them when you need custom content or indicator placement. The callable `Radio` supplies Twenty's radio appearance and indicator for common options.

```tsx theme={null}
import { Radio, RadioGroup } from 'twenty-ui/primitives/input';

export const StorageChoice = () => (
  <RadioGroup aria-label="Storage" defaultValue="ssd">
    <Radio.Root value="ssd">
      <Radio.Indicator keepMounted render={<span />}>
        ✓
      </Radio.Indicator>
      SSD
    </Radio.Root>
    <Radio.Root value="hdd">HDD</Radio.Root>
  </RadioGroup>
);
```

Standard radios and `Radio.Root` render a span with radio semantics and an adjacent hidden native input. Card radios render a div. `ref` targets the rendered root; `inputRef` targets the input. `Radio.Indicator` renders a span and its ref targets that element. `render`, state-based `className` and `style`, native attributes and event handlers retain their upstream contracts. The public types include `RadioProps`, `RadioRootProps`, `RadioRootState`, `RadioIndicatorProps` and `RadioIndicatorState`.

## Card options

Use `variant="card"` for full-width options with an inset radio indicator. Card radios keep their div root by default. Use `render` to choose another element explicitly. When that element is a native button, also set `nativeButton`. Keep interactive controls outside the card content.

Give the group enough width for its content and leave space for the indicator at the top right. A title, icon, and short description help explain each choice.

<StoryEmbed storyId="ui-input-radio--cards-documentation" title="Radio card options" height={290} />

```tsx theme={null}
import { IconLayoutKanban, IconTable } from 'twenty-ui/icon';
import { Radio, RadioGroup } from 'twenty-ui/primitives/input';
import { Text } from 'twenty-ui/primitives/typography';

const VIEWS = [
  {
    value: 'table',
    title: 'Table view',
    description: 'Browse records in rows.',
    Icon: IconTable,
  },
  {
    value: 'board',
    title: 'Board view',
    description: 'Track progress across columns.',
    Icon: IconLayoutKanban,
  },
];

export const ViewChoice = () => (
  <RadioGroup
    aria-label="Default view"
    defaultValue="table"
    style={{ width: 280 }}
  >
    {VIEWS.map(({ value, title, description, Icon }) => (
      <Radio key={value} variant="card" value={value}>
        <span style={{ display: 'grid', gap: 8, paddingInlineEnd: 24 }}>
          <Icon size={20} aria-hidden />
          <Text
            render={<span />}
            style={{ fontWeight: 'var(--t-font-weight-medium)' }}
          >
            {title}
          </Text>
          <Text
            render={<span />}
            style={{ color: 'var(--t-font-color-secondary)', lineHeight: 1.5 }}
          >
            {description}
          </Text>
        </span>
      </Radio>
    ))}
  </RadioGroup>
);
```

## Explicit native composition

```tsx theme={null}
import { Radio, RadioGroup } from 'twenty-ui/primitives/input';

export const BillingChoice = () => (
  <RadioGroup aria-label="Billing interval" defaultValue="monthly">
    <Radio
      variant="card"
      value="monthly"
      nativeButton
      render={<button type="button" />}
    >
      Monthly
    </Radio>
    <Radio
      variant="card"
      value="yearly"
      nativeButton
      render={<button type="button" />}
    >
      Yearly
    </Radio>
  </RadioGroup>
);
```

The ref targets the composed button. For custom non-button roots, leave `nativeButton` false and forward the supplied render props and ref to the actual element.

## Keyboard and selection

Tab enters or leaves the group. Arrow keys move between enabled options and select the focused option. Space selects the focused radio. The installed Base UI radio suppresses Enter selection for both default roots and composed native buttons. Selection and change handlers belong on `RadioGroup`; see its [uncontrolled example](/ui/primitives/input/radio-group#uncontrolled-state) and [controlled example](/ui/primitives/input/radio-group#controlled-state).

Radio arrows change selection as they move focus. [Dropdown.SelectionGroup](/ui/components/navigation/dropdown) moves focus with arrows and commits a choice with Enter or Space.

`Radio` and `Radio.Root` are generic over the value type. Keep that type consistent with its group. Native attributes such as `aria-describedby` can associate an option with additional help text.

## Props

The reference is generated from the public component types. Native attributes, including accessible names and event handlers, are also accepted on parts that render elements.

<ParamField body="children" type="ReactNode">
  Option content. Card options wrap it in a padded full-width content area.
</ParamField>

<ParamField body="className" type="string | ((state: RadioRootState) => string | undefined)">
  CSS class applied to the element, or a function that
  returns a class based on the component's state.
</ParamField>

<ParamField body="disabled" type="boolean">
  Whether the component should ignore user interaction.
</ParamField>

<ParamField body="inputRef" type="Ref<HTMLInputElement>">
  A ref to access the hidden input element.
</ParamField>

<ParamField body="nativeButton" type="boolean" default="false">
  Whether the component renders a native `<button>` element when replacing it
  via the `render` prop.
  Set to `true` if the rendered element is a native button.
</ParamField>

<ParamField body="readOnly" type="boolean">
  Whether the user should be unable to select the radio button.
</ParamField>

<ParamField body="render" type="ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, RadioRootState>">
  Allows you to replace the component's HTML element
  with a different tag, or compose it with another component.

  Accepts a `ReactElement` or a function that returns the element to render.
</ParamField>

<ParamField body="required" type="boolean">
  Whether the user must choose a value before submitting a form.
</ParamField>

<ParamField body="size" type="&#x22;md&#x22; | &#x22;sm&#x22;" default="sm">
  Visual size of the radio.
</ParamField>

<ParamField body="style" type="CSSProperties | ((state: RadioRootState) => CSSProperties | undefined)">
  Style applied to the element, or a function that
  returns a style object based on the component's state.
</ParamField>

<ParamField body="value" type="TValue" required>
  The unique identifying value of the radio in a group.
</ParamField>

<ParamField body="variant" type="&#x22;card&#x22; | &#x22;default&#x22;" default="default">
  Appearance of the styled assembly: a standard radio or a full-width card. Standard radios use a span and card radios use a div unless render replaces the root.
</ParamField>

### Radio.Root

<ParamField body="Root.className" type="string | ((state: RadioRootState) => string | undefined)">
  CSS class applied to the element, or a function that
  returns a class based on the component's state.
</ParamField>

<ParamField body="Root.disabled" type="boolean">
  Whether the component should ignore user interaction.
</ParamField>

<ParamField body="Root.inputRef" type="Ref<HTMLInputElement>">
  A ref to access the hidden input element.
</ParamField>

<ParamField body="Root.nativeButton" type="boolean" default="false">
  Whether the component renders a native `<button>` element when replacing it
  via the `render` prop.
  Set to `true` if the rendered element is a native button.
</ParamField>

<ParamField body="Root.readOnly" type="boolean">
  Whether the user should be unable to select the radio button.
</ParamField>

<ParamField body="Root.render" type="ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, RadioRootState>">
  Allows you to replace the component's HTML element
  with a different tag, or compose it with another component.

  Accepts a `ReactElement` or a function that returns the element to render.
</ParamField>

<ParamField body="Root.required" type="boolean">
  Whether the user must choose a value before submitting a form.
</ParamField>

<ParamField body="Root.style" type="CSSProperties | ((state: RadioRootState) => CSSProperties | undefined)">
  Style applied to the element, or a function that
  returns a style object based on the component's state.
</ParamField>

<ParamField body="Root.value" type="Value" required>
  The unique identifying value of the radio in a group.
</ParamField>

### Radio.Indicator

<ParamField body="Indicator.className" type="string | ((state: RadioIndicatorState) => string | undefined)">
  CSS class applied to the element, or a function that
  returns a class based on the component's state.
</ParamField>

<ParamField body="Indicator.keepMounted" type="boolean" default="false">
  Whether to keep the HTML element in the DOM when the radio button is inactive.
</ParamField>

<ParamField body="Indicator.render" type="ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, RadioIndicatorState>">
  Allows you to replace the component's HTML element
  with a different tag, or compose it with another component.

  Accepts a `ReactElement` or a function that returns the element to render.
</ParamField>

<ParamField body="Indicator.style" type="CSSProperties | ((state: RadioIndicatorState) => CSSProperties | undefined)">
  Style applied to the element, or a function that
  returns a style object based on the component's state.
</ParamField>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.