Radio Group Primitive
A group of selectable buttons, commonly referred to as radio buttons, wherein only one button can be checked simultaneously.
Installation
Section titled “Installation”Install the component via your command line.
npx expo install @rn-primitives/radio-group
Install @radix-ui/react-radio-group
npx expo install @radix-ui/react-radio-group
Copy/paste the following code for web to ~/components/primitives/radio-group/radio-group.web.tsx
import * as RadioGroup from '@radix-ui/react-radio-group';import * as Slot from '~/components/primitives/slot';import * as React from 'react';import { GestureResponderEvent, Pressable, View } from 'react-native';import type { IndicatorProps, IndicatorRef, ItemProps, ItemRef, RootProps, RootRef } from './types';
const RadioGroupContext = React.createContext<RootProps | null>(null);
const Root = React.forwardRef<RootRef, RootProps>( ({ asChild, value, onValueChange, disabled = false, ...viewProps }, ref) => { const Component = asChild ? Slot.View : View; return ( <RadioGroupContext.Provider value={{ value, disabled, onValueChange, }} > <RadioGroup.Root value={value} onValueChange={onValueChange} disabled={disabled} asChild> <Component ref={ref} {...viewProps} /> </RadioGroup.Root> </RadioGroupContext.Provider> ); });
Root.displayName = 'RootRadioGroup';function useRadioGroupContext() { const context = React.useContext(RadioGroupContext); if (!context) { throw new Error( 'RadioGroup compound components cannot be rendered outside the RadioGroup component' ); } return context;}const Item = React.forwardRef<ItemRef, ItemProps>( ({ asChild, value, onPress: onPressProps, ...props }, ref) => { const { onValueChange } = useRadioGroupContext();
function onPress(ev: GestureResponderEvent) { if (onPressProps) { onPressProps(ev); } onValueChange(value); }
const Component = asChild ? Slot.Pressable : Pressable; return ( <RadioGroup.Item value={value} asChild> <Component ref={ref} onPress={onPress} {...props} /> </RadioGroup.Item> ); });
Item.displayName = 'ItemRadioGroup';
const Indicator = React.forwardRef<IndicatorRef, IndicatorProps>( ({ asChild, forceMount, ...props }, ref) => { const Component = asChild ? Slot.View : View; return ( <RadioGroup.Indicator asChild> <Component ref={ref} {...props} /> </RadioGroup.Indicator> ); });
Indicator.displayName = 'IndicatorRadioGroup';
export { Indicator, Item, Root };
Copy/paste the following code for native to ~/components/primitives/radio-group/radio-group.tsx
import * as React from 'react';import { Pressable, View, type GestureResponderEvent } from 'react-native';import * as Slot from '~/components/primitives/slot';import type { IndicatorProps, IndicatorRef, ItemProps, ItemRef, RootProps, RootRef } from './types';
const RadioGroupContext = React.createContext<RootProps | null>(null);
const Root = React.forwardRef<RootRef, RootProps>( ({ asChild, value, onValueChange, disabled = false, ...viewProps }, ref) => { const Component = asChild ? Slot.View : View; return ( <RadioGroupContext.Provider value={{ value, disabled, onValueChange, }} > <Component ref={ref} role='radiogroup' {...viewProps} /> </RadioGroupContext.Provider> ); });
Root.displayName = 'RootRadioGroup';
function useRadioGroupContext() { const context = React.useContext(RadioGroupContext); if (!context) { throw new Error( 'RadioGroup compound components cannot be rendered outside the RadioGroup component' ); } return context;}
interface RadioItemContext { itemValue: string | undefined;}
const RadioItemContext = React.createContext<RadioItemContext | null>(null);
const Item = React.forwardRef<ItemRef, ItemProps>( ( { asChild, value: itemValue, disabled: disabledProp = false, onPress: onPressProp, ...props }, ref ) => { const { disabled, value, onValueChange } = useRadioGroupContext();
function onPress(ev: GestureResponderEvent) { if (disabled || disabledProp) return; onValueChange(itemValue); onPressProp?.(ev); }
const Component = asChild ? Slot.Pressable : Pressable; return ( <RadioItemContext.Provider value={{ itemValue: itemValue, }} > <Component ref={ref} role='radio' onPress={onPress} aria-checked={value === itemValue} disabled={(disabled || disabledProp) ?? false} accessibilityState={{ disabled: (disabled || disabledProp) ?? false, checked: value === itemValue, }} {...props} /> </RadioItemContext.Provider> ); });
Item.displayName = 'ItemRadioGroup';
function useRadioItemContext() { const context = React.useContext(RadioItemContext); if (!context) { throw new Error( 'RadioItem compound components cannot be rendered outside the RadioItem component' ); } return context;}
const Indicator = React.forwardRef<IndicatorRef, IndicatorProps>( ({ asChild, forceMount, ...props }, ref) => { const { value } = useRadioGroupContext(); const { itemValue } = useRadioItemContext();
if (!forceMount) { if (value !== itemValue) { return null; } } const Component = asChild ? Slot.View : View; return <Component ref={ref} role='presentation' {...props} />; });
Indicator.displayName = 'IndicatorRadioGroup';
export { Indicator, Item, Root };
Copy/paste the following code for types to ~/components/primitives/radio-group/types.ts
import { ForceMountable, PressableRef, SlottablePressableProps, SlottableViewProps, ViewRef,} from '~/components/primitives/types';
type RootProps = SlottableViewProps & { value: string | undefined; onValueChange: (val: string) => void; disabled?: boolean;};
type ItemProps = SlottablePressableProps & { value: string; /** * nativeID of the label element that describes this radio group item */ 'aria-labelledby'?: string;};
type IndicatorProps = SlottableViewProps & ForceMountable;
type RootRef = ViewRef;type ItemRef = PressableRef;type IndicatorRef = ViewRef;
export type { IndicatorProps, IndicatorRef, ItemProps, ItemRef, RootProps, RootRef };
Copy/paste the following code for exporting to ~/components/primitives/radio-group/index.ts
export * from './radio-group';export * from './types';
Copy/paste the following code for native to ~/components/primitives/radio-group/index.tsx
import * as React from 'react';import { Pressable, View, type GestureResponderEvent } from 'react-native';import * as Slot from '~/components/primitives/slot';import type { IndicatorProps, IndicatorRef, ItemProps, ItemRef, RootProps, RootRef } from './types';
const RadioGroupContext = React.createContext<RootProps | null>(null);
const Root = React.forwardRef<RootRef, RootProps>( ({ asChild, value, onValueChange, disabled = false, ...viewProps }, ref) => { const Component = asChild ? Slot.View : View; return ( <RadioGroupContext.Provider value={{ value, disabled, onValueChange, }} > <Component ref={ref} role='radiogroup' {...viewProps} /> </RadioGroupContext.Provider> ); });
Root.displayName = 'RootRadioGroup';
function useRadioGroupContext() { const context = React.useContext(RadioGroupContext); if (!context) { throw new Error( 'RadioGroup compound components cannot be rendered outside the RadioGroup component' ); } return context;}
interface RadioItemContext { itemValue: string | undefined;}
const RadioItemContext = React.createContext<RadioItemContext | null>(null);
const Item = React.forwardRef<ItemRef, ItemProps>( ( { asChild, value: itemValue, disabled: disabledProp = false, onPress: onPressProp, ...props }, ref ) => { const { disabled, value, onValueChange } = useRadioGroupContext();
function onPress(ev: GestureResponderEvent) { if (disabled || disabledProp) return; onValueChange(itemValue); onPressProp?.(ev); }
const Component = asChild ? Slot.Pressable : Pressable; return ( <RadioItemContext.Provider value={{ itemValue: itemValue, }} > <Component ref={ref} role='radio' onPress={onPress} aria-checked={value === itemValue} disabled={(disabled || disabledProp) ?? false} accessibilityState={{ disabled: (disabled || disabledProp) ?? false, checked: value === itemValue, }} {...props} /> </RadioItemContext.Provider> ); });
Item.displayName = 'ItemRadioGroup';
function useRadioItemContext() { const context = React.useContext(RadioItemContext); if (!context) { throw new Error( 'RadioItem compound components cannot be rendered outside the RadioItem component' ); } return context;}
const Indicator = React.forwardRef<IndicatorRef, IndicatorProps>( ({ asChild, forceMount, ...props }, ref) => { const { value } = useRadioGroupContext(); const { itemValue } = useRadioItemContext();
if (!forceMount) { if (value !== itemValue) { return null; } } const Component = asChild ? Slot.View : View; return <Component ref={ref} role='presentation' {...props} />; });
Indicator.displayName = 'IndicatorRadioGroup';
export { Indicator, Item, Root };
Copy/paste the following code for types to ~/components/primitives/radio-group/types.ts
import { ForceMountable, PressableRef, SlottablePressableProps, SlottableViewProps, ViewRef,} from '~/components/primitives/types';
type RootProps = SlottableViewProps & { value: string | undefined; onValueChange: (val: string) => void; disabled?: boolean;};
type ItemProps = SlottablePressableProps & { value: string; /** * nativeID of the label element that describes this radio group item */ 'aria-labelledby'?: string;};
type IndicatorProps = SlottableViewProps & ForceMountable;
type RootRef = ViewRef;type ItemRef = PressableRef;type IndicatorRef = ViewRef;
export type { IndicatorProps, IndicatorRef, ItemProps, ItemRef, RootProps, RootRef };
import * as React from 'react';import * as RadioGroupPrimitive from '@rn-primitives/radio-group';import { Text, View } from 'react-native';
function Example() { const [value, setValue] = React.useState('Comfortable');
function onLabelPress(label: string) { return () => { setValue(label); }; } return ( <RadioGroupPrimitive.Root value={value} onValueChange={setValue}> <View> <RadioGroupPrimitive.Item value='Default' aria-labelledby='default-label'> <RadioGroupPrimitive.Indicator /> </RadioGroupPrimitive.Item> <Text nativeID='default-label' onPress={onLabelPress('Default')}>Default</Text> </View> <View> <RadioGroupPrimitive.Item value='Comfortable' aria-labelledby='comfortable-label'> <RadioGroupPrimitive.Indicator /> </RadioGroupPrimitive.Item> <Text nativeID='comfortable-label' onPress={onLabelPress('Comfortable')}>Comfortable</Text> </View> <View> <RadioGroupPrimitive.Item value='Compact' aria-labelledby='compact-label'> <RadioGroupPrimitive.Indicator /> </RadioGroupPrimitive.Item> <Text nativeID='compact-label' onPress={onLabelPress('Compact')}>Compact</Text> </View> </RadioGroupPrimitive.Root> );}
Extends View
props
Prop | Type | Note |
---|---|---|
value | string | undefined | |
onValueChange | (val: string) => void | |
asChild | boolean | (optional) |
disabled | boolean | (optional) |
Extends Pressable
props
Prop | Type | Note |
---|---|---|
value | string | |
aria-labelledby | string | Its label’s nativeID |
asChild | boolean | (optional) |
Indicator
Section titled “Indicator”Extends View
props
Prop | Type | Note |
---|---|---|
forceMount | boolean | (optional) |
asChild | boolean | (optional) |