# ControlGroup **📖 Live documentation:** https://cds.coinbase.com/components/inputs/ControlGroup/ A layout component that arranges and manages a group of related controls, such as radio buttons, switches, or checkboxes. ## Import ```tsx import { ControlGroup } from '@coinbase/cds-web/controls/ControlGroup' ``` ## Examples ### Checkbox Cell Group ```tsx live function CheckboxGroupExample() { const [selected, setSelected] = useState(['one', 'four']); return ( Checkbox Group} ControlComponent={CheckboxCell} value={selected} onChange={(e) => { const { value: checkboxValue, checked } = e.target; setSelected((prev) => checked ? [...prev, checkboxValue] : prev.filter((v) => v !== checkboxValue), ); }} options={[ { value: 'one', title: 'Option 1', description: 'A description for the first option.' }, { value: 'two', title: 'Option 2', description: 'A description for the second option.' }, { value: 'three', title: 'Option 3', description: 'This option is disabled.', disabled: true, }, { value: 'four', title: 'Option 4', description: 'This option is read-only.', readOnly: true, }, ]} /> Selected: {selected.join(', ')} ); } ``` ### Radio Cell Group ```tsx live function RadioGroupExample() { const [selected, setSelected] = useState('one'); return ( Radio Group} ControlComponent={RadioCell} value={selected} role="radiogroup" onChange={(e) => setSelected(e.target.value)} options={[ { value: 'one', title: 'Option 1', description: 'A description for the first option.' }, { value: 'two', title: 'Option 2', description: 'A description for the second option.' }, { value: 'three', title: 'Option 3', description: 'This option is disabled.', disabled: true, }, { value: 'four', title: 'Option 4', description: 'This option is read-only.', readOnly: true, }, ]} /> Selected: {selected} ); } ``` ### Checkbox ```tsx live function CheckboxExample() { const [selected, setSelected] = useState(['one', 'four']); return ( Checkbox} ControlComponent={Checkbox} value={selected} onChange={(e) => { const { value: checkboxValue, checked } = e.target; setSelected((prev) => checked ? [...prev, checkboxValue] : prev.filter((v) => v !== checkboxValue), ); }} options={[ { value: 'one', label: 'Option 1' }, { value: 'two', label: 'Option 2' }, { value: 'three', label: 'Option 3 (disabled)', disabled: true }, { value: 'four', label: 'Option 4 (read-only)', readOnly: true }, ]} /> Selected: {selected.join(', ')} ); } ``` ### Radio ```tsx live function RadioExample() { const [selected, setSelected] = useState('one'); return ( Radio} ControlComponent={Radio} value={selected} role="radiogroup" onChange={(e) => setSelected(e.target.value)} options={[ { value: 'one', label: 'Option 1' }, { value: 'two', label: 'Option 2' }, { value: 'three', label: 'Option 3 (disabled)', disabled: true }, { value: 'four', label: 'Option 4 (read-only)', readOnly: true }, ]} /> Selected: {selected} ); } ``` ### Switch ```tsx live function SwitchExample() { const [selected, setSelected] = useState(['one', 'four']); return ( Switch} ControlComponent={Switch} value={selected} onChange={(e) => { const { value: switchValue, checked } = e.target; setSelected((prev) => checked ? [...prev, switchValue] : prev.filter((v) => v !== switchValue), ); }} options={[ { value: 'one', label: 'Option 1' }, { value: 'two', label: 'Option 2' }, { value: 'three', label: 'Option 3 (disabled)', disabled: true }, { value: 'four', label: 'Option 4 (read-only)', readOnly: true }, ]} /> Selected: {selected.join(', ')} ); } ``` ### Custom Card Toggle ```tsx live function CustomCardToggleExample() { // Custom component that works with ControlGroup const CustomCardToggle = ({ checked, onChange, disabled, label, value, ...props }) => { return ( {checked && ( ✓ )} {label} ); }; const [selected, setSelected] = useState(['premium']); return ( Custom Card Toggle} ControlComponent={CustomCardToggle} value={selected} onChange={(e) => { const { value: toggleValue, checked } = e.target; setSelected((prev) => checked ? [...prev, toggleValue] : prev.filter((v) => v !== toggleValue), ); }} options={[ { value: 'basic', label: 'Basic Plan' }, { value: 'premium', label: 'Premium Plan' }, { value: 'enterprise', label: 'Enterprise Plan' }, { value: 'custom', label: 'Custom Plan (disabled)', disabled: true }, ]} /> Selected: {selected.join(', ')} ); } ``` ### Custom Radio Button ```tsx live function CustomRadioButtonExample() { // Custom radio component with enhanced styling const CustomRadioButton = ({ checked, onChange, disabled, children, value, ...props }) => { return ( {checked && ( )} {children} {value === 'starter' && 'Perfect for beginners'} {value === 'professional' && 'For growing businesses'} {value === 'enterprise' && 'For large organizations'} ); }; const [selected, setSelected] = useState('professional'); return ( Custom Radio Button} ControlComponent={CustomRadioButton} value={selected} role="radiogroup" onChange={(e) => setSelected(e.target.value)} options={[ { value: 'starter', label: 'Starter' }, { value: 'professional', label: 'Professional' }, { value: 'enterprise', label: 'Enterprise' }, ]} /> Selected: {selected} ); } ``` ## Props | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `ControlComponent` | `ComponentType` | Yes | `-` | The control component to render for each option. | | `options` | `(ControlGroupOption & { value: ControlValue; })[]` | Yes | `-` | Control options for the group. | | `value` | `string \| ControlValue[]` | Yes | `-` | Current selected value(s). Use a string for single-select (e.g., RadioGroup) and an array of strings for multi-select (e.g., CheckboxGroup). | | `alignContent` | `ResponsiveProp
` | No | `-` | - | | `alignItems` | `ResponsiveProp
` | No | `-` | - | | `alignSelf` | `ResponsiveProp
` | No | `-` | - | | `aspectRatio` | `ResponsiveProp` | No | `-` | - | | `background` | `ResponsiveProp` | No | `-` | - | | `borderBottomLeftRadius` | `ResponsiveProp` | No | `-` | - | | `borderBottomRightRadius` | `ResponsiveProp` | No | `-` | - | | `borderBottomWidth` | `ResponsiveProp` | No | `-` | - | | `borderColor` | `ResponsiveProp` | No | `-` | - | | `borderEndWidth` | `ResponsiveProp` | No | `-` | - | | `borderRadius` | `ResponsiveProp` | No | `-` | - | | `borderStartWidth` | `ResponsiveProp` | No | `-` | - | | `borderTopLeftRadius` | `ResponsiveProp` | No | `-` | - | | `borderTopRightRadius` | `ResponsiveProp` | No | `-` | - | | `borderTopWidth` | `ResponsiveProp` | No | `-` | - | | `borderWidth` | `ResponsiveProp` | No | `-` | - | | `bordered` | `boolean` | No | `-` | Add a border around all sides of the box. | | `borderedBottom` | `boolean` | No | `-` | Add a border to the bottom side of the box. | | `borderedEnd` | `boolean` | No | `-` | Add a border to the trailing side of the box. | | `borderedHorizontal` | `boolean` | No | `-` | Add a border to the leading and trailing sides of the box. | | `borderedStart` | `boolean` | No | `-` | Add a border to the leading side of the box. | | `borderedTop` | `boolean` | No | `-` | Add a border to the top side of the box. | | `borderedVertical` | `boolean` | No | `-` | Add a border to the top and bottom sides of the box. | | `bottom` | `ResponsiveProp>` | No | `-` | - | | `color` | `ResponsiveProp` | No | `-` | - | | `columnGap` | `ResponsiveProp` | No | `-` | - | | `dangerouslySetBackground` | `string` | No | `-` | - | | `direction` | `horizontal \| vertical` | No | `-` | The direction of the group. | | `display` | `ResponsiveProp` | No | `-` | - | | `elevation` | `ResponsiveProp` | No | `-` | - | | `flexBasis` | `ResponsiveProp>` | No | `-` | - | | `flexDirection` | `ResponsiveProp` | No | `-` | - | | `flexGrow` | `ResponsiveProp` | No | `-` | - | | `flexShrink` | `ResponsiveProp` | No | `-` | - | | `flexWrap` | `ResponsiveProp` | No | `-` | - | | `font` | `ResponsiveProp` | No | `-` | - | | `fontFamily` | `ResponsiveProp` | No | `-` | - | | `fontSize` | `ResponsiveProp` | No | `-` | - | | `fontWeight` | `ResponsiveProp` | No | `-` | - | | `gap` | `ResponsiveProp` | No | `-` | - | | `grid` | `ResponsiveProp` | No | `-` | - | | `gridArea` | `ResponsiveProp` | No | `-` | - | | `gridAutoColumns` | `ResponsiveProp>` | No | `-` | - | | `gridAutoFlow` | `ResponsiveProp` | No | `-` | - | | `gridAutoRows` | `ResponsiveProp>` | No | `-` | - | | `gridColumn` | `ResponsiveProp` | No | `-` | - | | `gridColumnEnd` | `ResponsiveProp` | No | `-` | - | | `gridColumnStart` | `ResponsiveProp` | No | `-` | - | | `gridRow` | `ResponsiveProp` | No | `-` | - | | `gridRowEnd` | `ResponsiveProp` | No | `-` | - | | `gridRowStart` | `ResponsiveProp` | No | `-` | - | | `gridTemplate` | `ResponsiveProp` | No | `-` | - | | `gridTemplateAreas` | `ResponsiveProp` | No | `-` | - | | `gridTemplateColumns` | `ResponsiveProp>` | No | `-` | - | | `gridTemplateRows` | `ResponsiveProp>` | No | `-` | - | | `height` | `ResponsiveProp>` | No | `-` | - | | `justifyContent` | `ResponsiveProp` | No | `-` | - | | `key` | `Key \| null` | No | `-` | - | | `label` | `ReactNode` | No | `-` | Set a label for the group. | | `left` | `ResponsiveProp>` | No | `-` | - | | `lineHeight` | `ResponsiveProp` | No | `-` | - | | `margin` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - | | `marginBottom` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - | | `marginEnd` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - | | `marginStart` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - | | `marginTop` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - | | `marginX` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - | | `marginY` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - | | `maxHeight` | `ResponsiveProp>` | No | `-` | - | | `maxWidth` | `ResponsiveProp>` | No | `-` | - | | `minHeight` | `ResponsiveProp>` | No | `-` | - | | `minWidth` | `ResponsiveProp>` | No | `-` | - | | `name` | `string` | No | `-` | The name of the group. | | `onChange` | `((e: ChangeEvent) => void)` | No | `-` | Handle change events. | | `opacity` | `ResponsiveProp` | No | `-` | - | | `overflow` | `ResponsiveProp` | No | `-` | - | | `padding` | `ResponsiveProp` | No | `-` | - | | `paddingBottom` | `ResponsiveProp` | No | `-` | - | | `paddingEnd` | `ResponsiveProp` | No | `-` | - | | `paddingStart` | `ResponsiveProp` | No | `-` | - | | `paddingTop` | `ResponsiveProp` | No | `-` | - | | `paddingX` | `ResponsiveProp` | No | `-` | - | | `paddingY` | `ResponsiveProp` | No | `-` | - | | `pin` | `PinningDirection` | No | `-` | Direction in which to absolutely pin the box. | | `position` | `ResponsiveProp` | No | `-` | - | | `ref` | `RefObject \| ((instance: HTMLDivElement \| null) => void) \| null` | No | `-` | - | | `right` | `ResponsiveProp>` | No | `-` | - | | `role` | `group \| radiogroup` | No | `-` | The role for the group. Use radiogroup for radio buttons, group for other controls. | | `rowGap` | `ResponsiveProp` | No | `-` | - | | `style` | `CSSProperties` | No | `-` | - | | `testID` | `string` | No | `-` | Used to locate this element in unit and end-to-end tests. Under the hood, testID translates to data-testid on Web. On Mobile, testID stays the same - testID | | `textAlign` | `ResponsiveProp
` | No | `-` | - | | `textDecoration` | `ResponsiveProp` | No | `-` | - | | `textTransform` | `ResponsiveProp` | No | `-` | - | | `top` | `ResponsiveProp>` | No | `-` | - | | `transform` | `ResponsiveProp` | No | `-` | - | | `userSelect` | `ResponsiveProp` | No | `-` | - | | `visibility` | `ResponsiveProp` | No | `-` | - | | `width` | `ResponsiveProp>` | No | `-` | - | | `zIndex` | `ResponsiveProp` | No | `-` | - |