Select

A dropdown menu for selecting a value from a list of options.

View source
import * as Select from "@danielfrg/solid-ui/select"
import styles from "./index.module.css"

const FRUITS = ["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]

export function DemoSelectHero() {
  return (
    <Select.Root
      options={FRUITS}
      placeholder="Select a fruit..."
      itemComponent={(props) => (
        <Select.Item item={props.item} class={styles.item}>
          <Select.ItemIndicator class={styles["item-indicator"]}>
            <CheckIcon />
          </Select.ItemIndicator>
          <Select.ItemLabel>{props.item.rawValue}</Select.ItemLabel>
        </Select.Item>
      )}
    >
      <Select.Trigger class={styles.trigger} aria-label="Fruit">
        <Select.Value<string> class={styles.value}>{(state) => state.selectedOption()}</Select.Value>
        <Select.Icon class={styles.icon}>
          <ChevronDownIcon />
        </Select.Icon>
      </Select.Trigger>
      <Select.Portal>
        <Select.Content class={styles.content}>
          <Select.Listbox class={styles.listbox} />
        </Select.Content>
      </Select.Portal>
    </Select.Root>
  )
}

function CheckIcon() {
  return (
    <svg width="12" height="12" viewBox="0 0 12 12" fill="currentcolor" style={{ display: "block" }}>
      <path d="M9.854 3.146a.5.5 0 0 1 0 .708l-5 5a.5.5 0 0 1-.708 0l-2-2a.5.5 0 1 1 .708-.708L4.5 7.793l4.646-4.647a.5.5 0 0 1 .708 0Z" />
    </svg>
  )
}

function ChevronDownIcon() {
  return (
    <svg width="15" height="15" viewBox="0 0 15 15" fill="none">
      <path
        d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z"
        fill="currentColor"
        fill-rule="evenodd"
        clip-rule="evenodd"
      />
    </svg>
  )
}
.trigger {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  width: 12rem;
  height: 2.5rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
}

@media (hover: hover) {
  .trigger:hover {
    background-color: var(--color-gray-100);
  }
}

.trigger[data-expanded] {
  background-color: var(--color-gray-100);
}

.trigger:focus-visible {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.trigger[data-disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

.value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.value[data-placeholder-shown] {
  color: var(--color-gray-500);
}

.icon {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  margin-left: 0.5rem;
  color: var(--color-gray-500);
}

.content {
  box-sizing: border-box;
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  background-clip: padding-box;
  outline: 1px solid var(--color-gray-200);
  box-shadow:
    0 10px 15px -3px var(--color-gray-200),
    0 4px 6px -4px var(--color-gray-200);
  z-index: 50;
  animation: contentHide 150ms ease forwards;
}

.content[data-expanded] {
  animation: contentShow 150ms ease;
}

@media (prefers-color-scheme: dark) {
  .content {
    outline: 1px solid var(--color-gray-300);
    box-shadow: none;
  }
}

.listbox {
  margin: 0;
  overflow-y: auto;
  max-height: 20rem;
  padding: 0.25rem;
  outline: 0;
}

.listbox:focus-visible {
  outline: 0;
}

.item {
  box-sizing: border-box;
  display: grid;
  align-items: center;
  grid-template-columns: 0.75rem 1fr;
  gap: 0.5rem;
  padding: 0.5rem 1rem 0.5rem 0.625rem;
  border-radius: 0;
  outline: 0;
  font-size: 0.875rem;
  line-height: 1rem;
  color: var(--color-gray-900);
  cursor: default;
  user-select: none;
  position: relative;
}

.item[data-highlighted] {
  z-index: 0;
  color: var(--color-gray-50);
}

.item[data-highlighted]::before {
  content: "";
  z-index: -1;
  position: absolute;
  inset-block: 0;
  inset-inline: 0.25rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-900);
}

.item[data-disabled] {
  opacity: 0.5;
}

.item-indicator {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  grid-column-start: 1;
  color: currentColor;
}

@keyframes contentShow {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes contentHide {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0;
    transform: scale(0.96);
  }
}

.description {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
  margin-top: 0.25rem;
}

.error {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: #dc2626;
  margin-top: 0.25rem;
}

.status {
  margin: 1rem 0 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
}

.form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.actions {
  display: flex;
  gap: 0.5rem;
}

.button {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-gray-900);
  cursor: pointer;
}

@media (hover: hover) {
  .button:hover {
    background-color: var(--color-gray-100);
  }
}

/* Multiple select trigger — acts as a div container */
.triggerMultiple {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  min-width: 14rem;
  min-height: 2.5rem;
  padding: 0.25rem 0.5rem;
  gap: 0.25rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
  flex-wrap: wrap;
}

.triggerMultiple:focus-within {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.valueMultiple {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  flex: 1;
  min-width: 0;
}

.placeholder {
  color: var(--color-gray-500);
}

.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0 0.375rem;
  height: 1.5rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-100);
  font-size: 0.875rem;
  color: var(--color-gray-900);
}

.tagRemove {
  all: unset;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  color: var(--color-gray-500);
}

.tagRemove:hover {
  color: var(--color-gray-900);
}
:root {
  --color-blue: oklch(45% 50% 264deg);
  --color-red: oklch(50% 55% 31deg);
  --color-gray-50: oklch(98% 0.25% 264deg);
  --color-gray-100: oklch(12% 9.5% 264deg / 5%);
  --color-gray-200: oklch(12% 9% 264deg / 7%);
  --color-gray-300: oklch(12% 8.5% 264deg / 17%);
  --color-gray-400: oklch(12% 8% 264deg / 38%);
  --color-gray-500: oklch(12% 7.5% 264deg / 50%);
  --color-gray-600: oklch(12% 7% 264deg / 67%);
  --color-gray-700: oklch(12% 6% 264deg / 77%);
  --color-gray-800: oklch(12% 5% 264deg / 85%);
  --color-gray-900: oklch(12% 5% 264deg / 90%);
  --color-gray-950: oklch(12% 5% 264deg / 95%);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-blue: oklch(69% 50% 264deg);
    --color-red: oklch(80% 55% 31deg);
    --color-gray-50: oklch(17% 0.25% 264deg);
    --color-gray-100: oklch(28% 0.75% 264deg / 65%);
    --color-gray-200: oklch(29% 0.75% 264deg / 80%);
    --color-gray-300: oklch(35% 0.75% 264deg / 80%);
    --color-gray-400: oklch(47% 0.875% 264deg / 80%);
    --color-gray-500: oklch(64% 1% 264deg / 80%);
    --color-gray-600: oklch(82% 1% 264deg / 80%);
    --color-gray-700: oklch(92% 1.125% 264deg / 80%);
    --color-gray-800: oklch(93% 0.875% 264deg / 85%);
    --color-gray-900: oklch(95% 0.5% 264deg / 90%);
    --color-gray-950: oklch(94% 0.375% 264deg / 95%);
  }
}

Examples

Default value

Use defaultValue to set the initial selection without controlling the state.

View source
import * as Select from "@danielfrg/solid-ui/select"
import styles from "./index.module.css"

const FRUITS = ["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]

export function DemoSelectDefaultValue() {
  return (
    <Select.Root
      options={FRUITS}
      defaultValue="Blueberry"
      itemComponent={(props) => (
        <Select.Item item={props.item} class={styles.item}>
          <Select.ItemIndicator class={styles["item-indicator"]}>
            <CheckIcon />
          </Select.ItemIndicator>
          <Select.ItemLabel>{props.item.rawValue}</Select.ItemLabel>
        </Select.Item>
      )}
    >
      <Select.Trigger class={styles.trigger} aria-label="Fruit">
        <Select.Value<string> class={styles.value}>{(state) => state.selectedOption()}</Select.Value>
        <Select.Icon class={styles.icon}>
          <ChevronDownIcon />
        </Select.Icon>
      </Select.Trigger>
      <Select.Portal>
        <Select.Content class={styles.content}>
          <Select.Listbox class={styles.listbox} />
        </Select.Content>
      </Select.Portal>
    </Select.Root>
  )
}

function CheckIcon() {
  return (
    <svg width="12" height="12" viewBox="0 0 12 12" fill="currentcolor" style={{ display: "block" }}>
      <path d="M9.854 3.146a.5.5 0 0 1 0 .708l-5 5a.5.5 0 0 1-.708 0l-2-2a.5.5 0 1 1 .708-.708L4.5 7.793l4.646-4.647a.5.5 0 0 1 .708 0Z" />
    </svg>
  )
}

function ChevronDownIcon() {
  return (
    <svg width="15" height="15" viewBox="0 0 15 15" fill="none">
      <path
        d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z"
        fill="currentColor"
        fill-rule="evenodd"
        clip-rule="evenodd"
      />
    </svg>
  )
}
.trigger {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  width: 12rem;
  height: 2.5rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
}

@media (hover: hover) {
  .trigger:hover {
    background-color: var(--color-gray-100);
  }
}

.trigger[data-expanded] {
  background-color: var(--color-gray-100);
}

.trigger:focus-visible {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.trigger[data-disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

.value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.value[data-placeholder-shown] {
  color: var(--color-gray-500);
}

.icon {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  margin-left: 0.5rem;
  color: var(--color-gray-500);
}

.content {
  box-sizing: border-box;
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  background-clip: padding-box;
  outline: 1px solid var(--color-gray-200);
  box-shadow:
    0 10px 15px -3px var(--color-gray-200),
    0 4px 6px -4px var(--color-gray-200);
  z-index: 50;
  animation: contentHide 150ms ease forwards;
}

.content[data-expanded] {
  animation: contentShow 150ms ease;
}

@media (prefers-color-scheme: dark) {
  .content {
    outline: 1px solid var(--color-gray-300);
    box-shadow: none;
  }
}

.listbox {
  margin: 0;
  overflow-y: auto;
  max-height: 20rem;
  padding: 0.25rem;
  outline: 0;
}

.listbox:focus-visible {
  outline: 0;
}

.item {
  box-sizing: border-box;
  display: grid;
  align-items: center;
  grid-template-columns: 0.75rem 1fr;
  gap: 0.5rem;
  padding: 0.5rem 1rem 0.5rem 0.625rem;
  border-radius: 0;
  outline: 0;
  font-size: 0.875rem;
  line-height: 1rem;
  color: var(--color-gray-900);
  cursor: default;
  user-select: none;
  position: relative;
}

.item[data-highlighted] {
  z-index: 0;
  color: var(--color-gray-50);
}

.item[data-highlighted]::before {
  content: "";
  z-index: -1;
  position: absolute;
  inset-block: 0;
  inset-inline: 0.25rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-900);
}

.item[data-disabled] {
  opacity: 0.5;
}

.item-indicator {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  grid-column-start: 1;
  color: currentColor;
}

@keyframes contentShow {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes contentHide {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0;
    transform: scale(0.96);
  }
}

.description {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
  margin-top: 0.25rem;
}

.error {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: #dc2626;
  margin-top: 0.25rem;
}

.status {
  margin: 1rem 0 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
}

.form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.actions {
  display: flex;
  gap: 0.5rem;
}

.button {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-gray-900);
  cursor: pointer;
}

@media (hover: hover) {
  .button:hover {
    background-color: var(--color-gray-100);
  }
}

/* Multiple select trigger — acts as a div container */
.triggerMultiple {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  min-width: 14rem;
  min-height: 2.5rem;
  padding: 0.25rem 0.5rem;
  gap: 0.25rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
  flex-wrap: wrap;
}

.triggerMultiple:focus-within {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.valueMultiple {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  flex: 1;
  min-width: 0;
}

.placeholder {
  color: var(--color-gray-500);
}

.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0 0.375rem;
  height: 1.5rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-100);
  font-size: 0.875rem;
  color: var(--color-gray-900);
}

.tagRemove {
  all: unset;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  color: var(--color-gray-500);
}

.tagRemove:hover {
  color: var(--color-gray-900);
}
:root {
  --color-blue: oklch(45% 50% 264deg);
  --color-red: oklch(50% 55% 31deg);
  --color-gray-50: oklch(98% 0.25% 264deg);
  --color-gray-100: oklch(12% 9.5% 264deg / 5%);
  --color-gray-200: oklch(12% 9% 264deg / 7%);
  --color-gray-300: oklch(12% 8.5% 264deg / 17%);
  --color-gray-400: oklch(12% 8% 264deg / 38%);
  --color-gray-500: oklch(12% 7.5% 264deg / 50%);
  --color-gray-600: oklch(12% 7% 264deg / 67%);
  --color-gray-700: oklch(12% 6% 264deg / 77%);
  --color-gray-800: oklch(12% 5% 264deg / 85%);
  --color-gray-900: oklch(12% 5% 264deg / 90%);
  --color-gray-950: oklch(12% 5% 264deg / 95%);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-blue: oklch(69% 50% 264deg);
    --color-red: oklch(80% 55% 31deg);
    --color-gray-50: oklch(17% 0.25% 264deg);
    --color-gray-100: oklch(28% 0.75% 264deg / 65%);
    --color-gray-200: oklch(29% 0.75% 264deg / 80%);
    --color-gray-300: oklch(35% 0.75% 264deg / 80%);
    --color-gray-400: oklch(47% 0.875% 264deg / 80%);
    --color-gray-500: oklch(64% 1% 264deg / 80%);
    --color-gray-600: oklch(82% 1% 264deg / 80%);
    --color-gray-700: oklch(92% 1.125% 264deg / 80%);
    --color-gray-800: oklch(93% 0.875% 264deg / 85%);
    --color-gray-900: oklch(95% 0.5% 264deg / 90%);
    --color-gray-950: oklch(94% 0.375% 264deg / 95%);
  }
}

Controlled

Use value and onChange to control the selection externally.

Selected: Blueberry

View source
import { createSignal } from "solid-js"
import * as Select from "@danielfrg/solid-ui/select"
import styles from "./index.module.css"

const FRUITS = ["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]

export function DemoSelectControlled() {
  const [value, setValue] = createSignal("Blueberry")

  return (
    <div>
      <Select.Root
        options={FRUITS}
        value={value()}
        onChange={setValue}
        itemComponent={(props) => (
          <Select.Item item={props.item} class={styles.item}>
            <Select.ItemIndicator class={styles["item-indicator"]}>
              <CheckIcon />
            </Select.ItemIndicator>
            <Select.ItemLabel>{props.item.rawValue}</Select.ItemLabel>
          </Select.Item>
        )}
      >
        <Select.Trigger class={styles.trigger} aria-label="Fruit">
          <Select.Value<string> class={styles.value}>{(state) => state.selectedOption()}</Select.Value>
          <Select.Icon class={styles.icon}>
            <ChevronDownIcon />
          </Select.Icon>
        </Select.Trigger>
        <Select.Portal>
          <Select.Content class={styles.content}>
            <Select.Listbox class={styles.listbox} />
          </Select.Content>
        </Select.Portal>
      </Select.Root>
      <p class={styles.status}>Selected: {value()}</p>
    </div>
  )
}

function CheckIcon() {
  return (
    <svg width="12" height="12" viewBox="0 0 12 12" fill="currentcolor" style={{ display: "block" }}>
      <path d="M9.854 3.146a.5.5 0 0 1 0 .708l-5 5a.5.5 0 0 1-.708 0l-2-2a.5.5 0 1 1 .708-.708L4.5 7.793l4.646-4.647a.5.5 0 0 1 .708 0Z" />
    </svg>
  )
}

function ChevronDownIcon() {
  return (
    <svg width="15" height="15" viewBox="0 0 15 15" fill="none">
      <path
        d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z"
        fill="currentColor"
        fill-rule="evenodd"
        clip-rule="evenodd"
      />
    </svg>
  )
}
.trigger {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  width: 12rem;
  height: 2.5rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
}

@media (hover: hover) {
  .trigger:hover {
    background-color: var(--color-gray-100);
  }
}

.trigger[data-expanded] {
  background-color: var(--color-gray-100);
}

.trigger:focus-visible {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.trigger[data-disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

.value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.value[data-placeholder-shown] {
  color: var(--color-gray-500);
}

.icon {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  margin-left: 0.5rem;
  color: var(--color-gray-500);
}

.content {
  box-sizing: border-box;
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  background-clip: padding-box;
  outline: 1px solid var(--color-gray-200);
  box-shadow:
    0 10px 15px -3px var(--color-gray-200),
    0 4px 6px -4px var(--color-gray-200);
  z-index: 50;
  animation: contentHide 150ms ease forwards;
}

.content[data-expanded] {
  animation: contentShow 150ms ease;
}

@media (prefers-color-scheme: dark) {
  .content {
    outline: 1px solid var(--color-gray-300);
    box-shadow: none;
  }
}

.listbox {
  margin: 0;
  overflow-y: auto;
  max-height: 20rem;
  padding: 0.25rem;
  outline: 0;
}

.listbox:focus-visible {
  outline: 0;
}

.item {
  box-sizing: border-box;
  display: grid;
  align-items: center;
  grid-template-columns: 0.75rem 1fr;
  gap: 0.5rem;
  padding: 0.5rem 1rem 0.5rem 0.625rem;
  border-radius: 0;
  outline: 0;
  font-size: 0.875rem;
  line-height: 1rem;
  color: var(--color-gray-900);
  cursor: default;
  user-select: none;
  position: relative;
}

.item[data-highlighted] {
  z-index: 0;
  color: var(--color-gray-50);
}

.item[data-highlighted]::before {
  content: "";
  z-index: -1;
  position: absolute;
  inset-block: 0;
  inset-inline: 0.25rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-900);
}

.item[data-disabled] {
  opacity: 0.5;
}

.item-indicator {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  grid-column-start: 1;
  color: currentColor;
}

@keyframes contentShow {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes contentHide {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0;
    transform: scale(0.96);
  }
}

.description {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
  margin-top: 0.25rem;
}

.error {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: #dc2626;
  margin-top: 0.25rem;
}

.status {
  margin: 1rem 0 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
}

.form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.actions {
  display: flex;
  gap: 0.5rem;
}

.button {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-gray-900);
  cursor: pointer;
}

@media (hover: hover) {
  .button:hover {
    background-color: var(--color-gray-100);
  }
}

/* Multiple select trigger — acts as a div container */
.triggerMultiple {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  min-width: 14rem;
  min-height: 2.5rem;
  padding: 0.25rem 0.5rem;
  gap: 0.25rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
  flex-wrap: wrap;
}

.triggerMultiple:focus-within {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.valueMultiple {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  flex: 1;
  min-width: 0;
}

.placeholder {
  color: var(--color-gray-500);
}

.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0 0.375rem;
  height: 1.5rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-100);
  font-size: 0.875rem;
  color: var(--color-gray-900);
}

.tagRemove {
  all: unset;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  color: var(--color-gray-500);
}

.tagRemove:hover {
  color: var(--color-gray-900);
}
:root {
  --color-blue: oklch(45% 50% 264deg);
  --color-red: oklch(50% 55% 31deg);
  --color-gray-50: oklch(98% 0.25% 264deg);
  --color-gray-100: oklch(12% 9.5% 264deg / 5%);
  --color-gray-200: oklch(12% 9% 264deg / 7%);
  --color-gray-300: oklch(12% 8.5% 264deg / 17%);
  --color-gray-400: oklch(12% 8% 264deg / 38%);
  --color-gray-500: oklch(12% 7.5% 264deg / 50%);
  --color-gray-600: oklch(12% 7% 264deg / 67%);
  --color-gray-700: oklch(12% 6% 264deg / 77%);
  --color-gray-800: oklch(12% 5% 264deg / 85%);
  --color-gray-900: oklch(12% 5% 264deg / 90%);
  --color-gray-950: oklch(12% 5% 264deg / 95%);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-blue: oklch(69% 50% 264deg);
    --color-red: oklch(80% 55% 31deg);
    --color-gray-50: oklch(17% 0.25% 264deg);
    --color-gray-100: oklch(28% 0.75% 264deg / 65%);
    --color-gray-200: oklch(29% 0.75% 264deg / 80%);
    --color-gray-300: oklch(35% 0.75% 264deg / 80%);
    --color-gray-400: oklch(47% 0.875% 264deg / 80%);
    --color-gray-500: oklch(64% 1% 264deg / 80%);
    --color-gray-600: oklch(82% 1% 264deg / 80%);
    --color-gray-700: oklch(92% 1.125% 264deg / 80%);
    --color-gray-800: oklch(93% 0.875% 264deg / 85%);
    --color-gray-900: oklch(95% 0.5% 264deg / 90%);
    --color-gray-950: oklch(94% 0.375% 264deg / 95%);
  }
}

Description

Use Select.Description to add a hint below the trigger.

Choose your favorite fruit from the list.
View source
import * as Select from "@danielfrg/solid-ui/select"
import styles from "./index.module.css"

const FRUITS = ["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]

export function DemoSelectDescription() {
  return (
    <div>
      <Select.Root
        options={FRUITS}
        placeholder="Select a fruit..."
        itemComponent={(props) => (
          <Select.Item item={props.item} class={styles.item}>
            <Select.ItemIndicator class={styles["item-indicator"]}>
              <CheckIcon />
            </Select.ItemIndicator>
            <Select.ItemLabel>{props.item.rawValue}</Select.ItemLabel>
          </Select.Item>
        )}
      >
        <Select.Trigger class={styles.trigger} aria-label="Fruit">
          <Select.Value<string> class={styles.value}>{(state) => state.selectedOption()}</Select.Value>
          <Select.Icon class={styles.icon}>
            <ChevronDownIcon />
          </Select.Icon>
        </Select.Trigger>
        <Select.Description class={styles.description}>Choose your favorite fruit from the list.</Select.Description>
        <Select.Portal>
          <Select.Content class={styles.content}>
            <Select.Listbox class={styles.listbox} />
          </Select.Content>
        </Select.Portal>
      </Select.Root>
    </div>
  )
}

function CheckIcon() {
  return (
    <svg width="12" height="12" viewBox="0 0 12 12" fill="currentcolor" style={{ display: "block" }}>
      <path d="M9.854 3.146a.5.5 0 0 1 0 .708l-5 5a.5.5 0 0 1-.708 0l-2-2a.5.5 0 1 1 .708-.708L4.5 7.793l4.646-4.647a.5.5 0 0 1 .708 0Z" />
    </svg>
  )
}

function ChevronDownIcon() {
  return (
    <svg width="15" height="15" viewBox="0 0 15 15" fill="none">
      <path
        d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z"
        fill="currentColor"
        fill-rule="evenodd"
        clip-rule="evenodd"
      />
    </svg>
  )
}
.trigger {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  width: 12rem;
  height: 2.5rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
}

@media (hover: hover) {
  .trigger:hover {
    background-color: var(--color-gray-100);
  }
}

.trigger[data-expanded] {
  background-color: var(--color-gray-100);
}

.trigger:focus-visible {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.trigger[data-disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

.value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.value[data-placeholder-shown] {
  color: var(--color-gray-500);
}

.icon {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  margin-left: 0.5rem;
  color: var(--color-gray-500);
}

.content {
  box-sizing: border-box;
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  background-clip: padding-box;
  outline: 1px solid var(--color-gray-200);
  box-shadow:
    0 10px 15px -3px var(--color-gray-200),
    0 4px 6px -4px var(--color-gray-200);
  z-index: 50;
  animation: contentHide 150ms ease forwards;
}

.content[data-expanded] {
  animation: contentShow 150ms ease;
}

@media (prefers-color-scheme: dark) {
  .content {
    outline: 1px solid var(--color-gray-300);
    box-shadow: none;
  }
}

.listbox {
  margin: 0;
  overflow-y: auto;
  max-height: 20rem;
  padding: 0.25rem;
  outline: 0;
}

.listbox:focus-visible {
  outline: 0;
}

.item {
  box-sizing: border-box;
  display: grid;
  align-items: center;
  grid-template-columns: 0.75rem 1fr;
  gap: 0.5rem;
  padding: 0.5rem 1rem 0.5rem 0.625rem;
  border-radius: 0;
  outline: 0;
  font-size: 0.875rem;
  line-height: 1rem;
  color: var(--color-gray-900);
  cursor: default;
  user-select: none;
  position: relative;
}

.item[data-highlighted] {
  z-index: 0;
  color: var(--color-gray-50);
}

.item[data-highlighted]::before {
  content: "";
  z-index: -1;
  position: absolute;
  inset-block: 0;
  inset-inline: 0.25rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-900);
}

.item[data-disabled] {
  opacity: 0.5;
}

.item-indicator {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  grid-column-start: 1;
  color: currentColor;
}

@keyframes contentShow {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes contentHide {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0;
    transform: scale(0.96);
  }
}

.description {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
  margin-top: 0.25rem;
}

.error {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: #dc2626;
  margin-top: 0.25rem;
}

.status {
  margin: 1rem 0 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
}

.form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.actions {
  display: flex;
  gap: 0.5rem;
}

.button {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-gray-900);
  cursor: pointer;
}

@media (hover: hover) {
  .button:hover {
    background-color: var(--color-gray-100);
  }
}

/* Multiple select trigger — acts as a div container */
.triggerMultiple {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  min-width: 14rem;
  min-height: 2.5rem;
  padding: 0.25rem 0.5rem;
  gap: 0.25rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
  flex-wrap: wrap;
}

.triggerMultiple:focus-within {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.valueMultiple {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  flex: 1;
  min-width: 0;
}

.placeholder {
  color: var(--color-gray-500);
}

.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0 0.375rem;
  height: 1.5rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-100);
  font-size: 0.875rem;
  color: var(--color-gray-900);
}

.tagRemove {
  all: unset;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  color: var(--color-gray-500);
}

.tagRemove:hover {
  color: var(--color-gray-900);
}
:root {
  --color-blue: oklch(45% 50% 264deg);
  --color-red: oklch(50% 55% 31deg);
  --color-gray-50: oklch(98% 0.25% 264deg);
  --color-gray-100: oklch(12% 9.5% 264deg / 5%);
  --color-gray-200: oklch(12% 9% 264deg / 7%);
  --color-gray-300: oklch(12% 8.5% 264deg / 17%);
  --color-gray-400: oklch(12% 8% 264deg / 38%);
  --color-gray-500: oklch(12% 7.5% 264deg / 50%);
  --color-gray-600: oklch(12% 7% 264deg / 67%);
  --color-gray-700: oklch(12% 6% 264deg / 77%);
  --color-gray-800: oklch(12% 5% 264deg / 85%);
  --color-gray-900: oklch(12% 5% 264deg / 90%);
  --color-gray-950: oklch(12% 5% 264deg / 95%);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-blue: oklch(69% 50% 264deg);
    --color-red: oklch(80% 55% 31deg);
    --color-gray-50: oklch(17% 0.25% 264deg);
    --color-gray-100: oklch(28% 0.75% 264deg / 65%);
    --color-gray-200: oklch(29% 0.75% 264deg / 80%);
    --color-gray-300: oklch(35% 0.75% 264deg / 80%);
    --color-gray-400: oklch(47% 0.875% 264deg / 80%);
    --color-gray-500: oklch(64% 1% 264deg / 80%);
    --color-gray-600: oklch(82% 1% 264deg / 80%);
    --color-gray-700: oklch(92% 1.125% 264deg / 80%);
    --color-gray-800: oklch(93% 0.875% 264deg / 85%);
    --color-gray-900: oklch(95% 0.5% 264deg / 90%);
    --color-gray-950: oklch(94% 0.375% 264deg / 95%);
  }
}

Error message

Use validationState and Select.ErrorMessage to show validation feedback.

Please select a fruit.
View source
import { createSignal } from "solid-js"
import * as Select from "@danielfrg/solid-ui/select"
import styles from "./index.module.css"

const FRUITS = ["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]

export function DemoSelectErrorMessage() {
  const [value, setValue] = createSignal<string | undefined>(undefined)

  return (
    <div>
      <Select.Root
        options={FRUITS}
        value={value()}
        onChange={setValue}
        validationState={!value() ? "invalid" : "valid"}
        placeholder="Select a fruit..."
        itemComponent={(props) => (
          <Select.Item item={props.item} class={styles.item}>
            <Select.ItemIndicator class={styles["item-indicator"]}>
              <CheckIcon />
            </Select.ItemIndicator>
            <Select.ItemLabel>{props.item.rawValue}</Select.ItemLabel>
          </Select.Item>
        )}
      >
        <Select.Trigger class={styles.trigger} aria-label="Fruit">
          <Select.Value<string> class={styles.value}>{(state) => state.selectedOption()}</Select.Value>
          <Select.Icon class={styles.icon}>
            <ChevronDownIcon />
          </Select.Icon>
        </Select.Trigger>
        <Select.ErrorMessage class={styles.error}>Please select a fruit.</Select.ErrorMessage>
        <Select.Portal>
          <Select.Content class={styles.content}>
            <Select.Listbox class={styles.listbox} />
          </Select.Content>
        </Select.Portal>
      </Select.Root>
    </div>
  )
}

function CheckIcon() {
  return (
    <svg width="12" height="12" viewBox="0 0 12 12" fill="currentcolor" style={{ display: "block" }}>
      <path d="M9.854 3.146a.5.5 0 0 1 0 .708l-5 5a.5.5 0 0 1-.708 0l-2-2a.5.5 0 1 1 .708-.708L4.5 7.793l4.646-4.647a.5.5 0 0 1 .708 0Z" />
    </svg>
  )
}

function ChevronDownIcon() {
  return (
    <svg width="15" height="15" viewBox="0 0 15 15" fill="none">
      <path
        d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z"
        fill="currentColor"
        fill-rule="evenodd"
        clip-rule="evenodd"
      />
    </svg>
  )
}
.trigger {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  width: 12rem;
  height: 2.5rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
}

@media (hover: hover) {
  .trigger:hover {
    background-color: var(--color-gray-100);
  }
}

.trigger[data-expanded] {
  background-color: var(--color-gray-100);
}

.trigger:focus-visible {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.trigger[data-disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

.value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.value[data-placeholder-shown] {
  color: var(--color-gray-500);
}

.icon {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  margin-left: 0.5rem;
  color: var(--color-gray-500);
}

.content {
  box-sizing: border-box;
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  background-clip: padding-box;
  outline: 1px solid var(--color-gray-200);
  box-shadow:
    0 10px 15px -3px var(--color-gray-200),
    0 4px 6px -4px var(--color-gray-200);
  z-index: 50;
  animation: contentHide 150ms ease forwards;
}

.content[data-expanded] {
  animation: contentShow 150ms ease;
}

@media (prefers-color-scheme: dark) {
  .content {
    outline: 1px solid var(--color-gray-300);
    box-shadow: none;
  }
}

.listbox {
  margin: 0;
  overflow-y: auto;
  max-height: 20rem;
  padding: 0.25rem;
  outline: 0;
}

.listbox:focus-visible {
  outline: 0;
}

.item {
  box-sizing: border-box;
  display: grid;
  align-items: center;
  grid-template-columns: 0.75rem 1fr;
  gap: 0.5rem;
  padding: 0.5rem 1rem 0.5rem 0.625rem;
  border-radius: 0;
  outline: 0;
  font-size: 0.875rem;
  line-height: 1rem;
  color: var(--color-gray-900);
  cursor: default;
  user-select: none;
  position: relative;
}

.item[data-highlighted] {
  z-index: 0;
  color: var(--color-gray-50);
}

.item[data-highlighted]::before {
  content: "";
  z-index: -1;
  position: absolute;
  inset-block: 0;
  inset-inline: 0.25rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-900);
}

.item[data-disabled] {
  opacity: 0.5;
}

.item-indicator {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  grid-column-start: 1;
  color: currentColor;
}

@keyframes contentShow {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes contentHide {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0;
    transform: scale(0.96);
  }
}

.description {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
  margin-top: 0.25rem;
}

.error {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: #dc2626;
  margin-top: 0.25rem;
}

.status {
  margin: 1rem 0 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
}

.form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.actions {
  display: flex;
  gap: 0.5rem;
}

.button {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-gray-900);
  cursor: pointer;
}

@media (hover: hover) {
  .button:hover {
    background-color: var(--color-gray-100);
  }
}

/* Multiple select trigger — acts as a div container */
.triggerMultiple {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  min-width: 14rem;
  min-height: 2.5rem;
  padding: 0.25rem 0.5rem;
  gap: 0.25rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
  flex-wrap: wrap;
}

.triggerMultiple:focus-within {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.valueMultiple {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  flex: 1;
  min-width: 0;
}

.placeholder {
  color: var(--color-gray-500);
}

.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0 0.375rem;
  height: 1.5rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-100);
  font-size: 0.875rem;
  color: var(--color-gray-900);
}

.tagRemove {
  all: unset;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  color: var(--color-gray-500);
}

.tagRemove:hover {
  color: var(--color-gray-900);
}
:root {
  --color-blue: oklch(45% 50% 264deg);
  --color-red: oklch(50% 55% 31deg);
  --color-gray-50: oklch(98% 0.25% 264deg);
  --color-gray-100: oklch(12% 9.5% 264deg / 5%);
  --color-gray-200: oklch(12% 9% 264deg / 7%);
  --color-gray-300: oklch(12% 8.5% 264deg / 17%);
  --color-gray-400: oklch(12% 8% 264deg / 38%);
  --color-gray-500: oklch(12% 7.5% 264deg / 50%);
  --color-gray-600: oklch(12% 7% 264deg / 67%);
  --color-gray-700: oklch(12% 6% 264deg / 77%);
  --color-gray-800: oklch(12% 5% 264deg / 85%);
  --color-gray-900: oklch(12% 5% 264deg / 90%);
  --color-gray-950: oklch(12% 5% 264deg / 95%);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-blue: oklch(69% 50% 264deg);
    --color-red: oklch(80% 55% 31deg);
    --color-gray-50: oklch(17% 0.25% 264deg);
    --color-gray-100: oklch(28% 0.75% 264deg / 65%);
    --color-gray-200: oklch(29% 0.75% 264deg / 80%);
    --color-gray-300: oklch(35% 0.75% 264deg / 80%);
    --color-gray-400: oklch(47% 0.875% 264deg / 80%);
    --color-gray-500: oklch(64% 1% 264deg / 80%);
    --color-gray-600: oklch(82% 1% 264deg / 80%);
    --color-gray-700: oklch(92% 1.125% 264deg / 80%);
    --color-gray-800: oklch(93% 0.875% 264deg / 85%);
    --color-gray-900: oklch(95% 0.5% 264deg / 90%);
    --color-gray-950: oklch(94% 0.375% 264deg / 95%);
  }
}

HTML form

Use the name prop with Select.HiddenSelect to include the value in a native form submission.

View source
import * as Select from "@danielfrg/solid-ui/select"
import styles from "./index.module.css"

const FRUITS = ["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]

export function DemoSelectHtmlForm() {
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault()
        const data = new FormData(e.currentTarget)
        window.alert(JSON.stringify(Object.fromEntries(data), null, 2))
      }}
      class={styles.form}
    >
      <Select.Root
        options={FRUITS}
        name="fruit"
        placeholder="Select a fruit..."
        itemComponent={(props) => (
          <Select.Item item={props.item} class={styles.item}>
            <Select.ItemIndicator class={styles["item-indicator"]}>
              <CheckIcon />
            </Select.ItemIndicator>
            <Select.ItemLabel>{props.item.rawValue}</Select.ItemLabel>
          </Select.Item>
        )}
      >
        <Select.HiddenSelect />
        <Select.Trigger class={styles.trigger} aria-label="Fruit">
          <Select.Value<string> class={styles.value}>{(state) => state.selectedOption()}</Select.Value>
          <Select.Icon class={styles.icon}>
            <ChevronDownIcon />
          </Select.Icon>
        </Select.Trigger>
        <Select.Portal>
          <Select.Content class={styles.content}>
            <Select.Listbox class={styles.listbox} />
          </Select.Content>
        </Select.Portal>
      </Select.Root>
      <div class={styles.actions}>
        <button type="reset" class={styles.button}>
          Reset
        </button>
        <button type="submit" class={styles.button}>
          Submit
        </button>
      </div>
    </form>
  )
}

function CheckIcon() {
  return (
    <svg width="12" height="12" viewBox="0 0 12 12" fill="currentcolor" style={{ display: "block" }}>
      <path d="M9.854 3.146a.5.5 0 0 1 0 .708l-5 5a.5.5 0 0 1-.708 0l-2-2a.5.5 0 1 1 .708-.708L4.5 7.793l4.646-4.647a.5.5 0 0 1 .708 0Z" />
    </svg>
  )
}

function ChevronDownIcon() {
  return (
    <svg width="15" height="15" viewBox="0 0 15 15" fill="none">
      <path
        d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z"
        fill="currentColor"
        fill-rule="evenodd"
        clip-rule="evenodd"
      />
    </svg>
  )
}
.trigger {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  width: 12rem;
  height: 2.5rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
}

@media (hover: hover) {
  .trigger:hover {
    background-color: var(--color-gray-100);
  }
}

.trigger[data-expanded] {
  background-color: var(--color-gray-100);
}

.trigger:focus-visible {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.trigger[data-disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

.value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.value[data-placeholder-shown] {
  color: var(--color-gray-500);
}

.icon {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  margin-left: 0.5rem;
  color: var(--color-gray-500);
}

.content {
  box-sizing: border-box;
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  background-clip: padding-box;
  outline: 1px solid var(--color-gray-200);
  box-shadow:
    0 10px 15px -3px var(--color-gray-200),
    0 4px 6px -4px var(--color-gray-200);
  z-index: 50;
  animation: contentHide 150ms ease forwards;
}

.content[data-expanded] {
  animation: contentShow 150ms ease;
}

@media (prefers-color-scheme: dark) {
  .content {
    outline: 1px solid var(--color-gray-300);
    box-shadow: none;
  }
}

.listbox {
  margin: 0;
  overflow-y: auto;
  max-height: 20rem;
  padding: 0.25rem;
  outline: 0;
}

.listbox:focus-visible {
  outline: 0;
}

.item {
  box-sizing: border-box;
  display: grid;
  align-items: center;
  grid-template-columns: 0.75rem 1fr;
  gap: 0.5rem;
  padding: 0.5rem 1rem 0.5rem 0.625rem;
  border-radius: 0;
  outline: 0;
  font-size: 0.875rem;
  line-height: 1rem;
  color: var(--color-gray-900);
  cursor: default;
  user-select: none;
  position: relative;
}

.item[data-highlighted] {
  z-index: 0;
  color: var(--color-gray-50);
}

.item[data-highlighted]::before {
  content: "";
  z-index: -1;
  position: absolute;
  inset-block: 0;
  inset-inline: 0.25rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-900);
}

.item[data-disabled] {
  opacity: 0.5;
}

.item-indicator {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  grid-column-start: 1;
  color: currentColor;
}

@keyframes contentShow {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes contentHide {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0;
    transform: scale(0.96);
  }
}

.description {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
  margin-top: 0.25rem;
}

.error {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: #dc2626;
  margin-top: 0.25rem;
}

.status {
  margin: 1rem 0 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
}

.form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.actions {
  display: flex;
  gap: 0.5rem;
}

.button {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-gray-900);
  cursor: pointer;
}

@media (hover: hover) {
  .button:hover {
    background-color: var(--color-gray-100);
  }
}

/* Multiple select trigger — acts as a div container */
.triggerMultiple {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  min-width: 14rem;
  min-height: 2.5rem;
  padding: 0.25rem 0.5rem;
  gap: 0.25rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
  flex-wrap: wrap;
}

.triggerMultiple:focus-within {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.valueMultiple {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  flex: 1;
  min-width: 0;
}

.placeholder {
  color: var(--color-gray-500);
}

.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0 0.375rem;
  height: 1.5rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-100);
  font-size: 0.875rem;
  color: var(--color-gray-900);
}

.tagRemove {
  all: unset;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  color: var(--color-gray-500);
}

.tagRemove:hover {
  color: var(--color-gray-900);
}
:root {
  --color-blue: oklch(45% 50% 264deg);
  --color-red: oklch(50% 55% 31deg);
  --color-gray-50: oklch(98% 0.25% 264deg);
  --color-gray-100: oklch(12% 9.5% 264deg / 5%);
  --color-gray-200: oklch(12% 9% 264deg / 7%);
  --color-gray-300: oklch(12% 8.5% 264deg / 17%);
  --color-gray-400: oklch(12% 8% 264deg / 38%);
  --color-gray-500: oklch(12% 7.5% 264deg / 50%);
  --color-gray-600: oklch(12% 7% 264deg / 67%);
  --color-gray-700: oklch(12% 6% 264deg / 77%);
  --color-gray-800: oklch(12% 5% 264deg / 85%);
  --color-gray-900: oklch(12% 5% 264deg / 90%);
  --color-gray-950: oklch(12% 5% 264deg / 95%);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-blue: oklch(69% 50% 264deg);
    --color-red: oklch(80% 55% 31deg);
    --color-gray-50: oklch(17% 0.25% 264deg);
    --color-gray-100: oklch(28% 0.75% 264deg / 65%);
    --color-gray-200: oklch(29% 0.75% 264deg / 80%);
    --color-gray-300: oklch(35% 0.75% 264deg / 80%);
    --color-gray-400: oklch(47% 0.875% 264deg / 80%);
    --color-gray-500: oklch(64% 1% 264deg / 80%);
    --color-gray-600: oklch(82% 1% 264deg / 80%);
    --color-gray-700: oklch(92% 1.125% 264deg / 80%);
    --color-gray-800: oklch(93% 0.875% 264deg / 85%);
    --color-gray-900: oklch(95% 0.5% 264deg / 90%);
    --color-gray-950: oklch(94% 0.375% 264deg / 95%);
  }
}

Object options

Use optionValue, optionTextValue, and optionDisabled to work with object arrays.

View source
import * as Select from "@danielfrg/solid-ui/select"
import styles from "./index.module.css"

interface Food {
  value: string
  label: string
  disabled: boolean
}

const FOODS: Food[] = [
  { value: "apple", label: "Apple", disabled: false },
  { value: "banana", label: "Banana", disabled: false },
  { value: "blueberry", label: "Blueberry", disabled: false },
  { value: "grapes", label: "Grapes", disabled: true },
  { value: "pineapple", label: "Pineapple", disabled: false },
]

export function DemoSelectObject() {
  return (
    <Select.Root<Food>
      options={FOODS}
      optionValue="value"
      optionTextValue="label"
      optionDisabled="disabled"
      placeholder="Select a fruit..."
      itemComponent={(props) => (
        <Select.Item item={props.item} class={styles.item}>
          <Select.ItemIndicator class={styles["item-indicator"]}>
            <CheckIcon />
          </Select.ItemIndicator>
          <Select.ItemLabel>{props.item.rawValue.label}</Select.ItemLabel>
        </Select.Item>
      )}
    >
      <Select.Trigger class={styles.trigger} aria-label="Fruit">
        <Select.Value<Food> class={styles.value}>{(state) => state.selectedOption().label}</Select.Value>
        <Select.Icon class={styles.icon}>
          <ChevronDownIcon />
        </Select.Icon>
      </Select.Trigger>
      <Select.Portal>
        <Select.Content class={styles.content}>
          <Select.Listbox class={styles.listbox} />
        </Select.Content>
      </Select.Portal>
    </Select.Root>
  )
}

function CheckIcon() {
  return (
    <svg width="12" height="12" viewBox="0 0 12 12" fill="currentcolor" style={{ display: "block" }}>
      <path d="M9.854 3.146a.5.5 0 0 1 0 .708l-5 5a.5.5 0 0 1-.708 0l-2-2a.5.5 0 1 1 .708-.708L4.5 7.793l4.646-4.647a.5.5 0 0 1 .708 0Z" />
    </svg>
  )
}

function ChevronDownIcon() {
  return (
    <svg width="15" height="15" viewBox="0 0 15 15" fill="none">
      <path
        d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z"
        fill="currentColor"
        fill-rule="evenodd"
        clip-rule="evenodd"
      />
    </svg>
  )
}
.trigger {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  width: 12rem;
  height: 2.5rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
}

@media (hover: hover) {
  .trigger:hover {
    background-color: var(--color-gray-100);
  }
}

.trigger[data-expanded] {
  background-color: var(--color-gray-100);
}

.trigger:focus-visible {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.trigger[data-disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

.value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.value[data-placeholder-shown] {
  color: var(--color-gray-500);
}

.icon {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  margin-left: 0.5rem;
  color: var(--color-gray-500);
}

.content {
  box-sizing: border-box;
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  background-clip: padding-box;
  outline: 1px solid var(--color-gray-200);
  box-shadow:
    0 10px 15px -3px var(--color-gray-200),
    0 4px 6px -4px var(--color-gray-200);
  z-index: 50;
  animation: contentHide 150ms ease forwards;
}

.content[data-expanded] {
  animation: contentShow 150ms ease;
}

@media (prefers-color-scheme: dark) {
  .content {
    outline: 1px solid var(--color-gray-300);
    box-shadow: none;
  }
}

.listbox {
  margin: 0;
  overflow-y: auto;
  max-height: 20rem;
  padding: 0.25rem;
  outline: 0;
}

.listbox:focus-visible {
  outline: 0;
}

.item {
  box-sizing: border-box;
  display: grid;
  align-items: center;
  grid-template-columns: 0.75rem 1fr;
  gap: 0.5rem;
  padding: 0.5rem 1rem 0.5rem 0.625rem;
  border-radius: 0;
  outline: 0;
  font-size: 0.875rem;
  line-height: 1rem;
  color: var(--color-gray-900);
  cursor: default;
  user-select: none;
  position: relative;
}

.item[data-highlighted] {
  z-index: 0;
  color: var(--color-gray-50);
}

.item[data-highlighted]::before {
  content: "";
  z-index: -1;
  position: absolute;
  inset-block: 0;
  inset-inline: 0.25rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-900);
}

.item[data-disabled] {
  opacity: 0.5;
}

.item-indicator {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  grid-column-start: 1;
  color: currentColor;
}

@keyframes contentShow {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes contentHide {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0;
    transform: scale(0.96);
  }
}

.description {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
  margin-top: 0.25rem;
}

.error {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: #dc2626;
  margin-top: 0.25rem;
}

.status {
  margin: 1rem 0 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
}

.form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.actions {
  display: flex;
  gap: 0.5rem;
}

.button {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-gray-900);
  cursor: pointer;
}

@media (hover: hover) {
  .button:hover {
    background-color: var(--color-gray-100);
  }
}

/* Multiple select trigger — acts as a div container */
.triggerMultiple {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  min-width: 14rem;
  min-height: 2.5rem;
  padding: 0.25rem 0.5rem;
  gap: 0.25rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
  flex-wrap: wrap;
}

.triggerMultiple:focus-within {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.valueMultiple {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  flex: 1;
  min-width: 0;
}

.placeholder {
  color: var(--color-gray-500);
}

.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0 0.375rem;
  height: 1.5rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-100);
  font-size: 0.875rem;
  color: var(--color-gray-900);
}

.tagRemove {
  all: unset;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  color: var(--color-gray-500);
}

.tagRemove:hover {
  color: var(--color-gray-900);
}
:root {
  --color-blue: oklch(45% 50% 264deg);
  --color-red: oklch(50% 55% 31deg);
  --color-gray-50: oklch(98% 0.25% 264deg);
  --color-gray-100: oklch(12% 9.5% 264deg / 5%);
  --color-gray-200: oklch(12% 9% 264deg / 7%);
  --color-gray-300: oklch(12% 8.5% 264deg / 17%);
  --color-gray-400: oklch(12% 8% 264deg / 38%);
  --color-gray-500: oklch(12% 7.5% 264deg / 50%);
  --color-gray-600: oklch(12% 7% 264deg / 67%);
  --color-gray-700: oklch(12% 6% 264deg / 77%);
  --color-gray-800: oklch(12% 5% 264deg / 85%);
  --color-gray-900: oklch(12% 5% 264deg / 90%);
  --color-gray-950: oklch(12% 5% 264deg / 95%);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-blue: oklch(69% 50% 264deg);
    --color-red: oklch(80% 55% 31deg);
    --color-gray-50: oklch(17% 0.25% 264deg);
    --color-gray-100: oklch(28% 0.75% 264deg / 65%);
    --color-gray-200: oklch(29% 0.75% 264deg / 80%);
    --color-gray-300: oklch(35% 0.75% 264deg / 80%);
    --color-gray-400: oklch(47% 0.875% 264deg / 80%);
    --color-gray-500: oklch(64% 1% 264deg / 80%);
    --color-gray-600: oklch(82% 1% 264deg / 80%);
    --color-gray-700: oklch(92% 1.125% 264deg / 80%);
    --color-gray-800: oklch(93% 0.875% 264deg / 85%);
    --color-gray-900: oklch(95% 0.5% 264deg / 90%);
    --color-gray-950: oklch(94% 0.375% 264deg / 95%);
  }
}

Multiple selection

Use the multiple prop to allow selecting more than one option.

View source
import { createSignal, For } from "solid-js"
import * as Select from "@danielfrg/solid-ui/select"
import styles from "./index.module.css"

const FRUITS = ["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]

export function DemoSelectMultiple() {
  const [values, setValues] = createSignal<string[]>(["Blueberry", "Grapes"])

  return (
    <Select.Root<string>
      multiple
      options={FRUITS}
      value={values()}
      onChange={setValues}
      placeholder="Select fruits..."
      itemComponent={(props) => (
        <Select.Item item={props.item} class={styles.item}>
          <Select.ItemIndicator class={styles["item-indicator"]}>
            <CheckIcon />
          </Select.ItemIndicator>
          <Select.ItemLabel>{props.item.rawValue}</Select.ItemLabel>
        </Select.Item>
      )}
    >
      <Select.Trigger as="div" class={styles.triggerMultiple} aria-label="Fruits">
        <Select.Value<string> class={styles.valueMultiple}>
          {(state) => (
            <For each={state.selectedOptions()} fallback={<span class={styles.placeholder}>Select fruits...</span>}>
              {(option) => (
                <span class={styles.tag}>
                  {option}
                  <button
                    class={styles.tagRemove}
                    onPointerDown={(e) => e.stopPropagation()}
                    onClick={() => state.remove(option)}
                    aria-label={`Remove ${option}`}
                  >
                    <CloseIcon />
                  </button>
                </span>
              )}
            </For>
          )}
        </Select.Value>
        <Select.Icon class={styles.icon}>
          <ChevronDownIcon />
        </Select.Icon>
      </Select.Trigger>
      <Select.Portal>
        <Select.Content class={styles.content}>
          <Select.Listbox class={styles.listbox} />
        </Select.Content>
      </Select.Portal>
    </Select.Root>
  )
}

function CheckIcon() {
  return (
    <svg width="12" height="12" viewBox="0 0 12 12" fill="currentcolor" style={{ display: "block" }}>
      <path d="M9.854 3.146a.5.5 0 0 1 0 .708l-5 5a.5.5 0 0 1-.708 0l-2-2a.5.5 0 1 1 .708-.708L4.5 7.793l4.646-4.647a.5.5 0 0 1 .708 0Z" />
    </svg>
  )
}

function ChevronDownIcon() {
  return (
    <svg width="15" height="15" viewBox="0 0 15 15" fill="none">
      <path
        d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z"
        fill="currentColor"
        fill-rule="evenodd"
        clip-rule="evenodd"
      />
    </svg>
  )
}

function CloseIcon() {
  return (
    <svg width="10" height="10" viewBox="0 0 10 10" fill="none">
      <path d="M7.5 2.5L2.5 7.5M2.5 2.5L7.5 7.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
    </svg>
  )
}
.trigger {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  width: 12rem;
  height: 2.5rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
}

@media (hover: hover) {
  .trigger:hover {
    background-color: var(--color-gray-100);
  }
}

.trigger[data-expanded] {
  background-color: var(--color-gray-100);
}

.trigger:focus-visible {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.trigger[data-disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

.value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.value[data-placeholder-shown] {
  color: var(--color-gray-500);
}

.icon {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  margin-left: 0.5rem;
  color: var(--color-gray-500);
}

.content {
  box-sizing: border-box;
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  background-clip: padding-box;
  outline: 1px solid var(--color-gray-200);
  box-shadow:
    0 10px 15px -3px var(--color-gray-200),
    0 4px 6px -4px var(--color-gray-200);
  z-index: 50;
  animation: contentHide 150ms ease forwards;
}

.content[data-expanded] {
  animation: contentShow 150ms ease;
}

@media (prefers-color-scheme: dark) {
  .content {
    outline: 1px solid var(--color-gray-300);
    box-shadow: none;
  }
}

.listbox {
  margin: 0;
  overflow-y: auto;
  max-height: 20rem;
  padding: 0.25rem;
  outline: 0;
}

.listbox:focus-visible {
  outline: 0;
}

.item {
  box-sizing: border-box;
  display: grid;
  align-items: center;
  grid-template-columns: 0.75rem 1fr;
  gap: 0.5rem;
  padding: 0.5rem 1rem 0.5rem 0.625rem;
  border-radius: 0;
  outline: 0;
  font-size: 0.875rem;
  line-height: 1rem;
  color: var(--color-gray-900);
  cursor: default;
  user-select: none;
  position: relative;
}

.item[data-highlighted] {
  z-index: 0;
  color: var(--color-gray-50);
}

.item[data-highlighted]::before {
  content: "";
  z-index: -1;
  position: absolute;
  inset-block: 0;
  inset-inline: 0.25rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-900);
}

.item[data-disabled] {
  opacity: 0.5;
}

.item-indicator {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  grid-column-start: 1;
  color: currentColor;
}

@keyframes contentShow {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes contentHide {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0;
    transform: scale(0.96);
  }
}

.description {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
  margin-top: 0.25rem;
}

.error {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: #dc2626;
  margin-top: 0.25rem;
}

.status {
  margin: 1rem 0 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
}

.form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.actions {
  display: flex;
  gap: 0.5rem;
}

.button {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2rem;
  padding: 0 0.75rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-gray-900);
  cursor: pointer;
}

@media (hover: hover) {
  .button:hover {
    background-color: var(--color-gray-100);
  }
}

/* Multiple select trigger — acts as a div container */
.triggerMultiple {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  min-width: 14rem;
  min-height: 2.5rem;
  padding: 0.25rem 0.5rem;
  gap: 0.25rem;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  cursor: pointer;
  user-select: none;
  flex-wrap: wrap;
}

.triggerMultiple:focus-within {
  outline: 2px solid var(--color-gray-900);
  outline-offset: -1px;
}

.valueMultiple {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  flex: 1;
  min-width: 0;
}

.placeholder {
  color: var(--color-gray-500);
}

.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0 0.375rem;
  height: 1.5rem;
  border-radius: 0.25rem;
  background-color: var(--color-gray-100);
  font-size: 0.875rem;
  color: var(--color-gray-900);
}

.tagRemove {
  all: unset;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  color: var(--color-gray-500);
}

.tagRemove:hover {
  color: var(--color-gray-900);
}
:root {
  --color-blue: oklch(45% 50% 264deg);
  --color-red: oklch(50% 55% 31deg);
  --color-gray-50: oklch(98% 0.25% 264deg);
  --color-gray-100: oklch(12% 9.5% 264deg / 5%);
  --color-gray-200: oklch(12% 9% 264deg / 7%);
  --color-gray-300: oklch(12% 8.5% 264deg / 17%);
  --color-gray-400: oklch(12% 8% 264deg / 38%);
  --color-gray-500: oklch(12% 7.5% 264deg / 50%);
  --color-gray-600: oklch(12% 7% 264deg / 67%);
  --color-gray-700: oklch(12% 6% 264deg / 77%);
  --color-gray-800: oklch(12% 5% 264deg / 85%);
  --color-gray-900: oklch(12% 5% 264deg / 90%);
  --color-gray-950: oklch(12% 5% 264deg / 95%);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-blue: oklch(69% 50% 264deg);
    --color-red: oklch(80% 55% 31deg);
    --color-gray-50: oklch(17% 0.25% 264deg);
    --color-gray-100: oklch(28% 0.75% 264deg / 65%);
    --color-gray-200: oklch(29% 0.75% 264deg / 80%);
    --color-gray-300: oklch(35% 0.75% 264deg / 80%);
    --color-gray-400: oklch(47% 0.875% 264deg / 80%);
    --color-gray-500: oklch(64% 1% 264deg / 80%);
    --color-gray-600: oklch(82% 1% 264deg / 80%);
    --color-gray-700: oklch(92% 1.125% 264deg / 80%);
    --color-gray-800: oklch(93% 0.875% 264deg / 85%);
    --color-gray-900: oklch(95% 0.5% 264deg / 90%);
    --color-gray-950: oklch(94% 0.375% 264deg / 95%);
  }
}