Combobox
A searchable dropdown for selecting a value from a list of options.
View source
import * as Combobox from "@danielfrg/solid-ui/combobox"
import styles from "./index.module.css"
const FRUITS = ["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]
export function DemoComboboxHero() {
return (
<Combobox.Root
options={FRUITS}
placeholder="Search a fruit..."
itemComponent={(props) => (
<Combobox.Item item={props.item} class={styles.item}>
<Combobox.ItemLabel>{props.item.rawValue}</Combobox.ItemLabel>
<Combobox.ItemIndicator class={styles["item-indicator"]}>
<CheckIcon />
</Combobox.ItemIndicator>
</Combobox.Item>
)}
>
<Combobox.Control<string> class={styles.control} aria-label="Fruit">
{(state) => (
<>
<Combobox.Input class={styles.input} />
<Combobox.Trigger class={styles.trigger}>
<Combobox.Icon class={styles.icon}>
<ChevronDownIcon />
</Combobox.Icon>
</Combobox.Trigger>
</>
)}
</Combobox.Control>
<Combobox.Portal>
<Combobox.Content class={styles.content}>
<Combobox.Listbox class={styles.listbox} />
</Combobox.Content>
</Combobox.Portal>
</Combobox.Root>
)
}
function CheckIcon() {
return (
<svg width="15" height="15" viewBox="0 0 15 15" fill="none">
<path
d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3354 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.5553 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z"
fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
/>
</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>
)
}.control {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 12rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
overflow: hidden;
}
.control:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.control[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.input {
box-sizing: border-box;
flex: 1;
min-width: 0;
height: 2.5rem;
padding: 0 0.75rem;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
}
.input::placeholder {
color: var(--color-gray-500);
}
.trigger {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 2rem;
height: 2.5rem;
outline: 0;
border: none;
background-color: transparent;
cursor: pointer;
}
.trigger[data-disabled] {
cursor: not-allowed;
}
.icon {
display: flex;
align-items: center;
color: var(--color-gray-500);
}
.content {
box-sizing: border-box;
border-radius: 0.5rem;
background-color: var(--color-gray-50);
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;
}
.item {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.375rem 0.5rem;
border-radius: 0.25rem;
outline: 0;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
cursor: default;
user-select: none;
}
.item[data-highlighted] {
background-color: var(--color-gray-100);
}
.item[data-disabled] {
opacity: 0.5;
}
.item-indicator {
display: flex;
align-items: center;
flex-shrink: 0;
margin-left: 0.5rem;
color: var(--color-blue);
}
@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 combobox control */
.controlMultiple {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 16rem;
min-height: 2.5rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
padding: 0.25rem 0.25rem 0.25rem 0.5rem;
gap: 0.25rem;
flex-wrap: wrap;
}
.controlMultiple:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
flex: 1;
min-width: 0;
align-items: center;
}
.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);
}
.inputInline {
box-sizing: border-box;
flex: 1;
min-width: 4rem;
height: 1.5rem;
padding: 0;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 0.875rem;
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 pre-select an option without controlling the state.
View source
import * as Combobox from "@danielfrg/solid-ui/combobox"
import styles from "./index.module.css"
const FRUITS = ["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]
export function DemoComboboxDefaultValue() {
return (
<Combobox.Root
options={FRUITS}
defaultValue="Blueberry"
itemComponent={(props) => (
<Combobox.Item item={props.item} class={styles.item}>
<Combobox.ItemLabel>{props.item.rawValue}</Combobox.ItemLabel>
<Combobox.ItemIndicator class={styles["item-indicator"]}>
<CheckIcon />
</Combobox.ItemIndicator>
</Combobox.Item>
)}
>
<Combobox.Control<string> class={styles.control} aria-label="Fruit">
{() => (
<>
<Combobox.Input class={styles.input} />
<Combobox.Trigger class={styles.trigger}>
<Combobox.Icon class={styles.icon}>
<ChevronDownIcon />
</Combobox.Icon>
</Combobox.Trigger>
</>
)}
</Combobox.Control>
<Combobox.Portal>
<Combobox.Content class={styles.content}>
<Combobox.Listbox class={styles.listbox} />
</Combobox.Content>
</Combobox.Portal>
</Combobox.Root>
)
}
function CheckIcon() {
return (
<svg width="15" height="15" viewBox="0 0 15 15" fill="none">
<path
d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3354 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.5553 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z"
fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
/>
</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>
)
}.control {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 12rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
overflow: hidden;
}
.control:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.control[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.input {
box-sizing: border-box;
flex: 1;
min-width: 0;
height: 2.5rem;
padding: 0 0.75rem;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
}
.input::placeholder {
color: var(--color-gray-500);
}
.trigger {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 2rem;
height: 2.5rem;
outline: 0;
border: none;
background-color: transparent;
cursor: pointer;
}
.trigger[data-disabled] {
cursor: not-allowed;
}
.icon {
display: flex;
align-items: center;
color: var(--color-gray-500);
}
.content {
box-sizing: border-box;
border-radius: 0.5rem;
background-color: var(--color-gray-50);
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;
}
.item {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.375rem 0.5rem;
border-radius: 0.25rem;
outline: 0;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
cursor: default;
user-select: none;
}
.item[data-highlighted] {
background-color: var(--color-gray-100);
}
.item[data-disabled] {
opacity: 0.5;
}
.item-indicator {
display: flex;
align-items: center;
flex-shrink: 0;
margin-left: 0.5rem;
color: var(--color-blue);
}
@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 combobox control */
.controlMultiple {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 16rem;
min-height: 2.5rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
padding: 0.25rem 0.25rem 0.25rem 0.5rem;
gap: 0.25rem;
flex-wrap: wrap;
}
.controlMultiple:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
flex: 1;
min-width: 0;
align-items: center;
}
.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);
}
.inputInline {
box-sizing: border-box;
flex: 1;
min-width: 4rem;
height: 1.5rem;
padding: 0;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 0.875rem;
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, onChange, and onInputChange to control the selection externally.
Selected: Blueberry
View source
import { createSignal } from "solid-js"
import * as Combobox from "@danielfrg/solid-ui/combobox"
import styles from "./index.module.css"
const FRUITS = ["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]
export function DemoComboboxControlled() {
const [value, setValue] = createSignal<string | undefined>("Blueberry")
return (
<div>
<Combobox.Root
options={FRUITS}
value={value()}
onChange={setValue}
onInputChange={(v) => {
if (!v) setValue(undefined)
}}
itemComponent={(props) => (
<Combobox.Item item={props.item} class={styles.item}>
<Combobox.ItemLabel>{props.item.rawValue}</Combobox.ItemLabel>
<Combobox.ItemIndicator class={styles["item-indicator"]}>
<CheckIcon />
</Combobox.ItemIndicator>
</Combobox.Item>
)}
>
<Combobox.Control<string> class={styles.control} aria-label="Fruit">
{() => (
<>
<Combobox.Input class={styles.input} />
<Combobox.Trigger class={styles.trigger}>
<Combobox.Icon class={styles.icon}>
<ChevronDownIcon />
</Combobox.Icon>
</Combobox.Trigger>
</>
)}
</Combobox.Control>
<Combobox.Portal>
<Combobox.Content class={styles.content}>
<Combobox.Listbox class={styles.listbox} />
</Combobox.Content>
</Combobox.Portal>
</Combobox.Root>
<p class={styles.status}>Selected: {value() ?? "none"}</p>
</div>
)
}
function CheckIcon() {
return (
<svg width="15" height="15" viewBox="0 0 15 15" fill="none">
<path
d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3354 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.5553 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z"
fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
/>
</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>
)
}.control {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 12rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
overflow: hidden;
}
.control:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.control[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.input {
box-sizing: border-box;
flex: 1;
min-width: 0;
height: 2.5rem;
padding: 0 0.75rem;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
}
.input::placeholder {
color: var(--color-gray-500);
}
.trigger {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 2rem;
height: 2.5rem;
outline: 0;
border: none;
background-color: transparent;
cursor: pointer;
}
.trigger[data-disabled] {
cursor: not-allowed;
}
.icon {
display: flex;
align-items: center;
color: var(--color-gray-500);
}
.content {
box-sizing: border-box;
border-radius: 0.5rem;
background-color: var(--color-gray-50);
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;
}
.item {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.375rem 0.5rem;
border-radius: 0.25rem;
outline: 0;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
cursor: default;
user-select: none;
}
.item[data-highlighted] {
background-color: var(--color-gray-100);
}
.item[data-disabled] {
opacity: 0.5;
}
.item-indicator {
display: flex;
align-items: center;
flex-shrink: 0;
margin-left: 0.5rem;
color: var(--color-blue);
}
@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 combobox control */
.controlMultiple {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 16rem;
min-height: 2.5rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
padding: 0.25rem 0.25rem 0.25rem 0.5rem;
gap: 0.25rem;
flex-wrap: wrap;
}
.controlMultiple:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
flex: 1;
min-width: 0;
align-items: center;
}
.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);
}
.inputInline {
box-sizing: border-box;
flex: 1;
min-width: 4rem;
height: 1.5rem;
padding: 0;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 0.875rem;
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 Combobox.Description to add a hint below the control.
Type to search or click to browse available fruits.
View source
import * as Combobox from "@danielfrg/solid-ui/combobox"
import styles from "./index.module.css"
const FRUITS = ["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]
export function DemoComboboxDescription() {
return (
<Combobox.Root
options={FRUITS}
placeholder="Search a fruit..."
itemComponent={(props) => (
<Combobox.Item item={props.item} class={styles.item}>
<Combobox.ItemLabel>{props.item.rawValue}</Combobox.ItemLabel>
<Combobox.ItemIndicator class={styles["item-indicator"]}>
<CheckIcon />
</Combobox.ItemIndicator>
</Combobox.Item>
)}
>
<Combobox.Control<string> class={styles.control} aria-label="Fruit">
{() => (
<>
<Combobox.Input class={styles.input} />
<Combobox.Trigger class={styles.trigger}>
<Combobox.Icon class={styles.icon}>
<ChevronDownIcon />
</Combobox.Icon>
</Combobox.Trigger>
</>
)}
</Combobox.Control>
<Combobox.Description class={styles.description}>
Type to search or click to browse available fruits.
</Combobox.Description>
<Combobox.Portal>
<Combobox.Content class={styles.content}>
<Combobox.Listbox class={styles.listbox} />
</Combobox.Content>
</Combobox.Portal>
</Combobox.Root>
)
}
function CheckIcon() {
return (
<svg width="15" height="15" viewBox="0 0 15 15" fill="none">
<path
d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3354 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.5553 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z"
fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
/>
</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>
)
}.control {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 12rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
overflow: hidden;
}
.control:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.control[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.input {
box-sizing: border-box;
flex: 1;
min-width: 0;
height: 2.5rem;
padding: 0 0.75rem;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
}
.input::placeholder {
color: var(--color-gray-500);
}
.trigger {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 2rem;
height: 2.5rem;
outline: 0;
border: none;
background-color: transparent;
cursor: pointer;
}
.trigger[data-disabled] {
cursor: not-allowed;
}
.icon {
display: flex;
align-items: center;
color: var(--color-gray-500);
}
.content {
box-sizing: border-box;
border-radius: 0.5rem;
background-color: var(--color-gray-50);
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;
}
.item {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.375rem 0.5rem;
border-radius: 0.25rem;
outline: 0;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
cursor: default;
user-select: none;
}
.item[data-highlighted] {
background-color: var(--color-gray-100);
}
.item[data-disabled] {
opacity: 0.5;
}
.item-indicator {
display: flex;
align-items: center;
flex-shrink: 0;
margin-left: 0.5rem;
color: var(--color-blue);
}
@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 combobox control */
.controlMultiple {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 16rem;
min-height: 2.5rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
padding: 0.25rem 0.25rem 0.25rem 0.5rem;
gap: 0.25rem;
flex-wrap: wrap;
}
.controlMultiple:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
flex: 1;
min-width: 0;
align-items: center;
}
.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);
}
.inputInline {
box-sizing: border-box;
flex: 1;
min-width: 4rem;
height: 1.5rem;
padding: 0;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 0.875rem;
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 Combobox.ErrorMessage to show validation feedback.
Please select a fruit.
View source
import { createSignal } from "solid-js"
import * as Combobox from "@danielfrg/solid-ui/combobox"
import styles from "./index.module.css"
const FRUITS = ["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]
export function DemoComboboxErrorMessage() {
const [value, setValue] = createSignal<string | undefined>(undefined)
return (
<Combobox.Root
options={FRUITS}
value={value()}
onChange={setValue}
validationState={!value() ? "invalid" : "valid"}
placeholder="Search a fruit..."
itemComponent={(props) => (
<Combobox.Item item={props.item} class={styles.item}>
<Combobox.ItemLabel>{props.item.rawValue}</Combobox.ItemLabel>
<Combobox.ItemIndicator class={styles["item-indicator"]}>
<CheckIcon />
</Combobox.ItemIndicator>
</Combobox.Item>
)}
>
<Combobox.Control<string> class={styles.control} aria-label="Fruit">
{() => (
<>
<Combobox.Input class={styles.input} />
<Combobox.Trigger class={styles.trigger}>
<Combobox.Icon class={styles.icon}>
<ChevronDownIcon />
</Combobox.Icon>
</Combobox.Trigger>
</>
)}
</Combobox.Control>
<Combobox.ErrorMessage class={styles.error}>Please select a fruit.</Combobox.ErrorMessage>
<Combobox.Portal>
<Combobox.Content class={styles.content}>
<Combobox.Listbox class={styles.listbox} />
</Combobox.Content>
</Combobox.Portal>
</Combobox.Root>
)
}
function CheckIcon() {
return (
<svg width="15" height="15" viewBox="0 0 15 15" fill="none">
<path
d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3354 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.5553 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z"
fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
/>
</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>
)
}.control {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 12rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
overflow: hidden;
}
.control:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.control[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.input {
box-sizing: border-box;
flex: 1;
min-width: 0;
height: 2.5rem;
padding: 0 0.75rem;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
}
.input::placeholder {
color: var(--color-gray-500);
}
.trigger {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 2rem;
height: 2.5rem;
outline: 0;
border: none;
background-color: transparent;
cursor: pointer;
}
.trigger[data-disabled] {
cursor: not-allowed;
}
.icon {
display: flex;
align-items: center;
color: var(--color-gray-500);
}
.content {
box-sizing: border-box;
border-radius: 0.5rem;
background-color: var(--color-gray-50);
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;
}
.item {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.375rem 0.5rem;
border-radius: 0.25rem;
outline: 0;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
cursor: default;
user-select: none;
}
.item[data-highlighted] {
background-color: var(--color-gray-100);
}
.item[data-disabled] {
opacity: 0.5;
}
.item-indicator {
display: flex;
align-items: center;
flex-shrink: 0;
margin-left: 0.5rem;
color: var(--color-blue);
}
@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 combobox control */
.controlMultiple {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 16rem;
min-height: 2.5rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
padding: 0.25rem 0.25rem 0.25rem 0.5rem;
gap: 0.25rem;
flex-wrap: wrap;
}
.controlMultiple:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
flex: 1;
min-width: 0;
align-items: center;
}
.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);
}
.inputInline {
box-sizing: border-box;
flex: 1;
min-width: 4rem;
height: 1.5rem;
padding: 0;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 0.875rem;
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 Combobox.HiddenSelect to include the value in a native form submission.
View source
import * as Combobox from "@danielfrg/solid-ui/combobox"
import styles from "./index.module.css"
const FRUITS = ["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]
export function DemoComboboxHtmlForm() {
return (
<form
onSubmit={(e) => {
e.preventDefault()
const data = new FormData(e.currentTarget)
window.alert(JSON.stringify(Object.fromEntries(data), null, 2))
}}
class={styles.form}
>
<Combobox.Root
options={FRUITS}
name="fruit"
placeholder="Search a fruit..."
itemComponent={(props) => (
<Combobox.Item item={props.item} class={styles.item}>
<Combobox.ItemLabel>{props.item.rawValue}</Combobox.ItemLabel>
<Combobox.ItemIndicator class={styles["item-indicator"]}>
<CheckIcon />
</Combobox.ItemIndicator>
</Combobox.Item>
)}
>
<Combobox.HiddenSelect />
<Combobox.Control<string> class={styles.control} aria-label="Fruit">
{() => (
<>
<Combobox.Input class={styles.input} />
<Combobox.Trigger class={styles.trigger}>
<Combobox.Icon class={styles.icon}>
<ChevronDownIcon />
</Combobox.Icon>
</Combobox.Trigger>
</>
)}
</Combobox.Control>
<Combobox.Portal>
<Combobox.Content class={styles.content}>
<Combobox.Listbox class={styles.listbox} />
</Combobox.Content>
</Combobox.Portal>
</Combobox.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="15" height="15" viewBox="0 0 15 15" fill="none">
<path
d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3354 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.5553 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z"
fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
/>
</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>
)
}.control {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 12rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
overflow: hidden;
}
.control:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.control[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.input {
box-sizing: border-box;
flex: 1;
min-width: 0;
height: 2.5rem;
padding: 0 0.75rem;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
}
.input::placeholder {
color: var(--color-gray-500);
}
.trigger {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 2rem;
height: 2.5rem;
outline: 0;
border: none;
background-color: transparent;
cursor: pointer;
}
.trigger[data-disabled] {
cursor: not-allowed;
}
.icon {
display: flex;
align-items: center;
color: var(--color-gray-500);
}
.content {
box-sizing: border-box;
border-radius: 0.5rem;
background-color: var(--color-gray-50);
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;
}
.item {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.375rem 0.5rem;
border-radius: 0.25rem;
outline: 0;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
cursor: default;
user-select: none;
}
.item[data-highlighted] {
background-color: var(--color-gray-100);
}
.item[data-disabled] {
opacity: 0.5;
}
.item-indicator {
display: flex;
align-items: center;
flex-shrink: 0;
margin-left: 0.5rem;
color: var(--color-blue);
}
@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 combobox control */
.controlMultiple {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 16rem;
min-height: 2.5rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
padding: 0.25rem 0.25rem 0.25rem 0.5rem;
gap: 0.25rem;
flex-wrap: wrap;
}
.controlMultiple:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
flex: 1;
min-width: 0;
align-items: center;
}
.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);
}
.inputInline {
box-sizing: border-box;
flex: 1;
min-width: 4rem;
height: 1.5rem;
padding: 0;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 0.875rem;
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, optionLabel, and optionDisabled to work with object arrays.
View source
import * as Combobox from "@danielfrg/solid-ui/combobox"
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 DemoComboboxObject() {
return (
<Combobox.Root<Food>
options={FOODS}
optionValue="value"
optionTextValue="label"
optionLabel="label"
optionDisabled="disabled"
placeholder="Search a fruit..."
itemComponent={(props) => (
<Combobox.Item item={props.item} class={styles.item}>
<Combobox.ItemLabel>{props.item.rawValue.label}</Combobox.ItemLabel>
<Combobox.ItemIndicator class={styles["item-indicator"]}>
<CheckIcon />
</Combobox.ItemIndicator>
</Combobox.Item>
)}
>
<Combobox.Control<Food> class={styles.control} aria-label="Fruit">
{() => (
<>
<Combobox.Input class={styles.input} />
<Combobox.Trigger class={styles.trigger}>
<Combobox.Icon class={styles.icon}>
<ChevronDownIcon />
</Combobox.Icon>
</Combobox.Trigger>
</>
)}
</Combobox.Control>
<Combobox.Portal>
<Combobox.Content class={styles.content}>
<Combobox.Listbox class={styles.listbox} />
</Combobox.Content>
</Combobox.Portal>
</Combobox.Root>
)
}
function CheckIcon() {
return (
<svg width="15" height="15" viewBox="0 0 15 15" fill="none">
<path
d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3354 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.5553 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z"
fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
/>
</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>
)
}.control {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 12rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
overflow: hidden;
}
.control:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.control[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.input {
box-sizing: border-box;
flex: 1;
min-width: 0;
height: 2.5rem;
padding: 0 0.75rem;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
}
.input::placeholder {
color: var(--color-gray-500);
}
.trigger {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 2rem;
height: 2.5rem;
outline: 0;
border: none;
background-color: transparent;
cursor: pointer;
}
.trigger[data-disabled] {
cursor: not-allowed;
}
.icon {
display: flex;
align-items: center;
color: var(--color-gray-500);
}
.content {
box-sizing: border-box;
border-radius: 0.5rem;
background-color: var(--color-gray-50);
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;
}
.item {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.375rem 0.5rem;
border-radius: 0.25rem;
outline: 0;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
cursor: default;
user-select: none;
}
.item[data-highlighted] {
background-color: var(--color-gray-100);
}
.item[data-disabled] {
opacity: 0.5;
}
.item-indicator {
display: flex;
align-items: center;
flex-shrink: 0;
margin-left: 0.5rem;
color: var(--color-blue);
}
@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 combobox control */
.controlMultiple {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 16rem;
min-height: 2.5rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
padding: 0.25rem 0.25rem 0.25rem 0.5rem;
gap: 0.25rem;
flex-wrap: wrap;
}
.controlMultiple:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
flex: 1;
min-width: 0;
align-items: center;
}
.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);
}
.inputInline {
box-sizing: border-box;
flex: 1;
min-width: 4rem;
height: 1.5rem;
padding: 0;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 0.875rem;
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 Combobox from "@danielfrg/solid-ui/combobox"
import styles from "./index.module.css"
const FRUITS = ["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]
export function DemoComboboxMultiple() {
const [values, setValues] = createSignal<string[]>(["Blueberry", "Grapes"])
return (
<Combobox.Root<string>
multiple
options={FRUITS}
value={values()}
onChange={setValues}
placeholder="Search fruits..."
itemComponent={(props) => (
<Combobox.Item item={props.item} class={styles.item}>
<Combobox.ItemLabel>{props.item.rawValue}</Combobox.ItemLabel>
<Combobox.ItemIndicator class={styles["item-indicator"]}>
<CheckIcon />
</Combobox.ItemIndicator>
</Combobox.Item>
)}
>
<Combobox.Control<string> class={styles.controlMultiple} aria-label="Fruits">
{(state) => (
<>
<div class={styles.tags}>
<For each={state.selectedOptions()}>
{(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>
<Combobox.Input class={styles.inputInline} />
</div>
<Combobox.Trigger class={styles.trigger}>
<Combobox.Icon class={styles.icon}>
<ChevronDownIcon />
</Combobox.Icon>
</Combobox.Trigger>
</>
)}
</Combobox.Control>
<Combobox.Portal>
<Combobox.Content class={styles.content}>
<Combobox.Listbox class={styles.listbox} />
</Combobox.Content>
</Combobox.Portal>
</Combobox.Root>
)
}
function CheckIcon() {
return (
<svg width="15" height="15" viewBox="0 0 15 15" fill="none">
<path
d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3354 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.5553 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z"
fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
/>
</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>
)
}.control {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 12rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
overflow: hidden;
}
.control:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.control[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.input {
box-sizing: border-box;
flex: 1;
min-width: 0;
height: 2.5rem;
padding: 0 0.75rem;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
}
.input::placeholder {
color: var(--color-gray-500);
}
.trigger {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 2rem;
height: 2.5rem;
outline: 0;
border: none;
background-color: transparent;
cursor: pointer;
}
.trigger[data-disabled] {
cursor: not-allowed;
}
.icon {
display: flex;
align-items: center;
color: var(--color-gray-500);
}
.content {
box-sizing: border-box;
border-radius: 0.5rem;
background-color: var(--color-gray-50);
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;
}
.item {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.375rem 0.5rem;
border-radius: 0.25rem;
outline: 0;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-900);
cursor: default;
user-select: none;
}
.item[data-highlighted] {
background-color: var(--color-gray-100);
}
.item[data-disabled] {
opacity: 0.5;
}
.item-indicator {
display: flex;
align-items: center;
flex-shrink: 0;
margin-left: 0.5rem;
color: var(--color-blue);
}
@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 combobox control */
.controlMultiple {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 16rem;
min-height: 2.5rem;
border: 1px solid var(--color-gray-300);
border-radius: 0.375rem;
background-color: transparent;
padding: 0.25rem 0.25rem 0.25rem 0.5rem;
gap: 0.25rem;
flex-wrap: wrap;
}
.controlMultiple:focus-within {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
flex: 1;
min-width: 0;
align-items: center;
}
.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);
}
.inputInline {
box-sizing: border-box;
flex: 1;
min-width: 4rem;
height: 1.5rem;
padding: 0;
outline: 0;
border: none;
background-color: transparent;
font-family: inherit;
font-size: 0.875rem;
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%);
}
}