The Radio component renders a radio input, which allows selecting an option from a group of options. Usually you'll want to use the Radio component along with the RadioGroup component.

Importing

The component can be imported via:

import { Radio } from '@customerio/pluma-components/react';

Usage

A Radio is intended to be used within a RadioGroup. Outside of a RadioGroup, you will have to give the radio a name, provide an isChecked state, and add a handler (either onCheckedChange or onChange) to update the state.

import { Radio } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
  const [value, setValue] = useState('one');

  return <>
    <Radio
      value="one"
      name="group_one"
      label="Radio one"
      isChecked={value === 'one'}
      onCheckedChange={(checked, e) => checked && setValue(e.currentTarget.value)}
    />

    <Radio
      value="two"
      name="group_one"
      label="Radio two"
      isChecked={(value === 'two')}
      onCheckedChange={(checked, e) => checked && setValue(e.currentTarget.value)}
    />
  </>
}

More examples of available props:

<Radio
  value="enabled_radio"
  name="radio_group_name"
  label="Enabled radio"
  isChecked={true}
  onCheckedChange={() => {}}
/>

<Radio
  value="unchecked_radio"
  name="unchecked_radio_group_name"
  label="Unchecked radio"
  isChecked={false}
  onCheckedChange={() => {}}
/>

<Radio
  value="disabled_radio"
  name="disabled_radio_group_name"
  label="Disabled radio"
  isChecked={false}
  isDisabled={true}
  onCheckedChange={() => {}}
/>

Labels

A Radio must always have an associated label. The component accepts a label string prop, which renders the label next to the radio.

Alternatively, ariaLabelledby may be used to associate the radio with a label elsewhere in the DOM. To make sure the association is set up both ways, it's recommended to also set for on the label, pointing to an id on the radio:

import { Radio } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
  const [value, setValue] = useState('one');

  return <>
    <Box
      as="label"
      id="my_label_id_1"
      htmlFor="my_input_1"
    >
      Radio one label
    </Box>
    <Radio
      id="my_input_1"
      value="one"
      ariaLabelledby="my_label_id_1"
      isChecked={value === 'one'}
      onCheckedChange={(checked, e) => checked && setValue(e.currentTarget.value)}
      name="label_group"
    />

    <Box
      as="label"
      id="my_label_id_2"
      htmlFor="my_input_2"
    >
      Radio two label
    </Box>
    <Radio
      id="my_input_2"
      value="two"
      ariaLabelledby="my_label_id_2"
      isChecked={value === 'two'}
      onCheckedChange={(checked, e) => checked && setValue(e.currentTarget.value)}
      name="label_group"
    />
  </>
}

Finally, the ariaLabel prop can be used if no other element is suitable for a label.

Non-standard layouts

For certain use cases, when the default group layout isn't compatible with your needs, you may want to use individual Radio components without a RadioGroup.

For example, you may want to render Radios in a table:

Current value: 1
Layout nameLast updated
Layout 12024-03-01
Layout 22024-03-01
Layout 32024-03-01
import { Radio } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
  const [value, setValue] = useState('1');
  const layouts = [
    { id: '1', name: 'Layout 1', updated_at: '2024-03-01' },
    { id: '2', name: 'Layout 2', updated_at: '2024-03-01' },
    { id: '3', name: 'Layout 3', updated_at: '2024-03-01' }
  ];

return <>
    <Box>
      <Text>Current value: {value}</Text>
    </Box>
    <Box as="table" className="radio-table">
      <thead>
        <tr>
          <th></th>
          <th><Text>Layout name</Text></th>
          <th><Text>Last updated</Text></th>
        </tr>
      </thead>
      <tbody>
        {layouts.map((layout) => (
          <Box
            as="tr"
            onClick={() => setValue(layout.id)}
            key={layout.id}
            backgroundColor={layout.id === value ? 'accent-subtle' : undefined}
          >
            <td>
              <Radio
                isChecked={layout.id === value}
                onCheckedChange={(checked) => checked && setValue(layout.id)}
                name="layout_radios"
                value={layout.id}
                ariaLabelledby={layout.id + '_name'}
              />
            </td>
            <td id={layout.id + '_name'}><Text>{layout.name}</Text></td>
            <td><Text>{layout.updated_at}</Text></td>
          </Box>
        ))}
      </tbody>
    </Box>
  </>
}

Customizing labels

If a string label is not sufficient, a component can be used to render the label.

In React, the label prop accepts any ReactNode.

import { Radio, Icon } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
  const [isChecked, setIsChecked] = useState(false);

  return <Radio
    label={<>Custom label <Icon name="campaigns" isInline={true} size="sm" /></>}
    isChecked={isChecked}
    onCheckedChange={(checked) => {
      setIsChecked(checked);
    }}
  />
}

API

PlumaRadio extends Box

Element (or elements) that describe the form field. Also used for error messages, as aria-errormessage isn't supported in all assistive technologies: https://a11ysupport.io/tech/aria/aria-errormessage_attribute

String value that labels the form field.

Element (or elements) that label the form field.

A description for the field, providing additional context or hints.

An error message associated with the form field. Boolean: Ember-only. If the error named block is used, this prop can be set to true to indicate an error is present, which will make the error block render.

An optional id to be assigned to the input element. Also used to generate ids for labels and error messages. If one isn't provided, it will be generated.

Whether the radio is in the "checked" state. This argument is not required when inside a RadioGroup

Whether the form field is disabled.

Whether the form field is in an invalid state.

The label text to show with the form field.

The name shared by radios in the same group. Not required if inside a RadioGroup

The change event handler for the input element. A change event only fires on radio inputs when they are checked (and not when unchecked). In React, the event is a React SyntheticEvent; in Ember it's a native change event.

Whether to allow browser autofill and password managers. When false (default), adds attributes to disable password managers (1Password, LastPass, Bitwarden, Dashlane). Set to true for fields where autofill is desired (e.g., email, password, address fields).

Whether the generated description id should be included in the input's aria-describedby attribute. Disable this when a label`` tag surrounds the input as well as it's description text, for example in an OptionCard` component.

Whether the generated label id should be included in the input's aria-labelledby attribute. Disable this when the label tag surrounds the input as well as it's label text, for example in an OptionCard component.

Whether the label text should be truncated (with ellipsis) if it overflows.

By default, form elements will throw an error if no label or aria-label is provided. Disable this to suppress the error, for example when building a custom form element, and you want to handle labeling yourself.

Additional classes to be applied to the description element.

Additional classes to be applied to the error element.

Additional classes to be applied to the field node.

Additional classes to be applied to the footer element.

Ember-only: this is used internally to detect whether a label named block is present, and to ignore empty label props if so.

Additional classes to be applied to the input node.

Sets the size attribute on the input element. This controls the visible width of the input in characters.

Whether the component should render with "legacy" styles.

Additional classes to be applied to the label node.

The value associated with this radio

Whether to apply the center-baseline variant.