Overview
The Choicebox component provides a card-based interface for making selections. It comes in two variants:
- Choicebox: Acts as a checkbox for multiple selections
- ChoiceboxRadio: Acts as a radio button for single selections within a group
Built on React Aria Components, Choicebox ensures full accessibility with keyboard navigation, focus management, and screen reader support.
Installation
npm install react-aria-components class-variance-authority lucide-react
Import
import { Choicebox, ChoiceboxRadio } from '@stride-ui/components';
import { RadioGroup } from '@stride-ui/components';
Basic Usage
Checkbox Variant
Use the Choicebox component for multiple independent selections:
import { Choicebox } from '@stride-ui/components';
export default function Example() {
return (
<Choicebox
title="Basic Plan"
description="Perfect for individuals getting started with our platform."
/>
);
}
Radio Variant
Use ChoiceboxRadio within a RadioGroup for single selections:
import { ChoiceboxRadio } from '@stride-ui/components';
import { RadioGroup } from '@stride-ui/components';
export default function Example() {
return (
<RadioGroup label="Choose your plan">
<ChoiceboxRadio
value="basic"
title="Basic Plan"
description="Perfect for individuals getting started."
/>
<ChoiceboxRadio
value="pro"
title="Pro Plan"
description="Great for small teams and growing businesses."
/>
<ChoiceboxRadio
value="enterprise"
title="Enterprise Plan"
description="Complete solution for large organizations."
/>
</RadioGroup>
);
}
Props
Choicebox Props
The main heading text displayed in the choicebox card.
Secondary text providing additional context or details about the option.
size
'sm' | 'md' | 'lg'
default:"'md'"
Controls the padding and overall size of the choicebox card.
sm: Compact size with p-3
md: Default size with p-4
lg: Large size with p-6
Controls whether the checkbox is checked. Use for controlled components.
Displays a minus icon instead of a checkmark, indicating a partial selection state.
Disables the choicebox, preventing user interaction and applying disabled styling.
onChange
(isSelected: boolean) => void
Callback fired when the selection state changes. Receives the new selected state.
Additional CSS classes to apply to the choicebox container.
The value associated with this checkbox (inherited from React Aria).
ChoiceboxRadio Props
The main heading text displayed in the choicebox card.
Secondary text providing additional context or details about the option.
size
'sm' | 'md' | 'lg'
default:"'md'"
Controls the padding and overall size of the choicebox card.
The unique value for this radio option within the group.
Disables the radio option, preventing user interaction.
Additional CSS classes to apply to the choicebox container.
Examples
Sizes
Choicebox supports three size variants:
import { Choicebox } from '@stride-ui/components';
export default function SizesExample() {
return (
<div className="flex flex-col space-y-4 max-w-sm">
<Choicebox
size="sm"
title="Small Plan"
description="Compact option for basic needs."
/>
<Choicebox
size="md"
title="Medium Plan"
description="Standard option with balanced features and pricing."
/>
<Choicebox
size="lg"
title="Large Plan"
description="Comprehensive solution with all features included."
/>
</div>
);
}
Title Only
When no description is provided, the title is displayed alone:
import { Choicebox } from '@stride-ui/components';
export default function TitleOnlyExample() {
return (
<Choicebox title="Enterprise Plan" />
);
}
States
Choicebox supports multiple states including selected, indeterminate, and disabled:
import { Choicebox } from '@stride-ui/components';
export default function StatesExample() {
return (
<div className="flex flex-col space-y-4 max-w-sm">
<Choicebox
title="Unchecked"
description="This option is not selected."
/>
<Choicebox
title="Selected"
description="This option is currently selected."
isSelected
/>
<Choicebox
title="Indeterminate"
description="This option has a partial selection state."
isIndeterminate
/>
<Choicebox
title="Disabled"
description="This option is disabled and cannot be selected."
isDisabled
/>
<Choicebox
title="Disabled & Selected"
description="This option is disabled but was previously selected."
isSelected
isDisabled
/>
</div>
);
}
Controlled Checkbox Selection
Manage multiple independent selections with state:
import { useState } from 'react';
import { Choicebox } from '@stride-ui/components';
export default function ControlledExample() {
const [features, setFeatures] = useState({
notifications: false,
analytics: true,
api: false,
support: false,
});
const updateFeature = (feature: keyof typeof features) => (selected: boolean) => {
setFeatures(prev => ({ ...prev, [feature]: selected }));
};
return (
<div className="flex flex-col space-y-4 max-w-md">
<h3 className="font-semibold text-lg mb-2">Select Features</h3>
<Choicebox
title="Push Notifications"
description="Receive real-time updates about your projects and team activities."
isSelected={features.notifications}
onChange={updateFeature('notifications')}
/>
<Choicebox
title="Advanced Analytics"
description="Access detailed insights and reporting tools for better decision making."
isSelected={features.analytics}
onChange={updateFeature('analytics')}
/>
<Choicebox
title="API Access"
description="Integrate with third-party tools and build custom solutions."
isSelected={features.api}
onChange={updateFeature('api')}
/>
<Choicebox
title="Priority Support"
description="Get 24/7 support with faster response times and dedicated assistance."
isSelected={features.support}
onChange={updateFeature('support')}
/>
</div>
);
}
Controlled Radio Selection
Use RadioGroup to manage single selections:
import { useState } from 'react';
import { ChoiceboxRadio } from '@stride-ui/components';
import { RadioGroup } from '@stride-ui/components';
export default function RadioExample() {
const [selectedPlan, setSelectedPlan] = useState('pro');
return (
<RadioGroup
value={selectedPlan}
onChange={setSelectedPlan}
label="Choose your plan"
className="max-w-sm"
>
<ChoiceboxRadio
value="free"
title="Free Plan"
description="Get started with basic features and limited usage."
/>
<ChoiceboxRadio
value="pro"
title="Pro Plan"
description="Perfect for professionals with advanced features and priority support."
/>
<ChoiceboxRadio
value="enterprise"
title="Enterprise Plan"
description="Complete solution for large organizations with custom integrations."
/>
</RadioGroup>
);
}
Long Content
Choicebox handles longer descriptions gracefully:
import { ChoiceboxRadio } from '@stride-ui/components';
import { RadioGroup } from '@stride-ui/components';
export default function LongContentExample() {
return (
<RadioGroup label="Select subscription" className="max-w-md">
<ChoiceboxRadio
value="basic"
title="Basic Subscription"
description="This is a basic subscription with limited features. You get access to core functionality, basic support, and standard updates. Perfect for individuals or small teams just getting started."
/>
<ChoiceboxRadio
value="premium"
title="Premium Subscription"
description="Our premium offering includes everything in Basic plus advanced analytics, priority support, custom integrations, and early access to new features. Ideal for growing businesses and professional teams."
/>
</RadioGroup>
);
}
import { useForm, Controller } from 'react-hook-form';
import { Choicebox, ChoiceboxRadio } from '@stride-ui/components';
import { RadioGroup } from '@stride-ui/components';
type FormData = {
features: {
notifications: boolean;
analytics: boolean;
};
plan: string;
};
export default function FormExample() {
const { control, handleSubmit } = useForm<FormData>({
defaultValues: {
features: {
notifications: false,
analytics: true,
},
plan: 'pro',
},
});
const onSubmit = (data: FormData) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
{/* Checkbox selections */}
<div className="space-y-4">
<h3 className="font-semibold">Features</h3>
<Controller
name="features.notifications"
control={control}
render={({ field }) => (
<Choicebox
title="Push Notifications"
description="Receive real-time updates"
isSelected={field.value}
onChange={field.onChange}
/>
)}
/>
<Controller
name="features.analytics"
control={control}
render={({ field }) => (
<Choicebox
title="Advanced Analytics"
description="Access detailed insights"
isSelected={field.value}
onChange={field.onChange}
/>
)}
/>
</div>
{/* Radio selection */}
<Controller
name="plan"
control={control}
render={({ field }) => (
<RadioGroup
label="Choose your plan"
value={field.value}
onChange={field.onChange}
>
<ChoiceboxRadio
value="basic"
title="Basic Plan"
description="Essential features"
/>
<ChoiceboxRadio
value="pro"
title="Pro Plan"
description="Advanced features"
/>
<ChoiceboxRadio
value="enterprise"
title="Enterprise Plan"
description="Complete solution"
/>
</RadioGroup>
)}
/>
<button type="submit">Submit</button>
</form>
);
}
import { Formik, Form, Field } from 'formik';
import { Choicebox, ChoiceboxRadio } from '@stride-ui/components';
import { RadioGroup } from '@stride-ui/components';
export default function FormikExample() {
return (
<Formik
initialValues={{
notifications: false,
analytics: true,
plan: 'pro',
}}
onSubmit={(values) => {
console.log(values);
}}
>
{({ values, setFieldValue }) => (
<Form className="space-y-6">
<div className="space-y-4">
<h3 className="font-semibold">Features</h3>
<Choicebox
title="Push Notifications"
description="Receive real-time updates"
isSelected={values.notifications}
onChange={(selected) => setFieldValue('notifications', selected)}
/>
<Choicebox
title="Advanced Analytics"
description="Access detailed insights"
isSelected={values.analytics}
onChange={(selected) => setFieldValue('analytics', selected)}
/>
</div>
<RadioGroup
label="Choose your plan"
value={values.plan}
onChange={(value) => setFieldValue('plan', value)}
>
<ChoiceboxRadio value="basic" title="Basic Plan" />
<ChoiceboxRadio value="pro" title="Pro Plan" />
<ChoiceboxRadio value="enterprise" title="Enterprise Plan" />
</RadioGroup>
<button type="submit">Submit</button>
</Form>
)}
</Formik>
);
}
Accessibility
Choicebox is built on React Aria Components and includes comprehensive accessibility features:
Keyboard Navigation
- Space: Toggle checkbox selection or select radio option
- Tab: Move focus between choiceboxes
- Shift + Tab: Move focus backwards
- Arrow Keys: Navigate between radio options within a RadioGroup
Screen Readers
- Proper ARIA labels and roles are automatically applied
- Selection state is announced to screen readers
- Disabled and indeterminate states are communicated
- Focus management follows WCAG 2.1 guidelines
Focus Management
- Clear focus indicators with ring styles
- Focus-visible only shows ring for keyboard navigation
- Focus state respects system preferences
Best Practices
- Always provide a title: The title acts as the label for screen readers
- Use RadioGroup for radio variants: Ensures proper ARIA relationships
- Provide meaningful descriptions: Helps users understand their options
- Use isDisabled appropriately: Don’t hide disabled options; show them with context
- Ensure sufficient color contrast: The component uses semantic tokens for proper contrast
Design Tokens
Choicebox uses the following design tokens from the Stride Design System:
--border-primary: Default border color
--border-secondary: Hover border color
--border-focus: Focus ring color
--bg-primary: Default background
--bg-secondary: Hover and selected background
--interactive-primary: Selected indicator color
--interactive-primary-hover: Hovered selected state
--text-primary: Title text color
--text-tertiary: Description text color
--radius-sm: Indicator border radius (checkbox)
--radius-md: Card border radius
--transition-normal: Transition duration
Component Structure
The Choicebox component is composed of:
- Card Container: The outer clickable area with border and background
- Indicator: Checkbox (square) or radio (circle) in the top-right corner
- Content Area: Title and optional description with responsive typography
- Visual Feedback: Hover, focus, and selection states
Source: /home/daytona/workspace/source/src/components/ui/Choicebox/Choicebox.tsx
- Checkbox - Traditional checkbox for compact forms
- Radio - Traditional radio buttons for lists
- RadioGroup - Container for radio options