Checkbox Primitive
A box that is a checked (ticked) indicator when activated.
Installation
Install the component via your command line.
npx expo install @rn-primitives/checkbox
Install @radix-ui/react-checkbox
npx expo install @radix-ui/react-checkbox
Copy/paste the following code for web to ~/components/primitives/checkbox/checkbox.web.tsx
import * as Checkbox from '@radix-ui/react-checkbox';import { useAugmentedRef, useIsomorphicLayoutEffect } from '~/components/primitives/hooks';import * as Slot from '~/components/primitives/slot';import * as React from 'react';import { GestureResponderEvent, Pressable, View } from 'react-native';import type { IndicatorProps, IndicatorRef, RootProps, RootRef } from './types';
const CheckboxContext = React.createContext<RootProps | null>(null);
const Root = React.forwardRef<RootRef, RootProps>( ( { asChild, disabled, checked, onCheckedChange, onPress: onPressProp, role: _role, ...props }, ref ) => { const augmentedRef = useAugmentedRef({ ref });
function onPress(ev: GestureResponderEvent) { onPressProp?.(ev); onCheckedChange(!checked); }
useIsomorphicLayoutEffect(() => { if (augmentedRef.current) { const augRef = augmentedRef.current as unknown as HTMLButtonElement; augRef.dataset.state = checked ? 'checked' : 'unchecked'; augRef.value = checked ? 'on' : 'off'; } }, [checked]);
useIsomorphicLayoutEffect(() => { if (augmentedRef.current) { const augRef = augmentedRef.current as unknown as HTMLButtonElement; augRef.type = 'button'; augRef.role = 'checkbox';
if (disabled) { augRef.dataset.disabled = 'true'; } else { augRef.dataset.disabled = undefined; } } }, [disabled]);
const Component = asChild ? Slot.Pressable : Pressable; return ( <CheckboxContext.Provider value={{ checked, disabled, onCheckedChange }}> <Checkbox.Root checked={checked} onCheckedChange={onCheckedChange} disabled={disabled} asChild > <Component ref={augmentedRef} role='button' onPress={onPress} disabled={disabled} {...props} /> </Checkbox.Root> </CheckboxContext.Provider> ); });
Root.displayName = 'RootWebCheckbox';
function useCheckboxContext() { const context = React.useContext(CheckboxContext); if (context === null) { throw new Error( 'Checkbox compound components cannot be rendered outside the Checkbox component' ); } return context;}
const Indicator = React.forwardRef<IndicatorRef, IndicatorProps>( ({ asChild, forceMount, ...props }, ref) => { const { checked, disabled } = useCheckboxContext(); const augmentedRef = useAugmentedRef({ ref });
useIsomorphicLayoutEffect(() => { if (augmentedRef.current) { const augRef = augmentedRef.current as unknown as HTMLDivElement; augRef.dataset.state = checked ? 'checked' : 'unchecked'; } }, [checked]);
useIsomorphicLayoutEffect(() => { if (augmentedRef.current) { const augRef = augmentedRef.current as unknown as HTMLDivElement; if (disabled) { augRef.dataset.disabled = 'true'; } else { augRef.dataset.disabled = undefined; } } }, [disabled]);
const Component = asChild ? Slot.View : View; return ( <Checkbox.Indicator forceMount={forceMount} asChild> <Component ref={ref} {...props} /> </Checkbox.Indicator> ); });
Indicator.displayName = 'IndicatorWebCheckbox';
export { Indicator, Root };
Copy/paste the following code for native to ~/components/primitives/checkbox/checkbox.tsx
import * as Slot from '~/components/primitives/slot';import type { PressableRef, SlottablePressableProps } from '~/components/primitives/types';import * as React from 'react';import { GestureResponderEvent, Pressable, View } from 'react-native';import type { IndicatorProps, IndicatorRef, RootProps, RootRef } from './types';
interface RootContext extends RootProps { nativeID?: string;}
const CheckboxContext = React.createContext<RootContext | null>(null);
const Root = React.forwardRef<RootRef, RootProps>( ({ asChild, disabled = false, checked, onCheckedChange, nativeID, ...props }, ref) => { return ( <CheckboxContext.Provider value={{ disabled, checked, onCheckedChange, nativeID, }} > <Trigger ref={ref} {...props} /> </CheckboxContext.Provider> ); });
Root.displayName = 'RootNativeCheckbox';
function useCheckboxContext() { const context = React.useContext(CheckboxContext); if (!context) { throw new Error( 'Checkbox compound components cannot be rendered outside the Checkbox component' ); } return context;}
const Trigger = React.forwardRef<PressableRef, SlottablePressableProps>( ({ asChild, onPress: onPressProp, ...props }, ref) => { const { disabled, checked, onCheckedChange, nativeID } = useCheckboxContext();
function onPress(ev: GestureResponderEvent) { if (disabled) return; const newValue = !checked; onCheckedChange(newValue); onPressProp?.(ev); }
const Component = asChild ? Slot.Pressable : Pressable; return ( <Component ref={ref} nativeID={nativeID} aria-disabled={disabled} role='checkbox' aria-checked={checked} onPress={onPress} accessibilityState={{ checked, disabled, }} disabled={disabled} {...props} /> ); });
Trigger.displayName = 'TriggerNativeCheckbox';
const Indicator = React.forwardRef<IndicatorRef, IndicatorProps>( ({ asChild, forceMount, ...props }, ref) => { const { checked, disabled } = useCheckboxContext();
if (!forceMount) { if (!checked) { return null; } }
const Component = asChild ? Slot.View : View; return ( <Component ref={ref} aria-disabled={disabled} aria-hidden={!(forceMount || checked)} role={'presentation'} {...props} /> ); });
Indicator.displayName = 'IndicatorNativeCheckbox';
export { Indicator, Root };
Copy/paste the following code for types to ~/components/primitives/checkbox/types.ts
import type { ForceMountable, PressableRef, SlottablePressableProps, SlottableViewProps, ViewRef,} from '~/components/primitives/types';
type RootProps = SlottablePressableProps & { checked: boolean; onCheckedChange: (checked: boolean) => void; disabled?: boolean;};
type IndicatorProps = ForceMountable & SlottableViewProps;
type RootRef = PressableRef;type IndicatorRef = ViewRef;
export type { IndicatorProps, IndicatorRef, RootProps, RootRef };
Copy/paste the following code for exporting to ~/components/primitives/checkbox/index.ts
export * from './checkbox';export * from './types';
Copy/paste the following code for native to ~/components/primitives/checkbox/index.tsx
import * as Slot from '~/components/primitives/slot';import type { PressableRef, SlottablePressableProps } from '~/components/primitives/types';import * as React from 'react';import { GestureResponderEvent, Pressable, View } from 'react-native';import type { IndicatorProps, IndicatorRef, RootProps, RootRef } from './types';
interface RootContext extends RootProps { nativeID?: string;}
const CheckboxContext = React.createContext<RootContext | null>(null);
const Root = React.forwardRef<RootRef, RootProps>( ({ asChild, disabled = false, checked, onCheckedChange, nativeID, ...props }, ref) => { return ( <CheckboxContext.Provider value={{ disabled, checked, onCheckedChange, nativeID, }} > <Trigger ref={ref} {...props} /> </CheckboxContext.Provider> ); });
Root.displayName = 'RootNativeCheckbox';
function useCheckboxContext() { const context = React.useContext(CheckboxContext); if (!context) { throw new Error( 'Checkbox compound components cannot be rendered outside the Checkbox component' ); } return context;}
const Trigger = React.forwardRef<PressableRef, SlottablePressableProps>( ({ asChild, onPress: onPressProp, ...props }, ref) => { const { disabled, checked, onCheckedChange, nativeID } = useCheckboxContext();
function onPress(ev: GestureResponderEvent) { if (disabled) return; const newValue = !checked; onCheckedChange(newValue); onPressProp?.(ev); }
const Component = asChild ? Slot.Pressable : Pressable; return ( <Component ref={ref} nativeID={nativeID} aria-disabled={disabled} role='checkbox' aria-checked={checked} onPress={onPress} accessibilityState={{ checked, disabled, }} disabled={disabled} {...props} /> ); });
Trigger.displayName = 'TriggerNativeCheckbox';
const Indicator = React.forwardRef<IndicatorRef, IndicatorProps>( ({ asChild, forceMount, ...props }, ref) => { const { checked, disabled } = useCheckboxContext();
if (!forceMount) { if (!checked) { return null; } }
const Component = asChild ? Slot.View : View; return ( <Component ref={ref} aria-disabled={disabled} aria-hidden={!(forceMount || checked)} role={'presentation'} {...props} /> ); });
Indicator.displayName = 'IndicatorNativeCheckbox';
export { Indicator, Root };
Copy/paste the following code for types to ~/components/primitives/checkbox/types.ts
import type { ForceMountable, PressableRef, SlottablePressableProps, SlottableViewProps, ViewRef,} from '~/components/primitives/types';
type RootProps = SlottablePressableProps & { checked: boolean; onCheckedChange: (checked: boolean) => void; disabled?: boolean;};
type IndicatorProps = ForceMountable & SlottableViewProps;
type RootRef = PressableRef;type IndicatorRef = ViewRef;
export type { IndicatorProps, IndicatorRef, RootProps, RootRef };
Usage
import * as CheckboxPrimitive from '@rn-primitives/checkbox';
function Example() { const [checked, setChecked] = React.useState(false); return ( <CheckboxPrimitive.Root checked={checked} onCheckedChange={setChecked} style={{ height: 16, width: 16, borderWidth: 1, borderColor: 'black', justifyContent: 'center', alignItems: 'center', }} > <CheckboxPrimitive.Indicator> <View style={{ height: 12, width: 12, backgroundColor: 'red' }} /> </CheckboxPrimitive.Indicator> </CheckboxPrimitive.Root> );}
Props
Root
Extends Pressable
props
Prop | Type | Note |
---|---|---|
checked* | boolean | |
onCheckedChange* | (checked: boolean) => void | |
disabled | boolean | (optional) |
Indicator
Extends View
props
Prop | Type | Note |
---|---|---|
forceMount | true | undefined | (optional) |