# 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-mobile/alpha/select-chip/SelectChip'
```

## Examples

SelectChip is built on top of the [Alpha Select](/components/inputs/SelectAlpha/) component. It provides a chip-styled interface while maintaining all the functionality of Select, including support for single and multi-selection, option groups, and custom components.

:::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.
:::

### Basic usage

```tsx
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<string | null>(null);
  return (
    <SelectChip
      label="Select a value"
      accessibilityLabel="Select a value"
      onChange={setValue}
      options={exampleOptions}
      placeholder="Choose an option"
      value={value}
    />
  );
}
```

### Single select with groups

```tsx
function ExampleSingleGroups() {
  const exampleOptions: Array<SelectOption | SelectOptionGroup> = [
    {
      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<string | null>(null);
  return (
    <SelectChip
      accessibilityLabel="Select a value"
      label="Select a value"
      onChange={setValue}
      options={exampleOptions}
      placeholder="Choose an option"
      value={value}
    />
  );
}
```

### With disabled option group

```tsx
function ExampleDisabledGroup() {
  const exampleOptions: Array<SelectOption | SelectOptionGroup> = [
    {
      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<string | null>(null);
  return (
    <SelectChip
      accessibilityLabel="Select a value"
      label="Select a value"
      onChange={setValue}
      options={exampleOptions}
      placeholder="Choose an option"
      value={value}
    />
  );
}
```

### Multi-select

:::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.
:::

```tsx
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 (
    <SelectChip
      accessibilityLabel="Select multiple values"
      label="Select multiple values"
      onChange={onChange}
      options={exampleOptions}
      placeholder="Choose options"
      type="multi"
      value={value}
    />
  );
}
```

### Multi-select with groups

```tsx
function ExampleMultiGroups() {
  const exampleOptions: Array<SelectOption | SelectOptionGroup> = [
    {
      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 (
    <SelectChip
      accessibilityLabel="Select multiple values"
      label="Select multiple values"
      onChange={onChange}
      options={exampleOptions}
      placeholder="Choose options"
      type="multi"
      value={value}
    />
  );
}
```

### Multi-select with assets

```tsx
function ExampleMultiAssets() {
  const assetImageMap: Record<string, string> = {
    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 (
      <RemoteImageGroup shape="circle" size={24}>
        {value.map((assetValue) => {
          const imageUrl = assetImageMap[assetValue];
          if (!imageUrl) return null;
          return <RemoteImage key={assetValue} source={imageUrl} />;
        })}
      </RemoteImageGroup>
    );
  }, [value]);

  return (
    <SelectChip
      accessibilityLabel="Select multiple assets"
      maxWidth={400}
      onChange={onChange}
      options={exampleOptions}
      placeholder="Choose assets"
      startNode={startNode}
      type="multi"
      value={value}
    />
  );
}
```

### Compact

```tsx
function ExampleCompact() {
  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<string | null>('1');
  return (
    <SelectChip
      compact
      label="Choose an option"
      onChange={setValue}
      options={exampleOptions}
      placeholder="Choose an option"
      value={value}
    />
  );
}
```

### With start and end nodes

```tsx
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<string | null>('eth');
  const getStartNode = (selectedValue: string | null) => {
    if (!selectedValue) return null;
    const assetMap: Record<string, string> = {
      btc: assets.btc.imageUrl,
      eth: assets.eth.imageUrl,
      dai: assets.dai.imageUrl,
    };
    const imageUrl = assetMap[selectedValue];
    if (!imageUrl) return null;
    return <RemoteImage height={24} shape="circle" source={imageUrl} width={24} />;
  };
  return (
    <SelectChip
      label="Choose an asset"
      onChange={setValue}
      options={exampleOptions}
      placeholder="Choose an asset"
      startNode={getStartNode(value)}
      value={value}
    />
  );
}
```

### Empty options

```tsx
function ExampleEmptyOptions() {
  const [value, setValue] = useState<string | null>(null);
  return (
    <SelectChip
      label="Select ..."
      onChange={setValue}
      options={[]}
      placeholder="Select ..."
      value={value}
    />
  );
}
```

### Options with descriptions

```tsx
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<string | null>(null);
  return (
    <SelectChip
      accessibilityLabel="Select a value"
      label="Select a value"
      onChange={setValue}
      options={exampleOptions}
      placeholder="Choose an option"
      value={value}
    />
  );
}
```

### With display value

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.

```tsx
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 (
    <SelectChip
      accessibilityLabel="Select multiple values"
      displayValue={displayValue}
      onChange={onChange}
      options={exampleOptions}
      placeholder="Choose options"
      type="multi"
      value={value}
    />
  );
}
```

### With max width

```tsx
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<string | null>(null);
  return (
    <VStack gap={2}>
      <VStack gap={1}>
        <Text>Default maxWidth (200px):</Text>
        <SelectChip
          accessibilityLabel="Select a value"
          label="Select a value"
          onChange={setValue}
          options={exampleOptions}
          placeholder="Choose an option"
          value={value}
        />
      </VStack>
      <VStack gap={1}>
        <Text>Custom maxWidth (150px):</Text>
        <SelectChip
          accessibilityLabel="Select a value"
          label="Select a value"
          maxWidth={150}
          onChange={setValue}
          options={exampleOptions}
          placeholder="Choose an option"
          value={value}
        />
      </VStack>
      <VStack gap={1}>
        <Text>No maxWidth constraint:</Text>
        <SelectChip
          accessibilityLabel="Select a value"
          label="Select a value"
          maxWidth="100%"
          onChange={setValue}
          options={exampleOptions}
          placeholder="Choose an option"
          value={value}
        />
      </VStack>
    </VStack>
  );
}
```

### Disabled

```tsx
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<string | null>('1');
  return (
    <VStack gap={2}>
      <SelectChip
        disabled
        accessibilityLabel="Select a value"
        onChange={setValue}
        options={exampleOptions}
        placeholder="Choose an option"
        value={null}
      />
      <SelectChip
        disabled
        accessibilityLabel="Select a value"
        onChange={setValue}
        options={exampleOptions}
        placeholder="Choose an option"
        value={value}
      />
    </VStack>
  );
}
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `onChange` | `(value: Type extends multi ? SelectOptionValue \| SelectOptionValue[] \| null : SelectOptionValue \| null) => void` | Yes | `-` | - |
| `options` | `SelectOptionList<Type, SelectOptionValue>` | 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<Type, SelectOptionValue>` | No | `-` | Custom component to render the Select All option |
| `SelectDropdownComponent` | `SelectDropdownComponent<Type, SelectOptionValue>` | No | `-` | Custom component to render the dropdown container |
| `SelectEmptyDropdownContentsComponent` | `SelectEmptyDropdownContentComponent` | No | `-` | Custom component to render when no options are available |
| `SelectOptionComponent` | `SelectOptionComponent<Type, SelectOptionValue>` | No | `-` | Custom component to render individual options |
| `SelectOptionGroupComponent` | `SelectOptionGroupComponent<Type, SelectOptionValue>` | No | `-` | Custom component to render group headers |
| `accessibilityRoles` | `{ option?: AccessibilityRole; } \| undefined` | No | `-` | Accessibility roles for dropdown elements |
| `accessory` | `ReactElement<CellAccessoryProps, string \| JSXElementConstructor<any>>` | No | `-` | - |
| `clearAllLabel` | `string` | No | `-` | Label for the Clear All option in multi-select mode |
| `compact` | `boolean` | No | `-` | Whether to use compact styling for the select |
| `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 \| false \| true \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal` | 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 \| false \| true \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal` | No | `-` | End-aligned content (e.g., value, status). Replaces the deprecated detail prop. |
| `endNode` | `null \| string \| number \| false \| true \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal` | No | `-` | Adds content to the end of the inner input. Refer to diagram for location of endNode in InputStack component |
| `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 |
| `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. |
| `label` | `null \| string \| number \| false \| true \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal` | No | `-` | Label displayed above the control |
| `maxSelectedOptionsToShow` | `number` | No | `-` | Maximum number of selected options to show before truncating |
| `maxWidth` | `string \| number` | 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 \| false \| true \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal` | No | `-` | Placeholder text displayed when no option is selected |
| `ref` | `null \| (instance: SelectRef \| null) => void \| RefObject<SelectRef>` | 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 |
| `startNode` | `null \| string \| number \| false \| true \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal` | No | `-` | Adds content to the start of the inner input. Refer to diagram for location of startNode in InputStack component |
| `style` | `null \| false \| ViewStyle \| RegisteredStyle<ViewStyle> \| RecursiveArray<ViewStyle \| Falsy \| RegisteredStyle<ViewStyle>>` | No | `-` | Inline styles for the root element |
| `styles` | `{ root?: StyleProp<ViewStyle>; control?: StyleProp<ViewStyle>; controlStartNode?: StyleProp<ViewStyle>; controlInputNode?: StyleProp<ViewStyle>; controlValueNode?: StyleProp<ViewStyle>; controlLabelNode?: StyleProp<ViewStyle>; controlHelperTextNode?: StyleProp<ViewStyle>; controlEndNode?: StyleProp<ViewStyle>; controlBlendStyles?: InteractableBlendStyles; dropdown?: StyleProp<ViewStyle>; option?: StyleProp<ViewStyle>; optionCell?: StyleProp<ViewStyle>; optionContent?: StyleProp<ViewStyle>; optionLabel?: StyleProp<ViewStyle>; optionDescription?: StyleProp<ViewStyle>; optionBlendStyles?: InteractableBlendStyles \| undefined; selectAllDivider?: StyleProp<ViewStyle>; emptyContentsContainer?: StyleProp<ViewStyle>; emptyContentsText?: StyleProp<ViewStyle>; optionGroup?: StyleProp<ViewStyle>; } \| undefined` | No | `-` | Custom styles for different parts of the select |
| `testID` | `string` | No | `-` | Test ID for the root element |
| `type` | `multi \| single` | No | `-` | Whether the select allows single or multiple selections |


