# SelectChipAlpha
**📖 Live documentation:** https://cds.coinbase.com/components/inputs/SelectChipAlpha/
A chip-styled Select control built on top of the Alpha Select component. Supports both single and multi selection.
## Import
```tsx
import { SelectChip } from '@coinbase/cds-web/alpha/select-chip/SelectChip'
```
## Examples
SelectChip is a chip-styled control built on top of the [Alpha Select](/components/inputs/SelectAlpha/). It supports single and multi-selection, option groups, custom start/end nodes, and shares the same `classNames` and `styles` API for targeting internal elements (see the Styles tab).
:::note Duplicate Values
Avoid using options with duplicate values. Each option's `value` should be unique within the options array to ensure proper selection behavior.
:::
### Active state
SelectChip uses the same `active` semantics as [Chip](/components/inputs/Chip/): when `active` is true, the control inverts the color scheme for everything inside the chip borders.
By default, SelectChip sets `active` to `true` whenever one or more values are selected and `false` when the control is empty. You do not need to wire `active` to `value` manually. Pass `active` explicitly to force the emphasized state even when nothing is selected, or set `active={false}` to keep the chip visually inactive despite a selection.
### Basics
#### Basic usage
```jsx live
function ExampleDefault() {
const exampleOptions = [
{ value: null, label: 'Clear selection' },
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2' },
{ value: '3', label: 'Option 3' },
{ value: '4', label: 'Option 4' },
];
const [value, setValue] = useState(null);
return (
);
}
```
### Single select
#### With groups
```jsx live
function ExampleSingleGroups() {
const exampleOptions = [
{
label: 'Group A',
options: [
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2' },
{ value: '3', label: 'Option 3' },
],
},
{
label: 'Group B',
options: [
{ value: '4', label: 'Option 4' },
{ value: '5', label: 'Option 5' },
],
},
{
label: 'Group C',
options: [{ value: '6', label: 'Option 6' }],
},
];
const [value, setValue] = useState(null);
return (
);
}
```
#### With disabled group
```jsx live
function ExampleDisabledGroup() {
const exampleOptions = [
{
label: 'Group A',
options: [
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2' },
{ value: '3', label: 'Option 3' },
],
},
{
label: 'Group B',
disabled: true,
options: [
{ value: '4', label: 'Option 4' },
{ value: '5', label: 'Option 5' },
],
},
{
label: 'Group C',
options: [{ value: '6', label: 'Option 6' }],
},
];
const [value, setValue] = useState(null);
return (
);
}
```
### Multi-select
#### Basic
:::note Disabled Options and Select All
Disabled options and options inside disabled groups will be skipped when "Select all" is pressed. Only enabled options will be selected.
:::
```jsx live
function ExampleMulti() {
const exampleOptions = [
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2', disabled: true },
{ value: '3', label: 'Option 3' },
{ value: '4', label: 'Option 4' },
{ value: '5', label: 'Option 5' },
];
const { value, onChange } = useMultiSelect({ initialValue: [] });
return (
);
}
```
#### With groups
```jsx live
function ExampleMultiGroups() {
const exampleOptions = [
{
label: 'Group A',
options: [
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2' },
{ value: '3', label: 'Option 3' },
],
},
{
label: 'Group B',
options: [
{ value: '4', label: 'Option 4' },
{ value: '5', label: 'Option 5' },
],
},
{
label: 'Group C',
options: [{ value: '6', label: 'Option 6' }],
},
];
const { value, onChange } = useMultiSelect({ initialValue: [] });
return (
);
}
```
#### With assets
```jsx live
function ExampleMultiAssets() {
const assetImageMap = {
btc: assets.btc.imageUrl,
eth: assets.eth.imageUrl,
dai: assets.dai.imageUrl,
ltc: assets.ltc.imageUrl,
xrp: assets.xrp.imageUrl,
};
const exampleOptions = [
{ value: 'btc', label: assets.btc.name },
{ value: 'eth', label: assets.eth.name },
{ value: 'dai', label: assets.dai.name },
{ value: 'ltc', label: assets.ltc.name },
{ value: 'xrp', label: assets.xrp.name },
];
const { value, onChange } = useMultiSelect({
initialValue: ['eth', 'btc'],
});
// Get startNode based on selected assets
const startNode = useMemo(() => {
if (value.length === 0) return null;
// Multiple assets selected - use RemoteImageGroup
return (
{value.map((assetValue) => {
const imageUrl = assetImageMap[assetValue];
if (!imageUrl) return null;
return ;
})}
);
}, [value]);
return (
);
}
```
### Customization
#### Sizes
`size` accepts `xs` and `s`. Defaults to `s`.
```jsx live
function ExampleSizes() {
const exampleOptions = [
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2' },
{ value: '3', label: 'Option 3' },
{ value: '4', label: 'Option 4' },
];
const [value, setValue] = useState('1');
return (
s (default)
xs
);
}
```
#### Start and end nodes
```jsx live
function ExampleWithNodes() {
const exampleOptions = [
{ value: 'btc', label: assets.btc.name },
{ value: 'eth', label: assets.eth.name },
{ value: 'dai', label: assets.dai.name },
];
const [value, setValue] = useState('eth');
const getStartNode = (selectedValue) => {
if (!selectedValue) return null;
const assetMap = {
btc: assets.btc.imageUrl,
eth: assets.eth.imageUrl,
dai: assets.dai.imageUrl,
};
const imageUrl = assetMap[selectedValue];
if (!imageUrl) return null;
return ;
};
return (
);
}
```
#### Empty state
```jsx live
function ExampleEmptyOptions() {
const [value, setValue] = useState(null);
return ;
}
```
#### Options with descriptions
```jsx live
function ExampleDescriptions() {
const exampleOptions = [
{ value: '1', label: 'Option 1', description: 'First option description' },
{ value: '2', label: 'Option 2', description: 'Second option description' },
{ value: '3', label: 'Option 3', description: 'Third option description' },
{ value: '4', label: 'Option 4', description: 'Fourth option description' },
];
const [value, setValue] = useState(null);
return (
);
}
```
#### Display value override
Use the `displayValue` prop to override the displayed value and avoid truncation, especially in multi-select scenarios where multiple option labels might be too long to display.
```jsx live
function ExampleDisplayValue() {
const exampleOptions = [
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2' },
{ value: '3', label: 'Option 3' },
{ value: '4', label: 'Option 4' },
{ value: '5', label: 'Option 5' },
];
const { value, onChange } = useMultiSelect({ initialValue: [] });
const displayValue =
Array.isArray(value) && value.length > 0
? `${value.length} ${value.length === 1 ? 'option' : 'options'} selected`
: undefined;
return (
);
}
```
#### Max width
```jsx live
function ExampleMaxWidth() {
const exampleOptions = [
{ value: '1', label: 'Very Long Option Name That Exceeds Default Width' },
{ value: '2', label: 'Another Extremely Long Option Label' },
{ value: '3', label: 'Short' },
{ value: '4', label: 'Medium Length Option' },
];
const [value, setValue] = useState(null);
return (
Default maxWidth (200px):
Custom maxWidth (150px):
No maxWidth constraint:
);
}
```
#### Disabled state
```jsx live
function ExampleDisabled() {
const exampleOptions = [
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2' },
{ value: '3', label: 'Option 3' },
{ value: '4', label: 'Option 4' },
];
const [value, setValue] = useState('1');
return (
);
}
```
## Props
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `onChange` | `(value: Type extends multi ? SelectOptionValue \| SelectOptionValue[] \| null : SelectOptionValue \| null) => void` | Yes | `-` | - |
| `options` | `SelectOptionList` | Yes | `-` | Array of options to display in the select dropdown. Can be individual options or groups with label and options |
| `value` | `string \| SelectOptionValue[] \| null` | Yes | `-` | - |
| `SelectAllOptionComponent` | `SelectOptionComponent` | No | `-` | Custom component to render the Select All option |
| `SelectDropdownComponent` | `SelectDropdownComponent` | No | `-` | Custom component to render the dropdown container |
| `SelectEmptyDropdownContentsComponent` | `SelectEmptyDropdownContentComponent` | No | `-` | Custom component to render when no options are available |
| `SelectOptionComponent` | `SelectOptionComponent` | No | `-` | Custom component to render individual options |
| `SelectOptionGroupComponent` | `SelectOptionGroupComponent` | No | `-` | Custom component to render group headers |
| `accessibilityRoles` | `{ dropdown?: AriaHasPopupType; option?: string \| undefined; } \| undefined` | No | `-` | Accessibility roles for dropdown and option elements |
| `accessory` | `ReactElement>` | No | `-` | Accessory element rendered at the end of the cell (e.g., chevron). |
| `active` | `boolean` | No | `false` | When true, emphasizes the Chip with higher contrast by inverting the color scheme for everything rendered inside the Chip — including background, color, icons, and other token-based colors. Those props are resolved against the opposite color scheme, matching the legacy invertColorScheme behavior. Set activeBackground and/or activeColor to opt out of inversion and paint explicit active colors instead. |
| `activeBackground` | `currentColor \| fg \| fgMuted \| fgInverse \| fgPrimary \| fgWarning \| fgPositive \| fgNegative \| bg \| bgAlternate \| bgInverse \| bgOverlay \| bgElevation1 \| bgElevation2 \| bgPrimary \| bgPrimaryWash \| bgSecondary \| bgTertiary \| bgSecondaryWash \| bgNegative \| bgNegativeWash \| bgPositive \| bgPositiveWash \| bgWarning \| bgWarningWash \| bgLine \| bgLineHeavy \| bgLineInverse \| bgLinePrimary \| bgLinePrimarySubtle \| accentSubtleRed \| accentBoldRed \| accentSubtleGreen \| accentBoldGreen \| accentSubtleBlue \| accentBoldBlue \| accentSubtlePurple \| accentBoldPurple \| accentSubtleYellow \| accentBoldYellow \| accentSubtleGray \| accentBoldGray \| transparent` | No | `-` | Background color applied when active is true. When set, the Chip skips color-scheme inversion and uses this token directly. **Warning:** start, end, and children ReactNodes are not updated automatically — pass explicit color props on nested icons and other content so they match the active palette. |
| `activeColor` | `currentColor \| fg \| fgMuted \| fgInverse \| fgPrimary \| fgWarning \| fgPositive \| fgNegative \| bg \| bgAlternate \| bgInverse \| bgOverlay \| bgElevation1 \| bgElevation2 \| bgPrimary \| bgPrimaryWash \| bgSecondary \| bgTertiary \| bgSecondaryWash \| bgNegative \| bgNegativeWash \| bgPositive \| bgPositiveWash \| bgWarning \| bgWarningWash \| bgLine \| bgLineHeavy \| bgLineInverse \| bgLinePrimary \| bgLinePrimarySubtle \| accentSubtleRed \| accentBoldRed \| accentSubtleGreen \| accentBoldGreen \| accentSubtleBlue \| accentBoldBlue \| accentSubtlePurple \| accentBoldPurple \| accentSubtleYellow \| accentBoldYellow \| accentSubtleGray \| accentBoldGray \| transparent \| ResponsiveValue` | No | `-` | Foreground color applied when active is true. When set, the Chip skips color-scheme inversion and uses this token for string labels. **Warning:** start, end, and children ReactNodes are not updated automatically — pass explicit color props on nested icons and other content so they match the active palette. |
| `align` | `center \| start \| end` | No | `'start'` | Alignment of the value node. |
| `background` | `((Color \| ResponsiveValue) & Color) \| undefined` | No | `-` | Background color of the overlay (element being interacted with). |
| `borderColor` | `currentColor \| fg \| fgMuted \| fgInverse \| fgPrimary \| fgWarning \| fgPositive \| fgNegative \| bg \| bgAlternate \| bgInverse \| bgOverlay \| bgElevation1 \| bgElevation2 \| bgPrimary \| bgPrimaryWash \| bgSecondary \| bgTertiary \| bgSecondaryWash \| bgNegative \| bgNegativeWash \| bgPositive \| bgPositiveWash \| bgWarning \| bgWarningWash \| bgLine \| bgLineHeavy \| bgLineInverse \| bgLinePrimary \| bgLinePrimarySubtle \| accentSubtleRed \| accentBoldRed \| accentSubtleGreen \| accentBoldGreen \| accentSubtleBlue \| accentBoldBlue \| accentSubtlePurple \| accentBoldPurple \| accentSubtleYellow \| accentBoldYellow \| accentSubtleGray \| accentBoldGray \| transparent \| ResponsiveValue` | No | `-` | - |
| `borderRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000 \| ResponsiveValue` | No | `-` | - |
| `borderWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| ResponsiveValue` | No | `-` | - |
| `bordered` | `boolean` | No | `-` | Add a border around all sides of the box. |
| `className` | `string` | No | `-` | CSS class name for the root element |
| `classNames` | `{ root?: string; control?: string \| undefined; controlStartNode?: string \| undefined; controlInputNode?: string \| undefined; controlValueNode?: string \| undefined; controlLabelNode?: string \| undefined; controlHelperTextNode?: string \| undefined; controlEndNode?: string \| undefined; dropdown?: string \| undefined; option?: string \| undefined; optionCell?: string \| undefined; optionContent?: string \| undefined; optionLabel?: string \| undefined; optionDescription?: string \| undefined; selectAllDivider?: string \| undefined; emptyContentsContainer?: string \| undefined; emptyContentsText?: string \| undefined; optionGroup?: string \| undefined; } \| undefined` | No | `-` | Custom class names for individual elements of the Select component |
| `clearAllLabel` | `string` | No | `-` | Label for the Clear All option in multi-select mode |
| `color` | `currentColor \| fg \| fgMuted \| fgInverse \| fgPrimary \| fgWarning \| fgPositive \| fgNegative \| bg \| bgAlternate \| bgInverse \| bgOverlay \| bgElevation1 \| bgElevation2 \| bgPrimary \| bgPrimaryWash \| bgSecondary \| bgTertiary \| bgSecondaryWash \| bgNegative \| bgNegativeWash \| bgPositive \| bgPositiveWash \| bgWarning \| bgWarningWash \| bgLine \| bgLineHeavy \| bgLineInverse \| bgLinePrimary \| bgLinePrimarySubtle \| accentSubtleRed \| accentBoldRed \| accentSubtleGreen \| accentBoldGreen \| accentSubtleBlue \| accentBoldBlue \| accentSubtlePurple \| accentBoldPurple \| accentSubtleYellow \| accentBoldYellow \| accentSubtleGray \| accentBoldGray \| transparent \| ResponsiveValue` | No | `-` | - |
| `compact` | `boolean` | No | `-` | Reduces spacing around the chip. |
| `controlAccessibilityLabel` | `string` | No | `-` | Accessibility label for the control |
| `defaultOpen` | `boolean` | No | `-` | Initial open state when component mounts (uncontrolled mode) |
| `disableClickOutsideClose` | `boolean` | No | `-` | Whether clicking outside the dropdown should close it |
| `disabled` | `boolean` | No | `false` | Toggles input interactability and opacity |
| `displayValue` | `null \| string \| number \| bigint \| false \| true \| ReactElement> \| Iterable \| ReactPortal \| Promise` | No | `-` | Override the displayed value in the chip control. Useful for avoiding truncation, especially in multi-select scenarios where multiple option labels might be too long to display. When provided, this value takes precedence over the default label generation. |
| `emptyOptionsLabel` | `string` | No | `-` | Label displayed when there are no options available |
| `end` | `null \| string \| number \| bigint \| false \| true \| ReactElement> \| Iterable \| ReactPortal \| Promise` | No | `-` | End-aligned content (e.g., value, status). Replaces the deprecated detail prop. |
| `endNode` | `null \| string \| number \| bigint \| false \| true \| ReactElement> \| Iterable \| ReactPortal \| Promise` | No | `-` | Adds content to the end of the inner input. Refer to diagram for location of endNode in InputStack component |
| `focusedBorderWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `200 when bordered is false, otherwise equals borderWidth` | Additional border width when focused. |
| `font` | `ResponsiveProp` | No | `-` | - |
| `hiddenSelectedOptionsLabel` | `string` | No | `-` | Label to show for showcasing count of hidden selected options |
| `hideSelectAll` | `boolean` | No | `-` | Whether to hide the Select All option in multi-select mode |
| `inputBackground` | `currentColor \| fg \| fgMuted \| fgInverse \| fgPrimary \| fgWarning \| fgPositive \| fgNegative \| bg \| bgAlternate \| bgInverse \| bgOverlay \| bgElevation1 \| bgElevation2 \| bgPrimary \| bgPrimaryWash \| bgSecondary \| bgTertiary \| bgSecondaryWash \| bgNegative \| bgNegativeWash \| bgPositive \| bgPositiveWash \| bgWarning \| bgWarningWash \| bgLine \| bgLineHeavy \| bgLineInverse \| bgLinePrimary \| bgLinePrimarySubtle \| accentSubtleRed \| accentBoldRed \| accentSubtleGreen \| accentBoldGreen \| accentSubtleBlue \| accentBoldBlue \| accentSubtlePurple \| accentBoldPurple \| accentSubtleYellow \| accentBoldYellow \| accentSubtleGray \| accentBoldGray \| transparent` | No | `'bgSecondary' when readOnly and not disabled, 'bg' otherwise` | Background of the input. |
| `invertColorScheme` | `boolean` | No | `false` | Invert the foreground and background colors to emphasize the Chip. Depending on your theme, it may be dangerous to use this prop in conjunction with transparentWhileInactive. |
| `inverted` | `boolean` | No | `false` | Invert the foreground and background colors to emphasize the Chip. Depending on your theme, it may be dangerous to use this prop in conjunction with transparentWhileInactive. |
| `label` | `null \| string \| number \| bigint \| false \| true \| ReactElement> \| Iterable \| ReactPortal \| Promise` | No | `-` | Label displayed above the control |
| `labelColor` | `currentColor \| fg \| fgMuted \| fgInverse \| fgPrimary \| fgWarning \| fgPositive \| fgNegative \| bg \| bgAlternate \| bgInverse \| bgOverlay \| bgElevation1 \| bgElevation2 \| bgPrimary \| bgPrimaryWash \| bgSecondary \| bgTertiary \| bgSecondaryWash \| bgNegative \| bgNegativeWash \| bgPositive \| bgPositiveWash \| bgWarning \| bgWarningWash \| bgLine \| bgLineHeavy \| bgLineInverse \| bgLinePrimary \| bgLinePrimarySubtle \| accentSubtleRed \| accentBoldRed \| accentSubtleGreen \| accentBoldGreen \| accentSubtleBlue \| accentBoldBlue \| accentSubtlePurple \| accentBoldPurple \| accentSubtleYellow \| accentBoldYellow \| accentSubtleGray \| accentBoldGray \| transparent` | No | `-` | Color token for the field label. |
| `labelFont` | `display1 \| display2 \| display3 \| title1 \| title2 \| title3 \| title4 \| headline \| body \| label1 \| label2 \| caption \| legal` | No | `-` | Typography token for the field label. |
| `maxSelectedOptionsToShow` | `number` | No | `-` | Maximum number of selected options to show before truncating |
| `maxWidth` | `ResponsiveProp>` | No | `200` | If text content overflows, it will get truncated with an ellipsis. |
| `media` | `ReactElement>` | No | `-` | Media rendered at the start of the cell (icon, avatar, image, etc). |
| `numberOfLines` | `number` | No | `1` | How many lines the text in the chip will be broken into. |
| `open` | `boolean` | No | `-` | Controlled open state of the dropdown |
| `placeholder` | `null \| string \| number \| bigint \| false \| true \| ReactElement> \| Iterable \| ReactPortal \| Promise` | No | `-` | Placeholder text displayed when no option is selected |
| `readOnly` | `boolean` | No | `-` | When true, the value cannot be edited but the control may remain focusable (unlike disabled). |
| `ref` | `null \| (instance: SelectRef \| null) => void \| (() => VoidOrUndefinedOnly) \| RefObject` | No | `-` | - |
| `removeSelectedOptionAccessibilityLabel` | `string` | No | `-` | Accessibility label for each chip in a multi-select |
| `selectAllLabel` | `string` | No | `-` | Label for the Select All option in multi-select mode |
| `setOpen` | `((open: boolean \| ((open: boolean) => boolean)) => void)` | No | `-` | Callback to update the open state |
| `size` | `xs \| s` | No | `s` | Set the size of the chip. |
| `startNode` | `null \| string \| number \| bigint \| false \| true \| ReactElement> \| Iterable \| ReactPortal \| Promise` | No | `-` | Adds content to the start of the inner input. Refer to diagram for location of startNode in InputStack component |
| `style` | `CSSProperties` | No | `-` | Inline styles for the root element |
| `styles` | `{ root?: CSSProperties; control?: CSSProperties \| undefined; controlStartNode?: CSSProperties \| undefined; controlInputNode?: CSSProperties \| undefined; controlValueNode?: CSSProperties \| undefined; controlLabelNode?: CSSProperties \| undefined; controlHelperTextNode?: CSSProperties \| undefined; controlEndNode?: CSSProperties \| undefined; controlBlendStyles?: InteractableBlendStyles \| undefined; dropdown?: CSSProperties \| undefined; option?: CSSProperties \| undefined; optionCell?: CSSProperties \| undefined; optionContent?: CSSProperties \| undefined; optionLabel?: CSSProperties \| undefined; optionDescription?: CSSProperties \| undefined; optionBlendStyles?: InteractableBlendStyles \| undefined; selectAllDivider?: CSSProperties \| undefined; emptyContentsContainer?: CSSProperties \| undefined; emptyContentsText?: CSSProperties \| undefined; optionGroup?: CSSProperties \| undefined; } \| undefined` | No | `-` | Custom styles for individual elements of the Select component |
| `testID` | `string` | No | `-` | Test ID for the root element |
| `type` | `multi \| single` | No | `-` | Whether the select allows single or multiple selections |
## Styles
| Selector | Static class name | Description |
| --- | --- | --- |
| `root` | `-` | Root container element |
| `control` | `-` | Control element |
| `controlStartNode` | `-` | Start node element |
| `controlInputNode` | `-` | Input node element |
| `controlValueNode` | `-` | Value node element |
| `controlLabelNode` | `-` | Label node element |
| `controlHelperTextNode` | `-` | Helper text node element |
| `controlEndNode` | `-` | End node element |
| `controlBlendStyles` | `-` | Blend styles for control interactivity |
| `dropdown` | `-` | Dropdown container element |
| `option` | `-` | Option element |
| `optionCell` | `-` | Option cell element |
| `optionContent` | `-` | Option content wrapper |
| `optionLabel` | `-` | Option label element |
| `optionDescription` | `-` | Option description element |
| `optionBlendStyles` | `-` | Option blend styles for interactivity |
| `selectAllDivider` | `-` | Select all divider element |
| `emptyContentsContainer` | `-` | Empty contents container element |
| `emptyContentsText` | `-` | Empty contents text element |
| `optionGroup` | `-` | Option group element |