or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-react-aria--radio

React hooks for building accessible radio buttons and radio groups with WAI-ARIA compliance.

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/@react-aria/radio@3.12.x

To install, run

npx @tessl/cli install tessl/npm-react-aria--radio@3.12.0

index.mddocs/

@react-aria/radio

@react-aria/radio provides React hooks for building accessible radio buttons and radio groups with full WAI-ARIA compliance. It implements the Radio Group pattern with complete keyboard navigation, screen reader support, and mouse/touch interactions while remaining completely unstyled to allow custom styling.

Package Information

  • Package Name: @react-aria/radio
  • Package Type: npm
  • Language: TypeScript
  • Installation: npm install @react-aria/radio

Core Imports

import { useRadio, useRadioGroup } from "@react-aria/radio";
import type { AriaRadioProps, AriaRadioGroupProps, RadioAria, RadioGroupAria, Orientation } from "@react-aria/radio";

For CommonJS:

const { useRadio, useRadioGroup } = require("@react-aria/radio");

Basic Usage

import { useRadio, useRadioGroup } from "@react-aria/radio";
import { useRadioGroupState } from "@react-stately/radio"; // Required peer dependency
import { useRef } from "react";

// Radio Group Component
function MyRadioGroup(props) {
  const state = useRadioGroupState(props);
  const { radioGroupProps, labelProps } = useRadioGroup(props, state);

  return (
    <div {...radioGroupProps}>
      <span {...labelProps}>{props.label}</span>
      {props.children}
    </div>
  );
}

// Individual Radio Component
function MyRadio(props) {
  const ref = useRef();
  const state = useRadioGroupState(props.groupProps);
  const { inputProps, labelProps, isSelected, isDisabled } = useRadio(props, state, ref);

  return (
    <label {...labelProps}>
      <input {...inputProps} ref={ref} />
      {props.children}
    </label>
  );
}

// Usage
<MyRadioGroup label="Favorite pet" value={selectedPet} onChange={setSelectedPet}>
  <MyRadio value="dogs">Dogs</MyRadio>
  <MyRadio value="cats">Cats</MyRadio>
  <MyRadio value="birds">Birds</MyRadio>
</MyRadioGroup>

Architecture

@react-aria/radio is built around the separation of behavior and presentation:

  • Behavior Layer: The hooks provide all accessibility, keyboard navigation, focus management, and interaction logic
  • State Management: Integrates with @react-stately/radio for radio group state management
  • Unstyled Approach: No CSS or styling is provided, allowing complete visual customization
  • ARIA Compliance: Full implementation of WAI-ARIA Radio Group pattern
  • Form Integration: Native HTML form support with validation

Capabilities

Radio Group Management

Provides behavior and accessibility implementation for radio group components that allow users to select a single item from mutually exclusive options.

/**
 * Provides the behavior and accessibility implementation for a radio group component.
 * @param props - Props for the radio group
 * @param state - State for the radio group, as returned by useRadioGroupState
 * @returns RadioGroupAria object with props and validation state
 */
function useRadioGroup(props: AriaRadioGroupProps, state: RadioGroupState): RadioGroupAria;

interface AriaRadioGroupProps extends RadioGroupProps, InputDOMProps, DOMProps, AriaLabelingProps, AriaValidationProps {
  /** The axis the radio buttons should align with */
  orientation?: Orientation;
  /** The name of the radio group for form submission */
  name?: string;
  /** Whether the radio group is disabled */
  isDisabled?: boolean;
  /** Whether the radio group is read-only */
  isReadOnly?: boolean;
  /** Whether the radio group is required */
  isRequired?: boolean;
  /** Validation behavior for the radio group */
  validationBehavior?: 'aria' | 'native';
  /** Handler called when the radio group receives focus */
  onFocus?: (e: FocusEvent) => void;
  /** Handler called when the radio group loses focus */
  onBlur?: (e: FocusEvent) => void;
  /** Handler called when the focus changes within the radio group */
  onFocusChange?: (isFocused: boolean) => void;
}

interface RadioGroupAria extends ValidationResult {
  /** Props for the radio group wrapper element */
  radioGroupProps: DOMAttributes;
  /** Props for the radio group's visible label (if any) */
  labelProps: DOMAttributes;
  /** Props for the radio group description element, if any */
  descriptionProps: DOMAttributes;
  /** Props for the radio group error message element, if any */
  errorMessageProps: DOMAttributes;
}

Usage Example:

import { useRadioGroup } from "@react-aria/radio";
import { useRadioGroupState } from "@react-stately/radio";

function RadioGroup({ label, children, ...props }) {
  const state = useRadioGroupState(props);
  const {
    radioGroupProps,
    labelProps,
    descriptionProps,
    errorMessageProps,
    isInvalid,
    validationErrors
  } = useRadioGroup(props, state);

  return (
    <div {...radioGroupProps}>
      <span {...labelProps}>{label}</span>
      {props.description && <div {...descriptionProps}>{props.description}</div>}
      {children}
      {isInvalid && <div {...errorMessageProps}>{validationErrors.join(' ')}</div>}
    </div>
  );
}

Individual Radio Button Management

Provides behavior and accessibility implementation for individual radio buttons within a radio group.

/**
 * Provides the behavior and accessibility implementation for an individual radio button.
 * @param props - Props for the radio
 * @param state - State for the radio group, as returned by useRadioGroupState
 * @param ref - Ref to the HTML input element
 * @returns RadioAria object with props and state
 */
function useRadio(props: AriaRadioProps, state: RadioGroupState, ref: RefObject<HTMLInputElement | null>): RadioAria;

interface AriaRadioProps extends RadioProps, DOMProps, AriaLabelingProps, PressEvents {
  /** The value of the radio button, used when submitting an HTML form */
  value: string;
  /** The label for the radio. Accepts any renderable node */
  children?: ReactNode;
  /** Whether the radio button is disabled */
  isDisabled?: boolean;
  /** Handler called when the radio is pressed */
  onPress?: (e: PressEvent) => void;
  /** Handler called when a press interaction starts */
  onPressStart?: (e: PressEvent) => void;
  /** Handler called when a press interaction ends */
  onPressEnd?: (e: PressEvent) => void;
  /** Handler called when the press state changes */
  onPressChange?: (isPressed: boolean) => void;
  /** Handler called when a press is released over the target */
  onPressUp?: (e: PressEvent) => void;
  /** Handler called when the element is clicked */
  onClick?: (e: MouseEvent) => void;
}

interface RadioAria {
  /** Props for the label wrapper element */
  labelProps: LabelHTMLAttributes<HTMLLabelElement>;
  /** Props for the input element */
  inputProps: InputHTMLAttributes<HTMLInputElement>;
  /** Whether the radio is disabled */
  isDisabled: boolean;
  /** Whether the radio is currently selected */
  isSelected: boolean;
  /** Whether the radio is in a pressed state */
  isPressed: boolean;
}

Usage Example:

import { useRadio } from "@react-aria/radio";
import { useRef } from "react";

function Radio({ children, ...props }) {
  const ref = useRef();
  const { inputProps, labelProps, isSelected, isDisabled, isPressed } = useRadio(props, state, ref);

  return (
    <label
      {...labelProps}
      className={`radio ${isSelected ? 'selected' : ''} ${isDisabled ? 'disabled' : ''}`}
    >
      <input {...inputProps} ref={ref} />
      <span className="radio-indicator" />
      {children}
    </label>
  );
}

Types

/** Layout orientation for radio group */
type Orientation = 'horizontal' | 'vertical';

/** React node type */
type ReactNode = React.ReactNode;

/** Mouse event type */
type MouseEvent = React.MouseEvent;

/** State management for radio groups from @react-stately/radio */
interface RadioGroupState {
  /** Currently selected value */
  selectedValue: string | null;
  /** Default selected value */
  defaultSelectedValue: string | null;
  /** Last focused value for keyboard navigation */
  lastFocusedValue: string | null;
  /** Whether the radio group is disabled */
  isDisabled: boolean;
  /** Whether the radio group is read-only */
  isReadOnly: boolean;
  /** Whether selection is required */
  isRequired: boolean;
  /** Whether the radio group is invalid */
  isInvalid: boolean;
  /** Current validation state */
  displayValidation: ValidationResult;
  /** Set the selected value */
  setSelectedValue: (value: string) => void;
  /** Set the last focused value */
  setLastFocusedValue: (value: string | null) => void;
}

/** Base props for radio groups */
interface RadioGroupProps {
  /** Currently selected value */
  value?: string | null;
  /** Default selected value for uncontrolled components */
  defaultValue?: string | null;
  /** Handler called when selection changes */
  onChange?: (value: string) => void;
}

/** Base props for individual radio buttons */
interface RadioProps {
  /** The value of the radio button */
  value: string;
  /** The label content */
  children?: ReactNode;
  /** Whether the radio is disabled */
  isDisabled?: boolean;
}

/** DOM input properties */
interface InputDOMProps {
  /** Form name attribute */
  name?: string;
  /** Form element */
  form?: string;
}

/** Validation properties for ARIA */
interface AriaValidationProps {
  /** Whether validation is required */
  isRequired?: boolean;
  /** Validation behavior */
  validationBehavior?: 'aria' | 'native';
  /** Custom validation function */
  validate?: (value: string | null) => ValidationError | true | null | undefined;
  /** Error message */
  errorMessage?: string | ((validation: ValidationResult) => string);
}

/** ARIA labeling properties */
interface AriaLabelingProps {
  /** Accessible label */
  'aria-label'?: string;
  /** ID of element that labels this element */
  'aria-labelledby'?: string;
  /** ID of element that describes this element */
  'aria-describedby'?: string;
}

/** DOM properties */
interface DOMProps {
  /** Element ID */
  id?: string;
}

/** Press event handlers */
interface PressEvents {
  /** Handler called when a press interaction starts */
  onPressStart?: (e: PressEvent) => void;
  /** Handler called when a press interaction ends */
  onPressEnd?: (e: PressEvent) => void;
  /** Handler called when the press state changes */
  onPressChange?: (isPressed: boolean) => void;
  /** Handler called when a press is released over the target */
  onPressUp?: (e: PressEvent) => void;
  /** Handler called when the element is pressed */
  onPress?: (e: PressEvent) => void;
}

/** Validation result containing error information */
interface ValidationResult {
  /** Whether the input is invalid */
  isInvalid: boolean;
  /** Array of validation error messages */
  validationErrors: string[];
  /** Detailed validation information */
  validationDetails: ValidationDetails;
}

/** Detailed validation information */
interface ValidationDetails {
  /** Validation errors from form constraints */
  formErrors: string[];
  /** Validation errors from custom validation */
  validationErrors: string[];
}

/** Validation error */
interface ValidationError {
  /** Error message */
  message: string;
}

/** DOM attributes for elements */
interface DOMAttributes {
  [key: string]: any;
}

/** Press event information */
interface PressEvent {
  /** The type of press event */
  type: 'pressstart' | 'pressend' | 'pressup' | 'press';
  /** The pointer type that triggered the press event */
  pointerType: 'mouse' | 'pen' | 'touch' | 'keyboard' | 'virtual';
  /** The target element of the press event */
  target: Element;
  /** Whether the shift key was held during the press event */
  shiftKey: boolean;
  /** Whether the ctrl key was held during the press event */
  ctrlKey: boolean;
  /** Whether the meta key was held during the press event */
  metaKey: boolean;
  /** Whether the alt key was held during the press event */
  altKey: boolean;
}

/** React ref object */
interface RefObject<T> {
  readonly current: T | null;
}

/** HTML element attributes */
interface HTMLAttributes<T> {
  [key: string]: any;
}

/** HTML label element attributes */
interface LabelHTMLAttributes<T> extends HTMLAttributes<T> {
  htmlFor?: string;
}

/** HTML input element attributes */
interface InputHTMLAttributes<T> extends HTMLAttributes<T> {
  type?: string;
  name?: string;
  value?: string | ReadonlyArray<string> | number;
  checked?: boolean;
  disabled?: boolean;
  required?: boolean;
  tabIndex?: number;
}

Error Handling

The hooks handle validation and error states through the ValidationResult interface:

  • isInvalid: Boolean indicating validation failure
  • validationErrors: Array of error messages for display
  • validationDetails: Detailed validation information

Common validation scenarios include:

  • Required radio group with no selection
  • Custom validation rules through the validate prop
  • Form constraint validation

Integration Requirements

@react-aria/radio is designed to work in conjunction with @react-stately/radio for state management. This separation allows for flexible state handling while the aria package focuses purely on accessibility and behavior.

Installation:

npm install @react-aria/radio @react-stately/radio

Basic state setup:

import { useRadioGroupState, type RadioGroupState } from "@react-stately/radio";

const state = useRadioGroupState({
  value: selectedValue,
  onChange: setSelectedValue,
  defaultValue: 'initial',
  isDisabled: false,
  isRequired: true
});

Required peer dependencies:

  • @react-stately/radio - Provides RadioGroupState and useRadioGroupState hook
  • react - React 16.8+ with hooks support (required for hook functionality)
  • react-dom - DOM utilities and event handling

The package integrates seamlessly with HTML forms and supports both controlled and uncontrolled usage patterns. The aria hooks handle all accessibility concerns while state management is handled separately by the stately package.