# Select

URL: https://ark-ui.com/docs/components/select
Source: https://raw.githubusercontent.com/chakra-ui/ark/refs/heads/main/website/src/content/pages/components/select.mdx

Displays a list of options for the user to pick from.

---

<ComponentPreview id="Select" />

## Anatomy

<Anatomy id="select" />

```tsx
<Select.Root>
  <Select.Label />
  <Select.Control>
    <Select.Trigger>
      <Select.ValueText />
    </Select.Trigger>
    <Select.ClearTrigger />
    <Select.Indicator />
  </Select.Control>
  <Select.Positioner>
    <Select.Content>
      <Select.ItemGroup>
        <Select.ItemGroupLabel />
        <Select.Item>
          <Select.ItemText />
          <Select.ItemIndicator />
        </Select.Item>
      </Select.ItemGroup>
    </Select.Content>
  </Select.Positioner>
  <Select.HiddenSelect />
</Select.Root>
```

## Examples

**Example: basic**

```ripple
import { Portal } from 'ark-ripple/portal';
import { Select, createListCollection } from 'ark-ripple/select';
import { ChevronsUpDown, X } from 'lucide-ripple';
import styles from 'styles/select.module.css';

const frameworks = createListCollection(
  {
    items: [
      { label: 'React', value: 'react' },
      { label: 'Solid', value: 'solid' },
      { label: 'Vue', value: 'vue' },
      { label: 'Svelte', value: 'svelte' },
    ],
  },
);

export component Basic() {
  <Select.Root class={styles.Root} collection={frameworks}>
    <Select.Label class={styles.Label}>{'Framework'}</Select.Label>
    <Select.Control class={styles.Control}>
      <Select.Trigger class={styles.Trigger}>
        <Select.ValueText class={styles.ValueText} placeholder="Select" />
      </Select.Trigger>
      <div class={styles.Indicators}>
        <Select.ClearTrigger class={styles.ClearTrigger}>
          <X />
        </Select.ClearTrigger>
        <Select.Indicator class={styles.Indicator}>
          <ChevronsUpDown />
        </Select.Indicator>
      </div>
    </Select.Control>
    <Portal>
      <Select.Positioner>
        <Select.Content class={styles.Content}>
          <Select.ItemGroup class={styles.ItemGroup}>
            <Select.ItemGroupLabel class={styles.ItemGroupLabel}>
              {'Frameworks'}
            </Select.ItemGroupLabel>
            for (const item of frameworks.items; key item.value) {
              <Select.Item class={styles.Item} {item}>
                <Select.ItemText class={styles.ItemText}>{item.label}</Select.ItemText>
                <Select.ItemIndicator class={styles.ItemIndicator}>{'✓'}</Select.ItemIndicator>
              </Select.Item>
            }
          </Select.ItemGroup>
        </Select.Content>
      </Select.Positioner>
    </Portal>
    <Select.HiddenSelect />
  </Select.Root>
}

```

### Controlled

Use the `value` and `onValueChange` props to control the selected items.

**Example: controlled**

```ripple
import { Portal } from 'ark-ripple/portal';
import { track } from 'ripple';
import { Select, createListCollection } from 'ark-ripple/select';
import { ChevronsUpDown, X } from 'lucide-ripple';
import styles from 'styles/select.module.css';

interface Item {
  label: string;
  value: string;
  disabled?: boolean | undefined;
}

const collection = createListCollection<Item>(
  {
    items: [
      { label: 'React', value: 'react' },
      { label: 'Solid', value: 'solid' },
      { label: 'Vue', value: 'vue' },
      { label: 'Svelte', value: 'svelte', disabled: true },
    ],
  },
);

export component Controlled() {
  let value = track<string[]>([]);
  const setValue = (v: string[]) => {
    @value = v;
  };

  <Select.Root
    class={styles.Root}
    {collection}
    {value}
    onValueChange={(e: any) => setValue(e.value)}
  >
    <Select.Label class={styles.Label}>{'Framework'}</Select.Label>
    <Select.Control class={styles.Control}>
      <Select.Trigger class={styles.Trigger}>
        <Select.ValueText class={styles.ValueText} placeholder="Select a Framework" />
      </Select.Trigger>
      <div class={styles.Indicators}>
        <Select.ClearTrigger class={styles.ClearTrigger}>
          <X />
        </Select.ClearTrigger>
        <Select.Indicator class={styles.Indicator}>
          <ChevronsUpDown />
        </Select.Indicator>
      </div>
    </Select.Control>
    <Portal>
      <Select.Positioner>
        <Select.Content class={styles.Content}>
          <Select.ItemGroup class={styles.ItemGroup}>
            <Select.ItemGroupLabel class={styles.ItemGroupLabel}>
              {'Frameworks'}
            </Select.ItemGroupLabel>
            for (const item of collection.items; key item.value) {
              <Select.Item class={styles.Item} {item}>
                <Select.ItemText class={styles.ItemText}>{item.label}</Select.ItemText>
                <Select.ItemIndicator class={styles.ItemIndicator}>{'✓'}</Select.ItemIndicator>
              </Select.Item>
            }
          </Select.ItemGroup>
        </Select.Content>
      </Select.Positioner>
    </Portal>
    <Select.HiddenSelect />
  </Select.Root>
}

```

### Root Provider

An alternative way to control the select is to use the `RootProvider` component and the `useSelect` hook. This way you
can access the state and methods from outside the component.

**Example: root-provider**

```ripple
import { Portal } from 'ark-ripple/portal';
import { Select, createListCollection, useSelect } from 'ark-ripple/select';
import { ChevronsUpDown, X } from 'lucide-ripple';
import styles from 'styles/select.module.css';

const frameworks = createListCollection(
  {
    items: [
      { label: 'React', value: 'react' },
      { label: 'Solid', value: 'solid' },
      { label: 'Vue', value: 'vue' },
      { label: 'Svelte', value: 'svelte' },
    ],
  },
);

export component RootProvider() {
  const select = useSelect({ collection: frameworks });

  <div>
    <output>
      {'selected: '}
      {JSON.stringify(@select.value)}
    </output>

    <Select.RootProvider class={styles.Root} value={select}>
      <Select.Label class={styles.Label}>{'Framework'}</Select.Label>
      <Select.Control class={styles.Control}>
        <Select.Trigger class={styles.Trigger}>
          <Select.ValueText class={styles.ValueText} placeholder="Select a Framework" />
        </Select.Trigger>
        <div class={styles.Indicators}>
          <Select.ClearTrigger class={styles.ClearTrigger}>
            <X />
          </Select.ClearTrigger>
          <Select.Indicator class={styles.Indicator}>
            <ChevronsUpDown />
          </Select.Indicator>
        </div>
      </Select.Control>
      <Portal>
        <Select.Positioner>
          <Select.Content class={styles.Content}>
            <Select.ItemGroup class={styles.ItemGroup}>
              <Select.ItemGroupLabel class={styles.ItemGroupLabel}>
                {'Frameworks'}
              </Select.ItemGroupLabel>
              for (const item of frameworks.items; key item.value) {
                <Select.Item class={styles.Item} {item}>
                  <Select.ItemText class={styles.ItemText}>{item.label}</Select.ItemText>
                  <Select.ItemIndicator class={styles.ItemIndicator}>{'✓'}</Select.ItemIndicator>
                </Select.Item>
              }
            </Select.ItemGroup>
          </Select.Content>
        </Select.Positioner>
      </Portal>
      <Select.HiddenSelect />
    </Select.RootProvider>
  </div>
}

```

### Multiple

To enable `multiple` item selection:

**Example: multiple**

```ripple
import { Portal } from 'ark-ripple/portal';
import { Select, createListCollection } from 'ark-ripple/select';
import { ChevronsUpDown, X } from 'lucide-ripple';
import styles from 'styles/select.module.css';

const frameworks = createListCollection(
  {
    items: [
      { label: 'React', value: 'react' },
      { label: 'Solid', value: 'solid' },
      { label: 'Vue', value: 'vue' },
      { label: 'Svelte', value: 'svelte', disabled: true },
    ],
  },
);

export component Multiple() {
  <Select.Root class={styles.Root} collection={frameworks} multiple>
    <Select.Label class={styles.Label}>{'Framework'}</Select.Label>
    <Select.Control class={styles.Control}>
      <Select.Trigger class={styles.Trigger}>
        <Select.ValueText class={styles.ValueText} placeholder="Select" />
      </Select.Trigger>
      <div class={styles.Indicators}>
        <Select.ClearTrigger class={styles.ClearTrigger}>
          <X />
        </Select.ClearTrigger>
        <Select.Indicator class={styles.Indicator}>
          <ChevronsUpDown />
        </Select.Indicator>
      </div>
    </Select.Control>
    <Portal>
      <Select.Positioner>
        <Select.Content class={styles.Content}>
          <Select.ItemGroup class={styles.ItemGroup}>
            <Select.ItemGroupLabel class={styles.ItemGroupLabel}>
              {'Frameworks'}
            </Select.ItemGroupLabel>
            for (const item of frameworks.items; key item.value) {
              <Select.Item class={styles.Item} {item}>
                <Select.ItemText class={styles.ItemText}>{item.label}</Select.ItemText>
                <Select.ItemIndicator class={styles.ItemIndicator}>{'✓'}</Select.ItemIndicator>
              </Select.Item>
            }
          </Select.ItemGroup>
        </Select.Content>
      </Select.Positioner>
    </Portal>
    <Select.HiddenSelect />
  </Select.Root>
}

```

### Grouping

Grouping related options can be useful for organizing options into categories.

- Use the `groupBy` prop to configure the grouping of the items.
- Use the `collection.group()` method to get the grouped items.
- Use the `Select.ItemGroup` and `Select.ItemGroupLabel` components to render the grouped items.

**Example: grouping**

```ripple
import { Portal } from 'ark-ripple/portal';
import { Select, createListCollection } from 'ark-ripple/select';
import { ChevronsUpDown, X } from 'lucide-ripple';
import styles from 'styles/select.module.css';

const frameworks = createListCollection(
  {
    items: [
      { label: 'React', value: 'react', type: 'JS' },
      { label: 'Solid', value: 'solid', type: 'JS' },
      { label: 'Vue', value: 'vue', type: 'JS' },
      { label: 'Panda', value: 'panda', type: 'CSS' },
      { label: 'Tailwind', value: 'tailwind', type: 'CSS' },
    ],
    groupBy: (item: any) => item.type,
  },
);

export component Grouping() {
  <Select.Root class={styles.Root} collection={frameworks}>
    <Select.Label class={styles.Label}>{'Framework'}</Select.Label>
    <Select.Control class={styles.Control}>
      <Select.Trigger class={styles.Trigger}>
        <Select.ValueText class={styles.ValueText} placeholder="Select a Framework" />
      </Select.Trigger>
      <div class={styles.Indicators}>
        <Select.ClearTrigger class={styles.ClearTrigger}>
          <X />
        </Select.ClearTrigger>
        <Select.Indicator class={styles.Indicator}>
          <ChevronsUpDown />
        </Select.Indicator>
      </div>
    </Select.Control>
    <Portal>
      <Select.Positioner>
        <Select.Content class={styles.Content}>
          for (const [type, group] of frameworks.group(); key type) {
            <Select.ItemGroup class={styles.ItemGroup}>
              <Select.ItemGroupLabel class={styles.ItemGroupLabel}>{type}</Select.ItemGroupLabel>
              for (const item of group; key item.value) {
                <Select.Item class={styles.Item} {item}>
                  <Select.ItemText class={styles.ItemText}>{item.label}</Select.ItemText>
                  <Select.ItemIndicator class={styles.ItemIndicator}>{'✓'}</Select.ItemIndicator>
                </Select.Item>
              }
            </Select.ItemGroup>
          }
        </Select.Content>
      </Select.Positioner>
    </Portal>
    <Select.HiddenSelect />
  </Select.Root>
}

```

### Field

Use `Field` to manage form state, ARIA labels, helper text, and error text.

**Example: with-field**

```ripple
import { Select, createListCollection } from 'ark-ripple/select';
import { Field } from 'ark-ripple/field';
import { ChevronsUpDown } from 'lucide-ripple';
import field from 'styles/field.module.css';
import styles from 'styles/select.module.css';

const collection = createListCollection({ items: ['React', 'Solid', 'Vue', 'Svelte'] });

export component WithField() {
  <Field.Root class={field.Root}>
    <Select.Root {collection} class={styles.Root}>
      <Select.Label class={styles.Label}>{'Label'}</Select.Label>
      <Select.Control class={styles.Control}>
        <Select.Trigger class={styles.Trigger}>
          <Select.ValueText class={styles.ValueText} placeholder="Select a Framework" />
          <Select.Indicator class={styles.Indicator}>
            <ChevronsUpDown />
          </Select.Indicator>
        </Select.Trigger>
      </Select.Control>
      <Select.Positioner>
        <Select.Content class={styles.Content}>
          for (const item of collection.items; key item) {
            <Select.Item class={styles.Item} {item}>
              <Select.ItemText class={styles.ItemText}>{item}</Select.ItemText>
              <Select.ItemIndicator class={styles.ItemIndicator}>{'✓'}</Select.ItemIndicator>
            </Select.Item>
          }
        </Select.Content>
      </Select.Positioner>
      <Select.HiddenSelect />
    </Select.Root>
    <Field.HelperText class={field.HelperText}>{'Additional Info'}</Field.HelperText>
    <Field.ErrorText class={field.ErrorText}>{'Error Info'}</Field.ErrorText>
  </Field.Root>
}

```

### Form Usage

Here's an example of integrating the `Select` component with a form library.

**Example: form-library**

```ripple
import { Portal } from 'ark-ripple/portal';
import { track } from 'ripple';
import { Select, createListCollection } from 'ark-ripple/select';
import { ChevronsUpDown, X } from 'lucide-ripple';
import button from 'styles/button.module.css';
import styles from 'styles/select.module.css';

const collection = createListCollection(
  {
    items: ['React', 'Solid', 'Vue', 'Svelte'],
  },
);

export component FormLibrary() {
  let value = track<string[]>(['React']);
  const setValue = (v: string[]) => {
    @value = v;
  };

  const handleSubmit = (e: any) => {
    e.preventDefault();
    window.alert(JSON.stringify(new FormData(e.currentTarget).getAll('framework')));
  };

  // TODO: Add form library instead of native form
  <form onSubmit={handleSubmit}>
    <Select.Root
      class={styles.Root}
      {collection}
      {value}
      onValueChange={(e: any) => setValue(e.value)}
    >
      <Select.Label class={styles.Label}>{'Framework'}</Select.Label>
      <Select.HiddenSelect />
      <Select.Control class={styles.Control}>
        <Select.Trigger class={styles.Trigger}>
          <Select.ValueText class={styles.ValueText} placeholder="Select a Framework" />
        </Select.Trigger>
        <div class={styles.Indicators}>
          <Select.ClearTrigger class={styles.ClearTrigger}>
            <X />
          </Select.ClearTrigger>
          <Select.Indicator class={styles.Indicator}>
            <ChevronsUpDown />
          </Select.Indicator>
        </div>
      </Select.Control>
      <Portal>
        <Select.Positioner>
          <Select.Content class={styles.Content}>
            <Select.ItemGroup class={styles.ItemGroup}>
              <Select.ItemGroupLabel class={styles.ItemGroupLabel}>
                {'Frameworks'}
              </Select.ItemGroupLabel>
              for (const item of collection.items; key item) {
                <Select.Item class={styles.Item} {item}>
                  <Select.ItemText class={styles.ItemText}>{item}</Select.ItemText>
                  <Select.ItemIndicator class={styles.ItemIndicator}>{'✓'}</Select.ItemIndicator>
                </Select.Item>
              }
            </Select.ItemGroup>
          </Select.Content>
        </Select.Positioner>
      </Portal>
    </Select.Root>
    <button class={button.Root} style="margin-top: 1rem" type="submit">{'Submit'}</button>
  </form>
}

```

### Async Loading

Here's an example of how to load the items asynchronously when the select is opened.

**Example: async**

```ripple
import { Portal } from 'ark-ripple/portal';
import { track } from 'ripple';
import { Select, createListCollection } from 'ark-ripple/select';
import { ChevronsUpDown } from 'lucide-ripple';
import styles from 'styles/select.module.css';

function loadData() {
  return new Promise<string[]>((resolve) => {
    setTimeout(() => resolve(['React', 'Solid', 'Vue', 'Svelte', 'Angular', 'Ember']), 500);
  });
}

export component Async() {
  let data = track<string[] | null>(null);
  let loading = track(false);
  let error = track<Error | null>(null);

  const collection = track(() => createListCollection<string>({ items: @data ?? [] }));

  const setData = (v: string[] | null) => {
    @data = v;
  };
  const setLoading = (v: boolean) => {
    @loading = v;
  };
  const setError = (v: Error | null) => {
    @error = v;
  };

  const handleOpenChange = (details: any) => {
    if (details.open && @data === null) {
      setLoading(true);
      setError(null);
      loadData().then((data) => setData(data)).catch((err) => setError(err)).finally(
        () => setLoading(false),
      );
    }
  };

  <Select.Root class={styles.Root} {collection} onOpenChange={handleOpenChange}>
    <Select.Label class={styles.Label}>{'Framework'}</Select.Label>
    <Select.Control class={styles.Control}>
      <Select.Trigger class={styles.Trigger}>
        <Select.ValueText class={styles.ValueText} placeholder="Select" />
        <Select.Indicator class={styles.Indicator}>
          <ChevronsUpDown />
        </Select.Indicator>
      </Select.Trigger>
    </Select.Control>
    <Portal>
      <Select.Positioner>
        <Select.Content class={styles.Content}>
          if (@loading) {
            <div class={styles.Item}>{'Loading...'}</div>
          } else if (@error) {
            <div class={styles.Item}>
              {'Error: '}
              {@error.message}
            </div>
          } else {
            for (const item of @collection.items; key item) {
              <Select.Item class={styles.Item} {item}>
                <Select.ItemText class={styles.ItemText}>{item}</Select.ItemText>
                <Select.ItemIndicator class={styles.ItemIndicator}>{'✓'}</Select.ItemIndicator>
              </Select.Item>
            }
          }
        </Select.Content>
      </Select.Positioner>
    </Portal>
    <Select.HiddenSelect />
  </Select.Root>
}

```

### Lazy Mount

Use `lazyMount` and `unmountOnExit` to control when content is mounted, improving performance.

**Example: lazy-mount**

```ripple
import { Portal } from 'ark-ripple/portal';
import { Select, createListCollection } from 'ark-ripple/select';
import { ChevronsUpDown } from 'lucide-ripple';
import styles from 'styles/select.module.css';

const collection = createListCollection(
  {
    items: ['React', 'Solid', 'Vue', 'Svelte', 'Angular', 'Alpine'],
  },
);

export component LazyMount() {
  <Select.Root class={styles.Root} {collection} lazyMount unmountOnExit>
    <Select.Label class={styles.Label}>{'Framework'}</Select.Label>
    <Select.Control class={styles.Control}>
      <Select.Trigger class={styles.Trigger}>
        <Select.ValueText class={styles.ValueText} placeholder="Select a Framework" />
        <Select.Indicator class={styles.Indicator}>
          <ChevronsUpDown />
        </Select.Indicator>
      </Select.Trigger>
      <Select.ClearTrigger class={styles.ClearTrigger}>{'Clear'}</Select.ClearTrigger>
    </Select.Control>
    <Portal>
      <Select.Positioner>
        <Select.Content class={styles.Content}>
          <Select.ItemGroup class={styles.ItemGroup}>
            <Select.ItemGroupLabel class={styles.ItemGroupLabel}>
              {'Frameworks'}
            </Select.ItemGroupLabel>
            for (const item of collection.items; key item) {
              <Select.Item class={styles.Item} {item}>
                <Select.ItemText class={styles.ItemText}>{item}</Select.ItemText>
                <Select.ItemIndicator class={styles.ItemIndicator}>{'✓'}</Select.ItemIndicator>
              </Select.Item>
            }
          </Select.ItemGroup>
        </Select.Content>
      </Select.Positioner>
    </Portal>
    <Select.HiddenSelect />
  </Select.Root>
}

```

### Select on Highlight

Here's an example of automatically selecting items when they are highlighted (hovered or navigated to with keyboard).

**Example: select-on-highlight**

```ripple
import { Portal } from 'ark-ripple/portal';
import { Select, createListCollection, useSelect } from 'ark-ripple/select';
import { ChevronsUpDown } from 'lucide-ripple';
import styles from 'styles/select.module.css';

const collection = createListCollection(
  {
    items: ['React', 'Solid', 'Vue', 'Svelte'],
  },
);

export component SelectOnHighlight() {
  const select = useSelect(
    {
      collection,
      onHighlightChange(details: any) {
        if (details.highlightedValue) {
          @select.selectValue(details.highlightedValue);
        }
      },
    },
  );

  <Select.RootProvider class={styles.Root} value={select}>
    <Select.Label class={styles.Label}>{'Framework'}</Select.Label>
    <Select.Control class={styles.Control}>
      <Select.Trigger class={styles.Trigger}>
        <Select.ValueText class={styles.ValueText} placeholder="Select a Framework" />
        <Select.Indicator class={styles.Indicator}>
          <ChevronsUpDown />
        </Select.Indicator>
      </Select.Trigger>
      <Select.ClearTrigger class={styles.ClearTrigger}>{'Clear'}</Select.ClearTrigger>
    </Select.Control>
    <Portal>
      <Select.Positioner>
        <Select.Content class={styles.Content}>
          <Select.ItemGroup class={styles.ItemGroup}>
            <Select.ItemGroupLabel class={styles.ItemGroupLabel}>
              {'Frameworks'}
            </Select.ItemGroupLabel>
            for (const item of collection.items; key item) {
              <Select.Item class={styles.Item} {item}>
                <Select.ItemText class={styles.ItemText}>{item}</Select.ItemText>
                <Select.ItemIndicator class={styles.ItemIndicator}>{'✓'}</Select.ItemIndicator>
              </Select.Item>
            }
          </Select.ItemGroup>
        </Select.Content>
      </Select.Positioner>
    </Portal>
    <Select.HiddenSelect />
  </Select.RootProvider>
}

```

### Max Selection

Here's an example of limiting the number of items that can be selected in a multiple select.

**Example: max-selected**

```ripple
import { Portal } from 'ark-ripple/portal';
import { track } from 'ripple';
import { Select, createListCollection } from 'ark-ripple/select';
import { ChevronsUpDown, X } from 'lucide-ripple';
import styles from 'styles/select.module.css';

const items = ['React', 'Solid', 'Vue', 'Svelte'];
const MAX_SELECTION = 2;
const hasReachedMax = (value: string[]) => value.length >= MAX_SELECTION;

export component MaxSelected() {
  let value = track<string[]>([]);
  const setValue = (v: string[]) => {
    @value = v;
  };

  const collection = track(
    () => createListCollection(
      {
        items: items.map(
          (item) => ({
            label: item,
            value: item,
            disabled: hasReachedMax(@value) && !@value.includes(item),
          }),
        ),
      },
    ),
  );

  const handleValueChange = (details: Select.ValueChangeDetails) => {
    if (hasReachedMax(@value) && details.value.length > @value.length) return;
    setValue(details.value);
  };

  <Select.Root class={styles.Root} multiple {value} {collection} onValueChange={handleValueChange}>
    <Select.Label class={styles.Label}>{'Framework'}</Select.Label>
    <Select.Control class={styles.Control}>
      <Select.Trigger class={styles.Trigger}>
        <Select.ValueText class={styles.ValueText} placeholder="Select" />
        <Select.Indicator class={styles.Indicator}>
          <ChevronsUpDown />
        </Select.Indicator>
      </Select.Trigger>
      <Select.ClearTrigger class={styles.ClearTrigger}>
        <X />
      </Select.ClearTrigger>
    </Select.Control>
    <Portal>
      <Select.Positioner>
        <Select.Content class={styles.Content}>
          <Select.ItemGroup class={styles.ItemGroup}>
            <Select.ItemGroupLabel class={styles.ItemGroupLabel}>
              {'Frameworks'}
            </Select.ItemGroupLabel>
            for (const item of @collection.items; key item.value) {
              <Select.Item class={styles.Item} {item}>
                <Select.ItemText class={styles.ItemText}>{item.label}</Select.ItemText>
                <Select.ItemIndicator class={styles.ItemIndicator}>{'✓'}</Select.ItemIndicator>
              </Select.Item>
            }
          </Select.ItemGroup>
        </Select.Content>
      </Select.Positioner>
    </Portal>
    <Select.HiddenSelect />
  </Select.Root>
}

```

### Select All

Use `selectAll()` from the select context to select all items at once.

**Example: select-all**

```ripple
import { Portal } from 'ark-ripple/portal';
import { Select, createListCollection, useSelectContext } from 'ark-ripple/select';
import { ChevronsUpDown } from 'lucide-ripple';
import button from 'styles/button.module.css';
import styles from 'styles/select.module.css';

component SelectAllButton() {
  const select = useSelectContext();
  <Select.Context>
    component children({ context }) {
      <button
        class={button.Root}
        style="width: 100%; margin-bottom: 0.25rem"
        type="button"
        onClick={() => {
          @context.selectAll();
          context.setOpen(false);
        }}
      >
        {'Select All'}
      </button>
    }
  </Select.Context>
}

export component SelectAll() {
  const collection = createListCollection({ items: ['React', 'Solid', 'Vue', 'Svelte'] });

  <Select.Root class={styles.Root} {collection}>
    <Select.Label class={styles.Label}>{'Framework'}</Select.Label>
    <Select.Control class={styles.Control}>
      <Select.Trigger class={styles.Trigger}>
        <Select.ValueText class={styles.ValueText} placeholder="Select a Framework" />
        <Select.Indicator class={styles.Indicator}>
          <ChevronsUpDown />
        </Select.Indicator>
      </Select.Trigger>
      <Select.ClearTrigger class={styles.ClearTrigger}>{'Clear'}</Select.ClearTrigger>
    </Select.Control>
    <Portal>
      <Select.Positioner>
        <Select.Content class={styles.Content}>
          <SelectAllButton />
          for (const item of collection.items; key item) {
            <Select.Item class={styles.Item} {item}>
              <Select.ItemText class={styles.ItemText}>{item}</Select.ItemText>
              <Select.ItemIndicator class={styles.ItemIndicator}>{'✓'}</Select.ItemIndicator>
            </Select.Item>
          }
        </Select.Content>
      </Select.Positioner>
    </Portal>
    <Select.HiddenSelect />
  </Select.Root>
}

```

### Overflow

For selects with many items, use `positioning.fitViewport` to ensure the dropdown fits within the viewport. Combine with
a max-height on the content to enable scrolling.

**Example: overflow**

```ripple
import { Portal } from 'ark-ripple/portal';
import { Select, createListCollection } from 'ark-ripple/select';
import { ChevronsUpDown } from 'lucide-ripple';
import styles from 'styles/select.module.css';

const collection = createListCollection(
  {
    items: [
      'Name 1',
      'Name 2',
      'Name 3',
      'Name 4',
      'Name 5',
      'Name 6',
      'Name 7',
      'Name 8',
      'Name 9',
      'Name 10',
      'Name 11',
      'Name 12',
      'Name 13',
      'Name 14',
    ],
  },
);

export component Overflow() {
  <Select.Root
    class={styles.Root}
    {collection}
    positioning={{ fitViewport: true, placement: 'bottom-start', sameWidth: true }}
  >
    <Select.Label class={styles.Label}>{'Framework'}</Select.Label>
    <Select.Control class={styles.Control}>
      <Select.Trigger class={styles.Trigger}>
        <Select.ValueText class={styles.ValueText} placeholder="Select a Framework" />
        <Select.Indicator class={styles.Indicator}>
          <ChevronsUpDown />
        </Select.Indicator>
      </Select.Trigger>
      <Select.ClearTrigger class={styles.ClearTrigger}>{'Clear'}</Select.ClearTrigger>
    </Select.Control>
    <Portal>
      <Select.Positioner>
        <Select.Content class={styles.Content} style="max-height: 200px">
          <Select.ItemGroup class={styles.ItemGroup}>
            <Select.ItemGroupLabel class={styles.ItemGroupLabel}>{'Names'}</Select.ItemGroupLabel>
            for (const item of collection.items; key item) {
              <Select.Item class={styles.Item} {item}>
                <Select.ItemText class={styles.ItemText}>{item}</Select.ItemText>
                <Select.ItemIndicator class={styles.ItemIndicator}>{'✓'}</Select.ItemIndicator>
              </Select.Item>
            }
          </Select.ItemGroup>
        </Select.Content>
      </Select.Positioner>
    </Portal>
    <Select.HiddenSelect />
  </Select.Root>
}

```

## Guides

### Nested Usage

When using the Select component within a `Popover` or `Dialog`, avoid rendering its content within a `Portal` or
`Teleport`.

This ensures the Select's content stays within the Popover/Dialog's DOM hierarchy rather than being portalled to the
document body, maintaining proper interaction and accessibility behavior.

### Hidden Select

The `Select.HiddenSelect` component renders a native HTML `<select>` element that's visually hidden but remains in the
DOM. This component is essential for:

- **Form submission**: Native form submission and serialization work seamlessly since the actual `<select>` element
  exists in the DOM
- **Browser auto-fill**: Browsers can properly auto-fill the select based on previously submitted form data
- **Progressive enhancement**: Forms remain functional even if JavaScript fails to load

```tsx
<Select.Root>
  <Select.HiddenSelect />
  {/* Other Select components */}
</Select.Root>
```

The hidden select automatically syncs with the Select component's value, ensuring form data is always up-to-date.

### Empty State

You can create an empty state component that displays when there are no items in the collection. Use the
`useSelectContext` hook to check the collection size:

```tsx
component SelectEmpty(props) {
  const select = useSelectContext()
  if (@select.collection.size === 0) {
    <div {...props} role="presentation" />
  }
}
```

Then use it within your Select content:

```tsx
<Select.Content>
  <SelectEmpty>{"No items to display"}</SelectEmpty>
  {/* Your items */}
</Select.Content>
```

### Available Size

The following css variables are exposed to the `Select.Positioner` which you can use to style the `Select.Content`

```css
/* width of the select trigger */
--reference-width: <pixel-value>;
/* width of the available viewport */
--available-width: <pixel-value>;
/* height of the available viewport */
--available-height: <pixel-value>;
```

For example, if you want to make sure the maximum height doesn't exceed the available height, you can use the following:

```css
[data-scope='select'][data-part='content'] {
  max-height: calc(var(--available-height) - 100px);
}
```

## API Reference

### Props

**Component API Reference**

**Root Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `collection` | `ListCollection<T>` | Yes | The collection of items |
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |
| `autoComplete` | `string` | No | The autocomplete attribute for the hidden select. Enables browser autofill (e.g. "address-level1" for state). |
| `closeOnSelect` | `boolean` | No | Whether the select should close after an item is selected |
| `composite` | `boolean` | No | Whether the select is a composed with other composite widgets like tabs or combobox |
| `defaultHighlightedValue` | `string` | No | The initial value of the highlighted item when opened.
Use when you don't need to control the highlighted value of the select. |
| `defaultOpen` | `boolean` | No | Whether the select's open state is controlled by the user |
| `defaultValue` | `string[]` | No | The initial default value of the select when rendered.
Use when you don't need to control the value of the select. |
| `deselectable` | `boolean` | No | Whether the value can be cleared by clicking the selected item.

**Note:** this is only applicable for single selection |
| `disabled` | `boolean` | No | Whether the select is disabled |
| `form` | `string` | No | The associate form of the underlying select. |
| `highlightedValue` | `string` | No | The controlled key of the highlighted item |
| `id` | `string` | No | The unique identifier of the machine. |
| `ids` | `Partial<{
  root: string
  content: string
  control: string
  trigger: string
  clearTrigger: string
  label: string
  hiddenSelect: string
  positioner: string
  item: (id: string | number) => string
  itemGroup: (id: string | number) => string
  itemGroupLabel: (id: string | number) => string
}>` | No | The ids of the elements in the select. Useful for composition. |
| `immediate` | `boolean` | No | Whether to synchronize the present change immediately or defer it to the next frame |
| `invalid` | `boolean` | No | Whether the select is invalid |
| `lazyMount` | `boolean` | No | Whether to enable lazy mounting |
| `loopFocus` | `boolean` | No | Whether to loop the keyboard navigation through the options |
| `multiple` | `boolean` | No | Whether to allow multiple selection |
| `name` | `string` | No | The `name` attribute of the underlying select. |
| `onExitComplete` | `VoidFunction` | No | Function called when the animation ends in the closed state |
| `onFocusOutside` | `(event: FocusOutsideEvent) => void` | No | Function called when the focus is moved outside the component |
| `onHighlightChange` | `(details: HighlightChangeDetails<T>) => void` | No | The callback fired when the highlighted item changes. |
| `onInteractOutside` | `(event: InteractOutsideEvent) => void` | No | Function called when an interaction happens outside the component |
| `onOpenChange` | `(details: OpenChangeDetails) => void` | No | Function called when the popup is opened |
| `onPointerDownOutside` | `(event: PointerDownOutsideEvent) => void` | No | Function called when the pointer is pressed down outside the component |
| `onSelect` | `(details: SelectionDetails) => void` | No | Function called when an item is selected |
| `onValueChange` | `(details: ValueChangeDetails<T>) => void` | No | The callback fired when the selected item changes. |
| `open` | `boolean` | No | Whether the select menu is open |
| `positioning` | `PositioningOptions` | No | The positioning options of the menu. |
| `present` | `boolean` | No | Whether the node is present (controlled by the user) |
| `readOnly` | `boolean` | No | Whether the select is read-only |
| `required` | `boolean` | No | Whether the select is required |
| `scrollToIndexFn` | `(details: ScrollToIndexDetails) => void` | No | Function to scroll to a specific index |
| `skipAnimationOnMount` | `boolean` | No | Whether to allow the initial presence animation. |
| `unmountOnExit` | `boolean` | No | Whether to unmount on exit. |
| `value` | `string[]` | No | The controlled keys of the selected items |


**Root Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | select |
| `[data-part]` | root |
| `[data-invalid]` | Present when invalid |
| `[data-readonly]` | Present when read-only |


**ClearTrigger Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**ClearTrigger Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | select |
| `[data-part]` | clear-trigger |
| `[data-invalid]` | Present when invalid |


**Content Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Content Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | select |
| `[data-part]` | content |
| `[data-state]` | "open" | "closed" |
| `[data-nested]` | listbox |
| `[data-has-nested]` | listbox |
| `[data-placement]` | The placement of the content |
| `[data-activedescendant]` | The id the active descendant of the content |


**Content CSS Variables:**

| Variable | Description |
|----------|-------------|
| `--layer-index` | The index of the dismissable in the layer stack |
| `--nested-layer-count` | The number of nested selects |


**Control Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Control Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | select |
| `[data-part]` | control |
| `[data-state]` | "open" | "closed" |
| `[data-focus]` | Present when focused |
| `[data-disabled]` | Present when disabled |
| `[data-invalid]` | Present when invalid |


**HiddenSelect Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Indicator Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Indicator Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | select |
| `[data-part]` | indicator |
| `[data-state]` | "open" | "closed" |
| `[data-disabled]` | Present when disabled |
| `[data-invalid]` | Present when invalid |
| `[data-readonly]` | Present when read-only |


**ItemGroupLabel Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**ItemGroup Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**ItemGroup Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | select |
| `[data-part]` | item-group |
| `[data-disabled]` | Present when disabled |


**ItemIndicator Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**ItemIndicator Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | select |
| `[data-part]` | item-indicator |
| `[data-state]` | "checked" | "unchecked" |


**Item Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |
| `item` | `any` | No | The item to render |
| `persistFocus` | `boolean` | No | Whether hovering outside should clear the highlighted state |


**Item Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | select |
| `[data-part]` | item |
| `[data-value]` | The value of the item |
| `[data-state]` | "checked" | "unchecked" |
| `[data-highlighted]` | Present when highlighted |
| `[data-disabled]` | Present when disabled |


**ItemText Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**ItemText Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | select |
| `[data-part]` | item-text |
| `[data-state]` | "checked" | "unchecked" |
| `[data-disabled]` | Present when disabled |
| `[data-highlighted]` | Present when highlighted |


**Label Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Label Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | select |
| `[data-part]` | label |
| `[data-disabled]` | Present when disabled |
| `[data-invalid]` | Present when invalid |
| `[data-readonly]` | Present when read-only |
| `[data-required]` | Present when required |


**List Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Positioner Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Positioner CSS Variables:**

| Variable | Description |
|----------|-------------|
| `--reference-width` | The width of the reference element |
| `--reference-height` | The height of the root |
| `--available-width` | The available width in viewport |
| `--available-height` | The available height in viewport |
| `--x` | The x position for transform |
| `--y` | The y position for transform |
| `--z-index` | The z-index value |
| `--transform-origin` | The transform origin for animations |


**RootProvider Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `value` | `UseSelectReturn<T>` | Yes |  |
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |
| `immediate` | `boolean` | No | Whether to synchronize the present change immediately or defer it to the next frame |
| `lazyMount` | `boolean` | No | Whether to enable lazy mounting |
| `onExitComplete` | `VoidFunction` | No | Function called when the animation ends in the closed state |
| `present` | `boolean` | No | Whether the node is present (controlled by the user) |
| `skipAnimationOnMount` | `boolean` | No | Whether to allow the initial presence animation. |
| `unmountOnExit` | `boolean` | No | Whether to unmount on exit. |


**Trigger Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Trigger Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | select |
| `[data-part]` | trigger |
| `[data-state]` | "open" | "closed" |
| `[data-disabled]` | Present when disabled |
| `[data-invalid]` | Present when invalid |
| `[data-readonly]` | Present when read-only |
| `[data-placement]` | The placement of the trigger |
| `[data-placeholder-shown]` | Present when placeholder is shown |


**ValueText Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |
| `placeholder` | `string` | No | Text to display when no value is selected. |


**ValueText Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | select |
| `[data-part]` | value-text |
| `[data-disabled]` | Present when disabled |
| `[data-invalid]` | Present when invalid |
| `[data-focus]` | Present when focused |


### Context

**API:**

| Property | Type | Description |
|----------|------|-------------|
| `focused` | `boolean` | Whether the select is focused |
| `open` | `boolean` | Whether the select is open |
| `empty` | `boolean` | Whether the select value is empty |
| `highlightedValue` | `string` | The value of the highlighted item |
| `highlightedItem` | `V` | The highlighted item |
| `setHighlightValue` | `(value: string) => void` | Function to highlight a value |
| `clearHighlightValue` | `VoidFunction` | Function to clear the highlighted value |
| `selectedItems` | `V[]` | The selected items |
| `hasSelectedItems` | `boolean` | Whether there's a selected option |
| `value` | `string[]` | The selected item keys |
| `valueAsString` | `string` | The string representation of the selected items |
| `selectValue` | `(value: string) => void` | Function to select a value |
| `selectAll` | `VoidFunction` | Function to select all values |
| `setValue` | `(value: string[]) => void` | Function to set the value of the select |
| `clearValue` | `(value?: string) => void` | Function to clear the value of the select.
If a value is provided, it will only clear that value, otherwise, it will clear all values. |
| `focus` | `VoidFunction` | Function to focus on the select input |
| `getItemState` | `(props: ItemProps<any>) => ItemState` | Returns the state of a select item |
| `setOpen` | `(open: boolean) => void` | Function to open or close the select |
| `collection` | `ListCollection<V>` | Function to toggle the select |
| `reposition` | `(options?: Partial<PositioningOptions>) => void` | Function to set the positioning options of the select |
| `multiple` | `boolean` | Whether the select allows multiple selections |
| `disabled` | `boolean` | Whether the select is disabled |


## Accessibility

Complies with the [Listbox WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/).

### Keyboard Support

<KeyBindingsTable id="select" />