Hanzo GUI

RadioGroup

Use in a form to allow selecting one option from multiple

StyledUnstyledHeadless

`

`

`

  • Accessible, easy to compose and customize.
  • Sizable & works controlled or uncontrolled.
  • Ability to opt-out to native radio button on web.

Installation

RadioGroup is already installed in @hanzo/gui, or you can install it independently:

npm install @hanzogui/radio-group

RadioGroup is already installed in @hanzo/gui, or you can install it independently:

npm install @hanzogui/radio-group

To use the headless radio group, import from the @hanzogui/radio-headless package. This package has no dependency on @hanzogui/core and provides hooks for building custom radio groups with any styling solution.

npm install @hanzogui/radio-headless

Usage

import { RadioGroup } from '@hanzo/gui'

export default () => (
  <RadioGroup value="foo" gap="$2">
    <RadioGroup.Item value="foo" id="foo-radio-item">
      <RadioGroup.Indicator />
    </RadioGroup.Item>
    <RadioGroup.Item value="bar" id="bar-radio-item">
      <RadioGroup.Indicator />
    </RadioGroup.Item>
  </RadioGroup>
)

Use the createRadioGroup export to create a fully custom radio group that still uses the Hanzo GUI styling system. You provide your own styled components and get back a fully functional radio group component.

import { createRadioGroup, RadioGroupContext } from '@hanzogui/radio-group'
import { styled, View } from '@hanzo/gui'

const CustomFrame = styled(View, {
  // your styles
})

const CustomItem = styled(View, {
  context: RadioGroupContext,
  // your styles
})

const CustomIndicator = styled(View, {
  context: RadioGroupContext,
  // your styles
})

export const CustomRadioGroup = createRadioGroup({
  Frame: CustomFrame,
  Item: CustomItem,
  Indicator: CustomIndicator,
})

The @hanzogui/radio-headless package provides three hooks for building custom radio groups:

  • useRadioGroup - for the container/group
  • useRadioGroupItem - for individual radio items
  • useRadioGroupItemIndicator - for the indicator (checked state visual)

Basic Usage

import {
  useRadioGroup,
  useRadioGroupItem,
  useRadioGroupItemIndicator,
  RadioGroupContextValue,
  RadioGroupItemContextValue,
} from '@hanzogui/radio-headless'
import { createContext, useContext } from 'react'

// Create contexts for the group and item
const RadioGroupContext = createContext<RadioGroupContextValue>({})
const RadioGroupItemContext = createContext<RadioGroupItemContextValue>({
  checked: false,
})

function RadioGroup({ children, ...props }) {
  const { providerValue, frameAttrs } = useRadioGroup({
    orientation: 'vertical',
    ...props,
  })

  return (
    <RadioGroupContext.Provider value={providerValue}>
      <div {...frameAttrs} style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {children}
      </div>
    </RadioGroupContext.Provider>
  )
}

function RadioItem({ value, children }) {
  const { checked, providerValue, frameAttrs, bubbleInput } = useRadioGroupItem({
    radioGroupContext: RadioGroupContext,
    value,
  })

  return (
    <RadioGroupItemContext.Provider value={providerValue}>
      <button
        {...frameAttrs}
        style={{
          display: 'flex',
          alignItems: 'center',
          gap: 8,
          padding: 8,
          border: '1px solid #ccc',
          borderRadius: 4,
          background: checked ? '#e0f2fe' : 'white',
        }}
      >
        <RadioIndicator />
        {children}
      </button>
      {bubbleInput}
    </RadioGroupItemContext.Provider>
  )
}

function RadioIndicator() {
  const { checked, ...indicatorProps } = useRadioGroupItemIndicator({
    radioGroupItemContext: RadioGroupItemContext,
  })

  return (
    <div
      {...indicatorProps}
      style={{
        width: 16,
        height: 16,
        borderRadius: '50%',
        border: '2px solid #3b82f6',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
      }}
    >
      {checked && (
        <div
          style={{ width: 8, height: 8, borderRadius: '50%', background: '#3b82f6' }}
        />
      )}
    </div>
  )
}

// Usage
function App() {
  return (
    <RadioGroup defaultValue="option1" onValueChange={console.log}>
      <RadioItem value="option1">Option 1</RadioItem>
      <RadioItem value="option2">Option 2</RadioItem>
      <RadioItem value="option3">Option 3</RadioItem>
    </RadioGroup>
  )
}

API Reference

RadioGroup

RadioGroup extends Stack views inheriting all the Gui standard props, plus:

When using createRadioGroup, you provide styled components:

  • Frame: The root container component
  • Item: The radio item component
  • Indicator: The indicator component

The useRadioGroup hook accepts these options:

PropTypeDefaultRequired
namestring--
valuestring--
defaultValuestring--
requiredboolean--
disabledboolean--
nativebooleanfalse-
onValueChange(value: string) => void--
orientation"horizontal" | "vertical"--
accentColorstring--

RadioGroup.Item

PropTypeDefaultRequired
labeledBystring--
valuestring--
disabledboolean--
idstring--
scaleSizenumber0.5-
unstyledbooleanfalse-

RadioGroup.Indicator

RadioGroup.Indicator appears only when the parent Item is checked. It extends ThemeableStack, getting Gui standard props adding:

PropTypeDefaultRequired
unstyledboolean--

useRadioGroup Return Value

PropertyTypeDescription
providerValueRadioGroupContextValueValue to pass to your context provider
frameAttrsobjectProps to spread on the group container (role, aria-orientation, etc.)
rovingFocusGroupAttrsobjectProps for roving focus behavior (optional)

useRadioGroupItem

Hook for individual radio items. Requires a context containing the group state.

const {
  checked,
  isFormControl,
  providerValue,
  bubbleInput,
  native,
  frameAttrs,
  rovingFocusGroupAttrs,
} = useRadioGroupItem({
  radioGroupContext: YourRadioGroupContext,
  value: 'option1',
  id: 'option1-id',
  labelledBy: 'label-id',
  disabled: false,
  onPress: () => {},
  onKeyDown: () => {},
  onFocus: () => {},
})
PropertyTypeDescription
checkedbooleanWhether this item is currently selected
providerValueobjectContext value for the indicator
frameAttrsobjectProps to spread on the item element
bubbleInputReactNodeHidden input for form compatibility
nativebooleanWhether using native radio
isFormControlbooleanWhether inside a form

useRadioGroupItemIndicator

Hook for the visual indicator of checked state.

const { checked, ...dataAttrs } = useRadioGroupItemIndicator({
  radioGroupItemContext: YourRadioGroupItemContext,
  disabled: false,
})
PropertyTypeDescription
checkedbooleanWhether the parent item is checked
data-statestring'checked' or 'unchecked'
data-disabledstring | undefinedPresent when disabled

Last updated on

On this page