Toggle Group Primitive
A collection of buttons with true or false states, which can be activated or deactivated by toggling.
Installation
Install the component via your command line.
npx expo install @rn-primitives/toggle-group
Install @radix-ui/react-toggle-group
npx expo install @radix-ui/react-toggle-group
Copy/paste the following code for web to ~/components/primitives/toggle-group/toggle-group.web.tsx
import * as ToggleGroup from '@radix-ui/react-toggle-group';import * as Slot from '~/components/primitives/slot';import { ToggleGroupUtils } from '~/components/primitives/utils';import * as React from 'react';import { Pressable, View, type GestureResponderEvent } from 'react-native';import type { ItemProps, ItemRef, RootProps, RootRef } from './types';
const ToggleGroupContext = React.createContext<RootProps | null>(null);
const Root = React.forwardRef<RootRef, RootProps>( ( { asChild, type, value, onValueChange, disabled = false, rovingFocus, orientation, dir, loop, ...viewProps }, ref ) => { const Component = asChild ? Slot.View : View; return ( <ToggleGroupContext.Provider value={ { type, value, disabled, onValueChange, } as RootProps } > <ToggleGroup.Root type={type as any} value={value as any} onValueChange={onValueChange as any} disabled={disabled} rovingFocus={rovingFocus} orientation={orientation} dir={dir} loop={loop} asChild > <Component ref={ref} {...viewProps} /> </ToggleGroup.Root> </ToggleGroupContext.Provider> ); });
Root.displayName = 'RootToggleGroup';
function useRootContext() { const context = React.useContext(ToggleGroupContext); if (!context) { throw new Error( 'ToggleGroup compound components cannot be rendered outside the ToggleGroup component' ); } return context;}
const ItemContext = React.createContext<ItemProps | null>(null);
const Item = React.forwardRef<ItemRef, ItemProps>( ( { asChild, value: itemValue, disabled: disabledProp = false, onPress: onPressProp, ...props }, ref ) => { const { type, disabled, value, onValueChange } = useRootContext();
function onPress(ev: GestureResponderEvent) { onPressProp?.(ev); if (type === 'single') { onValueChange(ToggleGroupUtils.getNewSingleValue(value, itemValue)); } if (type === 'multiple') { onValueChange(ToggleGroupUtils.getNewMultipleValue(value, itemValue)); } }
const Component = asChild ? Slot.Pressable : Pressable; return ( <ItemContext.Provider value={{ value: itemValue }}> <ToggleGroup.Item value={itemValue} asChild> <Component ref={ref} onPress={onPress} disabled={disabled || disabledProp} role='button' {...props} /> </ToggleGroup.Item> </ItemContext.Provider> ); });
Item.displayName = 'ItemToggleGroup';
function useItemContext() { const context = React.useContext(ItemContext); if (!context) { throw new Error( 'ToggleGroupItem compound components cannot be rendered outside the ToggleGroupItem component' ); } return context;}
const utils = ToggleGroupUtils;
export { Item, Root, useItemContext, useRootContext, utils };
Copy/paste the following code for native to ~/components/primitives/toggle-group/toggle-group.tsx
import * as Slot from '~/components/primitives/slot';import { ToggleGroupUtils } from '~/components/primitives/utils';import * as React from 'react';import { Pressable, View, type GestureResponderEvent } from 'react-native';import type { ItemProps, ItemRef, RootProps, RootRef } from './types';
const ToggleGroupContext = React.createContext<RootProps | null>(null);
const Root = React.forwardRef<RootRef, RootProps>( ( { asChild, type, value, onValueChange, disabled = false, rovingFocus: _rovingFocus, orientation: _orientation, dir: _dir, loop: _loop, ...viewProps }, ref ) => { const Component = asChild ? Slot.View : View; return ( <ToggleGroupContext.Provider value={ { type, value, disabled, onValueChange, } as RootProps } > <Component ref={ref} role='group' {...viewProps} /> </ToggleGroupContext.Provider> ); });
Root.displayName = 'RootToggleGroup';
function useRootContext() { const context = React.useContext(ToggleGroupContext); if (!context) { throw new Error( 'ToggleGroup compound components cannot be rendered outside the ToggleGroup component' ); } return context;}
const ItemContext = React.createContext<ItemProps | null>(null);
const Item = React.forwardRef<ItemRef, ItemProps>( ( { asChild, value: itemValue, disabled: disabledProp = false, onPress: onPressProp, ...props }, ref ) => { const id = React.useId(); const { type, disabled, value, onValueChange } = useRootContext();
function onPress(ev: GestureResponderEvent) { if (disabled || disabledProp) return; if (type === 'single') { onValueChange(ToggleGroupUtils.getNewSingleValue(value, itemValue)); } if (type === 'multiple') { onValueChange(ToggleGroupUtils.getNewMultipleValue(value, itemValue)); } onPressProp?.(ev); }
const isChecked = type === 'single' ? ToggleGroupUtils.getIsSelected(value, itemValue) : undefined; const isSelected = type === 'multiple' ? ToggleGroupUtils.getIsSelected(value, itemValue) : undefined;
const Component = asChild ? Slot.Pressable : Pressable; return ( <ItemContext.Provider value={{ value: itemValue }}> <Component ref={ref} aria-disabled={disabled} role={type === 'single' ? 'radio' : 'checkbox'} onPress={onPress} aria-checked={isChecked} aria-selected={isSelected} disabled={(disabled || disabledProp) ?? false} accessibilityState={{ disabled: (disabled || disabledProp) ?? false, checked: isChecked, selected: isSelected, }} {...props} /> </ItemContext.Provider> ); });
Item.displayName = 'ItemToggleGroup';
function useItemContext() { const context = React.useContext(ItemContext); if (!context) { throw new Error( 'ToggleGroupItem compound components cannot be rendered outside the ToggleGroupItem component' ); } return context;}
const utils = ToggleGroupUtils;
export { Item, Root, useItemContext, useRootContext, utils };
Copy/paste the following code for types to ~/components/primitives/toggle-group/types.ts
import { PressableRef, SlottablePressableProps, SlottableViewProps, ViewRef,} from '~/components/primitives/types';
type SingleRootProps = { type: 'single'; value: string | undefined; onValueChange: (val: string | undefined) => void;};
type MultipleRootProps = { type: 'multiple'; value: string[]; onValueChange: (val: string[]) => void;};
type RootProps = (SingleRootProps | MultipleRootProps) & { disabled?: boolean; /** * Platform: WEB ONLY */ rovingFocus?: boolean; /** * Platform: WEB ONLY */ orientation?: 'horizontal' | 'vertical'; /** * Platform: WEB ONLY */ dir?: 'ltr' | 'rtl'; /** * Platform: WEB ONLY */ loop?: boolean;} & SlottableViewProps;
type ItemProps = SlottablePressableProps & { value: string;};
type RootRef = ViewRef;type ItemRef = PressableRef;
export type { ItemProps, ItemRef, RootProps, RootRef };
Copy/paste the following code for exporting to ~/components/primitives/toggle-group/index.ts
export * from './toggle-group';export * from './types';
Copy/paste the following code for native to ~/components/primitives/toggle-group/index.tsx
import * as Slot from '~/components/primitives/slot';import { ToggleGroupUtils } from '~/components/primitives/utils';import * as React from 'react';import { Pressable, View, type GestureResponderEvent } from 'react-native';import type { ItemProps, ItemRef, RootProps, RootRef } from './types';
const ToggleGroupContext = React.createContext<RootProps | null>(null);
const Root = React.forwardRef<RootRef, RootProps>( ( { asChild, type, value, onValueChange, disabled = false, rovingFocus: _rovingFocus, orientation: _orientation, dir: _dir, loop: _loop, ...viewProps }, ref ) => { const Component = asChild ? Slot.View : View; return ( <ToggleGroupContext.Provider value={ { type, value, disabled, onValueChange, } as RootProps } > <Component ref={ref} role='group' {...viewProps} /> </ToggleGroupContext.Provider> ); });
Root.displayName = 'RootToggleGroup';
function useRootContext() { const context = React.useContext(ToggleGroupContext); if (!context) { throw new Error( 'ToggleGroup compound components cannot be rendered outside the ToggleGroup component' ); } return context;}
const ItemContext = React.createContext<ItemProps | null>(null);
const Item = React.forwardRef<ItemRef, ItemProps>( ( { asChild, value: itemValue, disabled: disabledProp = false, onPress: onPressProp, ...props }, ref ) => { const id = React.useId(); const { type, disabled, value, onValueChange } = useRootContext();
function onPress(ev: GestureResponderEvent) { if (disabled || disabledProp) return; if (type === 'single') { onValueChange(ToggleGroupUtils.getNewSingleValue(value, itemValue)); } if (type === 'multiple') { onValueChange(ToggleGroupUtils.getNewMultipleValue(value, itemValue)); } onPressProp?.(ev); }
const isChecked = type === 'single' ? ToggleGroupUtils.getIsSelected(value, itemValue) : undefined; const isSelected = type === 'multiple' ? ToggleGroupUtils.getIsSelected(value, itemValue) : undefined;
const Component = asChild ? Slot.Pressable : Pressable; return ( <ItemContext.Provider value={{ value: itemValue }}> <Component ref={ref} aria-disabled={disabled} role={type === 'single' ? 'radio' : 'checkbox'} onPress={onPress} aria-checked={isChecked} aria-selected={isSelected} disabled={(disabled || disabledProp) ?? false} accessibilityState={{ disabled: (disabled || disabledProp) ?? false, checked: isChecked, selected: isSelected, }} {...props} /> </ItemContext.Provider> ); });
Item.displayName = 'ItemToggleGroup';
function useItemContext() { const context = React.useContext(ItemContext); if (!context) { throw new Error( 'ToggleGroupItem compound components cannot be rendered outside the ToggleGroupItem component' ); } return context;}
const utils = ToggleGroupUtils;
export { Item, Root, useItemContext, useRootContext, utils };
Copy/paste the following code for types to ~/components/primitives/toggle-group/types.ts
import { PressableRef, SlottablePressableProps, SlottableViewProps, ViewRef,} from '~/components/primitives/types';
type SingleRootProps = { type: 'single'; value: string | undefined; onValueChange: (val: string | undefined) => void;};
type MultipleRootProps = { type: 'multiple'; value: string[]; onValueChange: (val: string[]) => void;};
type RootProps = (SingleRootProps | MultipleRootProps) & { disabled?: boolean; /** * Platform: WEB ONLY */ rovingFocus?: boolean; /** * Platform: WEB ONLY */ orientation?: 'horizontal' | 'vertical'; /** * Platform: WEB ONLY */ dir?: 'ltr' | 'rtl'; /** * Platform: WEB ONLY */ loop?: boolean;} & SlottableViewProps;
type ItemProps = SlottablePressableProps & { value: string;};
type RootRef = ViewRef;type ItemRef = PressableRef;
export type { ItemProps, ItemRef, RootProps, RootRef };
Usage
import * as React from 'react';import { Text, View } from 'react-native';import * as ToggleGroupPrimitive from '@rn-primitives/toggle-group';
function Example() { const [multipleValue, setMultipleValue] = React.useState<string[]>([]); return ( <ToggleGroupPrimitive.Root type='multiple' value={multipleValue} onValueChange={setMultipleValue}> <ToggleGroupPrimitive.ToggleItem value='bold'> <Text>Bold</Text> </ToggleGroupPrimitive.ToggleItem> <ToggleGroupPrimitive.ToggleItem value='italic'> <Text>Italic</Text> </ToggleGroupPrimitive.ToggleItem> </ToggleGroupPrimitive.Root> );}
Props
Root
Extends View
props
Prop | Type | Note |
---|---|---|
type | ’single’ | ‘multiple’ | |
value | string | undefined | string[] | |
onValueChange | (val: string | undefined | string[]) => void | |
asChild | boolean | (optional) |
disabled | boolean | (optional) |
rovingFocus | boolean | Web only (optional) |
orientation | ’horizontal’ | ‘vertical’ | Web only (optional) |
dir | ’ltr’ | ‘rtl’ | Web only (optional) |
ltr | boolean | Web only (optional) |
Item
Extends Pressable
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
value | string |
useRootContext
Must be used within a Root
component. It provides the following values from the dropdown menu: value
, onValueChange
, type
, and disabled
.
useItemContext
Must be used within a Item
component. It provides the following values from the dropdown menu: value
.