Combobox 10.18.1
An input that behaves similarly to a select, with the addition of a free text input to filter options.
Based on Headless U.
Default / Nullable
The <Combobox />
component enables selection from a defined range of options. Its nullable
property determines if the selected value can be cleared or not.
To provide informative helper text below the dropdown field, utilize the <Combobox.Hint />
sub-component.
The <Combobox />
also offers multiple placement options determined by the position
property, as detailed in the props table
below. The default position is bottom
when not specified.
<Combobox> <Combobox.Trigger>...</Combobox.Trigger> <Combobox.Options> <Combobox.Option>...</Combobox.Option> </Combobox.Options> <Combobox.Hint>...</Combobox.Hint> </Combobox>
Informative message holder (default)
Informative message holder (nullable)
"use client";
import React, { useState } from "react";
import { Combobox, MenuItem } from "@heathmont/moon-core-tw";
import { ControlsChevronDownSmall } from "@heathmont/moon-icons-tw";
const people = [
{ id: 1, label: "Wade Cooper", value: "Wade Cooper" },
{ id: 2, label: "Arlene Mccoy", value: "Arlene Mccoy" },
{ id: 3, label: "Devon Webb", value: "Devon Webb" },
{ id: 4, label: "Tom Cook", value: "Tom Cook" },
{ id: 5, label: "Tanya Fox", value: "Tanya Fox" },
{ id: 6, label: "Hellen Schmidt", value: "Hellen Schmidt" },
];
const filter = (
query: string,
people: { id: number; label: string; value: string }[],
) => {
return query === ""
? people
: people.filter(({ value }) =>
value
.toLowerCase()
.replace(/\s+/g, "")
.includes(query.toLowerCase().replace(/\s+/g, "")),
);
};
const Example = () => {
const [selected0, setSelected0] = useState(people[4]);
const [selected1, setSelected1] = useState(people[2]);
const [query0, setQuery0] = useState<string>("");
const [query1, setQuery1] = useState<string>("");
const filteredPeople0 = filter(query0, people);
const filteredPeople1 = filter(query1, people);
return (
<div className="flex flex-col lg:flex-row lg:justify-center items-center w-full gap-4">
<Combobox
value={selected0}
onChange={setSelected0}
onQueryChange={setQuery0}
className="w-full max-w-xs"
>
{({ open }) => (
<>
<Combobox.Trigger open={open} onClose={console.log}>
<Combobox.Input
open={open}
placeholder={"Choose a name..."}
displayValue={({ label }) => label}
/>
<Combobox.Button open={open}>
<ControlsChevronDownSmall />
</Combobox.Button>
</Combobox.Trigger>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople0.length === 0 && query0 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople0.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<div>
<MenuItem isActive={active} isSelected={selected}>
{person.label}
</MenuItem>
</div>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>Informative message holder (default)</Combobox.Hint>
</>
)}
</Combobox>
<Combobox
value={selected1}
onChange={setSelected1}
onQueryChange={setQuery1}
className="w-full max-w-xs"
nullable
>
{({ open }) => (
<>
<Combobox.Trigger open={open} onClose={console.log}>
<Combobox.Input
open={open}
placeholder={"Choose a name..."}
displayValue={(person) => person?.label}
/>
<Combobox.Button open={open}>
<ControlsChevronDownSmall />
</Combobox.Button>
</Combobox.Trigger>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople1.length === 0 && query1 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople1.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<div>
<MenuItem isActive={active} isSelected={selected}>
{person.label}
</MenuItem>
</div>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>Informative message holder (nullable)</Combobox.Hint>
</>
)}
</Combobox>
</div>
);
};
export default Example;
Select
You can use the <Combobox.Select />
sub-component as an alternative to <Combobox.Trigger />
.
<Combobox />
component supports various sizes by using the size
prop. If not specified, the default size is Medium (md
).
<Combobox> <Combobox.Select>...</Combobox.Select> <Combobox.Options> <Combobox.Option>...</Combobox.Option> </Combobox.Options> <Combobox.Hint>...</Combobox.Hint> </Combobox>
Informative message holder
Informative message holder
Informative message holder
"use client";
import React, { useState } from "react";
import { Combobox, MenuItem } from "@heathmont/moon-core-tw";
import { ControlsChevronDownSmall } from "@heathmont/moon-icons-tw";
const people = [
{ id: 1, label: "Wade Cooper", value: "Wade Cooper" },
{ id: 2, label: "Arlene Mccoy", value: "Arlene Mccoy" },
{ id: 3, label: "Devon Webb", value: "Devon Webb" },
{ id: 4, label: "Tom Cook", value: "Tom Cook" },
{ id: 5, label: "Tanya Fox", value: "Tanya Fox" },
{ id: 6, label: "Hellen Schmidt", value: "Hellen Schmidt" },
];
const filter = (
query: string,
people: { id: number; label: string; value: string }[],
) => {
return query === ""
? people
: people.filter(({ value }) =>
value
.toLowerCase()
.replace(/\s+/g, "")
.includes(query.toLowerCase().replace(/\s+/g, "")),
);
};
const Example = () => {
const [selected0, setSelected0] = useState({});
const [selected1, setSelected1] = useState({});
const [selected2, setSelected2] = useState({});
const [query0, setQuery0] = useState<string>("");
const [query1, setQuery1] = useState<string>("");
const [query2, setQuery2] = useState<string>("");
const filteredPeople0 = filter(query0, people);
const filteredPeople1 = filter(query1, people);
const filteredPeople2 = filter(query2, people);
return (
<div className={"flex flex-col items-center w-full h-50"}>
<div className="flex flex-col items-center lg:flex-row lg:justify-center lg:items-start w-full gap-4">
<Combobox
value={selected0}
onChange={setSelected0}
onQueryChange={setQuery0}
className="w-full max-w-xs"
size="sm"
>
{({ open }) => (
<>
<Combobox.Select
open={open}
label="Small"
placeholder="Choose an option"
displayValue={({ label }) => label}
>
<ControlsChevronDownSmall />
</Combobox.Select>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople0.length === 0 && query0 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople0.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem isActive={active} isSelected={selected}>
<MenuItem.Title>{person.label}</MenuItem.Title>
<MenuItem.Radio isSelected={selected} />
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>Informative message holder</Combobox.Hint>
</>
)}
</Combobox>
<Combobox
value={selected1}
onChange={setSelected1}
onQueryChange={setQuery1}
className="w-full max-w-xs"
>
{({ open }) => (
<>
<Combobox.Select
open={open}
label="Medium"
placeholder="Choose an option"
displayValue={({ label }) => label}
>
<ControlsChevronDownSmall />
</Combobox.Select>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople1.length === 0 && query1 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople1.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem isActive={active} isSelected={selected}>
<MenuItem.Title>{person.label}</MenuItem.Title>
<MenuItem.Radio isSelected={selected} />
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>Informative message holder</Combobox.Hint>
</>
)}
</Combobox>
<Combobox
value={selected2}
onChange={setSelected2}
onQueryChange={setQuery2}
className="w-full max-w-xs"
size="lg"
>
{({ open }) => (
<>
<Combobox.Select
open={open}
label="Large"
placeholder="Choose an option"
displayValue={({ label }) => label}
>
<ControlsChevronDownSmall />
</Combobox.Select>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople2.length === 0 && query2 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople2.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem isActive={active} isSelected={selected}>
<MenuItem.Title>{person.label}</MenuItem.Title>
<MenuItem.Radio isSelected={selected} />
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>Informative message holder</Combobox.Hint>
</>
)}
</Combobox>
</div>
</div>
);
};
export default Example;
Different states for Select
Set the Disabled and Error states for the <Combobox />
component using the disabled
and isError
props, respectively.
Informative message holder
Informative message holder
"use client";
import React, { useState } from "react";
import { Combobox, MenuItem } from "@heathmont/moon-core-tw";
import {
ControlsChevronDownSmall,
GenericInfo,
} from "@heathmont/moon-icons-tw";
const people = [
{ id: 1, label: "Wade Cooper", value: "Wade Cooper" },
{ id: 2, label: "Arlene Mccoy", value: "Arlene Mccoy" },
{ id: 3, label: "Devon Webb", value: "Devon Webb" },
{ id: 4, label: "Tom Cook", value: "Tom Cook" },
{ id: 5, label: "Tanya Fox", value: "Tanya Fox" },
{ id: 6, label: "Hellen Schmidt", value: "Hellen Schmidt" },
];
const filter = (
query: string,
people: { id: number; label: string; value: string }[],
) => {
return query === ""
? people
: people.filter(({ value }) =>
value
.toLowerCase()
.replace(/\s+/g, "")
.includes(query.toLowerCase().replace(/\s+/g, "")),
);
};
const Example = () => {
const [selected0, setSelected0] = useState(people[2]);
const [selected1, setSelected1] = useState(people[0]);
const [query0, setQuery0] = useState<string>("");
const [query1, setQuery1] = useState<string>("");
const filteredPeople0 = filter(query0, people);
const filteredPeople1 = filter(query1, people);
return (
<div className="flex flex-col items-end lg:flex-row lg:justify-center w-full gap-4">
<Combobox
value={selected0}
onChange={setSelected0}
onQueryChange={setQuery0}
isError
className="w-full max-w-xs"
>
{({ open }) => (
<>
<Combobox.Select
open={open}
label="Error"
placeholder="Choose an option"
displayValue={({ label }) => label}
>
<ControlsChevronDownSmall />
</Combobox.Select>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople0.length === 0 && query0 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople0.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem isActive={active} isSelected={selected}>
<MenuItem.Title>{person.label}</MenuItem.Title>
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>
<GenericInfo />
Informative message holder
</Combobox.Hint>
</>
)}
</Combobox>
<Combobox
value={selected1}
onChange={setSelected1}
onQueryChange={setQuery1}
className="w-full max-w-xs"
disabled
>
{({ open }) => (
<>
<Combobox.Select
open={open}
label="Disabled"
placeholder="Choose an option"
displayValue={({ label }) => label}
>
<ControlsChevronDownSmall />
</Combobox.Select>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople1.length === 0 && query1 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople1.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem isActive={active} isSelected={selected}>
<MenuItem.Title>{person.label}</MenuItem.Title>
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>Informative message holder</Combobox.Hint>
</>
)}
</Combobox>
</div>
);
};
export default Example;
Select with inner label
To add an inner label to your combobox component, use the <Combobox.InsetSelect />
sub-component.
<Combobox> <Combobox.InsetSelect>...</Combobox.InsetSelect> <Combobox.Options> <Combobox.Option>...</Combobox.Option> </Combobox.Options> <Combobox.Hint>...</Combobox.Hint> </Combobox>
Informative message holder
"use client";
import React, { useState } from "react";
import { Combobox, MenuItem } from "@heathmont/moon-core-tw";
import { ControlsChevronDownSmall } from "@heathmont/moon-icons-tw";
const people = [
{ id: 1, label: "Wade Cooper", value: "Wade Cooper" },
{ id: 2, label: "Arlene Mccoy", value: "Arlene Mccoy" },
{ id: 3, label: "Devon Webb", value: "Devon Webb" },
{ id: 4, label: "Tom Cook", value: "Tom Cook" },
{ id: 5, label: "Tanya Fox", value: "Tanya Fox" },
{ id: 6, label: "Hellen Schmidt", value: "Hellen Schmidt" },
];
const filter = (
query: string,
people: { id: number; label: string; value: string }[],
) => {
return query === ""
? people
: people.filter(({ value }) =>
value
.toLowerCase()
.replace(/\s+/g, "")
.includes(query.toLowerCase().replace(/\s+/g, "")),
);
};
const Example = () => {
const [option, setOption] = useState({});
const [query, setQuery] = useState<string>("");
const filteredPeople = filter(query, people);
return (
<div className="flex w-full max-w-xs items-center">
<Combobox value={option} onChange={setOption} onQueryChange={setQuery}>
{({ open }) => (
<>
<Combobox.InsetSelect
open={open}
label="Label"
placeholder="Choose an option"
displayValue={({ label }) => label}
>
<ControlsChevronDownSmall />
</Combobox.InsetSelect>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople.length === 0 && query !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem isActive={active} isSelected={selected}>
<MenuItem.Title>{person.label}</MenuItem.Title>
<MenuItem.Radio isSelected={selected} />
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>Informative message holder</Combobox.Hint>
</>
)}
</Combobox>
</div>
);
};
export default Example;
Different states for Select with inner label
Set the Disabled and Error states for the <Combobox />
component using the disabled
and isError
props, respectively.
Informative message holder
Informative message holder
"use client";
import React, { useState } from "react";
import { Combobox, MenuItem } from "@heathmont/moon-core-tw";
import {
ControlsChevronDownSmall,
GenericInfo,
} from "@heathmont/moon-icons-tw";
const people = [
{ id: 1, label: "Wade Cooper", value: "Wade Cooper" },
{ id: 2, label: "Arlene Mccoy", value: "Arlene Mccoy" },
{ id: 3, label: "Devon Webb", value: "Devon Webb" },
{ id: 4, label: "Tom Cook", value: "Tom Cook" },
{ id: 5, label: "Tanya Fox", value: "Tanya Fox" },
{ id: 6, label: "Hellen Schmidt", value: "Hellen Schmidt" },
];
const filter = (
query: string,
people: { id: number; label: string; value: string }[],
) => {
return query === ""
? people
: people.filter(({ value }) =>
value
.toLowerCase()
.replace(/\s+/g, "")
.includes(query.toLowerCase().replace(/\s+/g, "")),
);
};
const Example = () => {
const [selected0, setSelected0] = useState(people[2]);
const [selected1, setSelected1] = useState(people[0]);
const [query0, setQuery0] = useState<string>("");
const [query1, setQuery1] = useState<string>("");
const filteredPeople0 = filter(query0, people);
const filteredPeople1 = filter(query1, people);
return (
<div className="flex flex-col items-center lg:flex-row lg:justify-center lg:items-start w-full gap-4">
<Combobox
value={selected0}
onChange={setSelected0}
onQueryChange={setQuery0}
isError
className="w-full max-w-xs"
>
{({ open }) => (
<>
<Combobox.InsetSelect
open={open}
label="Error"
placeholder="Choose an option"
displayValue={({ label }) => label}
>
<ControlsChevronDownSmall />
</Combobox.InsetSelect>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople0.length === 0 && query0 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople0.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem isActive={active} isSelected={selected}>
<MenuItem.Title>{person.label}</MenuItem.Title>
<MenuItem.Radio isSelected={selected} />
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>
{" "}
<GenericInfo />
Informative message holder
</Combobox.Hint>
</>
)}
</Combobox>
<Combobox
value={selected1}
onChange={setSelected1}
onQueryChange={setQuery1}
disabled
className="w-full max-w-xs"
>
{({ open }) => (
<>
<Combobox.InsetSelect
open={open}
label="Disabled"
placeholder="Choose an option"
displayValue={({ label }) => label}
>
<ControlsChevronDownSmall />
</Combobox.InsetSelect>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople1.length === 0 && query1 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople1.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem isActive={active} isSelected={selected}>
<MenuItem.Title>{person.label}</MenuItem.Title>
<MenuItem.Radio isSelected={selected} />
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>Informative message holder</Combobox.Hint>
</>
)}
</Combobox>
</div>
);
};
export default Example;
MultiSelect
Using the multiple
prop with the <Combobox.MultiSelect />
sub-component allows <Combobox />
to support multiple option selection.
<Combobox> <Combobox.MultiSelect>...</Combobox.MultiSelect> <Combobox.Options> <Combobox.Option>...</Combobox.Option> </Combobox.Options> <Combobox.Hint>...</Combobox.Hint> </Combobox>
Informative message holder
Informative message holder
Informative message holder
"use client";
import React, { useCallback, useState } from "react";
import { Combobox, MenuItem } from "@heathmont/moon-core-tw";
import { ControlsChevronDownSmall } from "@heathmont/moon-icons-tw";
const people = [
{ id: 1, label: "Wade Cooper", value: "Wade Cooper" },
{ id: 2, label: "Arlene Mccoy", value: "Arlene Mccoy" },
{ id: 3, label: "Devon Webb", value: "Devon Webb" },
{ id: 4, label: "Tom Cook", value: "Tom Cook" },
{ id: 5, label: "Tanya Fox", value: "Tanya Fox" },
{ id: 6, label: "Hellen Schmidt", value: "Hellen Schmidt" },
];
const filter = (
query: string,
people: { id: number; label: string; value: string }[],
) => {
return query === ""
? people
: people.filter(({ value }) =>
value
.toLowerCase()
.replace(/\s+/g, "")
.includes(query.toLowerCase().replace(/\s+/g, "")),
);
};
const Example = () => {
const [selected0, setSelected0] = useState([]);
const [selected1, setSelected1] = useState([]);
const [selected2, setSelected2] = useState([]);
const [query0, setQuery0] = useState<string>("");
const [query1, setQuery1] = useState<string>("");
const [query2, setQuery2] = useState<string>("");
const filteredPeople0 = filter(query0, people);
const filteredPeople1 = filter(query1, people);
const filteredPeople2 = filter(query2, people);
return (
<div className="flex flex-col items-center lg:flex-row lg:justify-center lg:items-start w-full gap-4">
<Combobox
value={selected0}
onChange={setSelected0}
onQueryChange={setQuery0}
onClear={useCallback(() => setSelected0([]), [setSelected0])}
className="w-full max-w-xs"
size="sm"
multiple
>
{({ open }) => (
<>
<Combobox.MultiSelect
open={open}
label="Small"
counter={selected0.length}
placeholder="Choose an option"
displayValue={({ label }) => label}
onClose={console.log}
>
<ControlsChevronDownSmall />
</Combobox.MultiSelect>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople0.length === 0 && query0 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople0.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem
isActive={active}
isSelected={selected}
data-testid={`test-${index}`}
>
<MenuItem.Title>{person.label}</MenuItem.Title>
<MenuItem.Checkbox isSelected={selected} />
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>Informative message holder</Combobox.Hint>
</>
)}
</Combobox>
<Combobox
value={selected1}
onChange={setSelected1}
onQueryChange={setQuery1}
onClear={useCallback(() => setSelected1([]), [setSelected1])}
className="w-full max-w-xs"
multiple
>
{({ open }) => (
<>
<Combobox.MultiSelect
open={open}
label="Medium"
counter={selected1.length}
placeholder="Choose an option"
displayValue={({ label }) => label}
onClose={console.log}
>
<ControlsChevronDownSmall />
</Combobox.MultiSelect>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople1.length === 0 && query1 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople1.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem
isActive={active}
isSelected={selected}
data-testid={`test-${index}`}
>
<MenuItem.Title>{person.label}</MenuItem.Title>
<MenuItem.Checkbox isSelected={selected} />
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>Informative message holder</Combobox.Hint>
</>
)}
</Combobox>
<Combobox
value={selected2}
onChange={setSelected2}
onQueryChange={setQuery2}
onClear={useCallback(() => setSelected2([]), [setSelected2])}
className="w-full max-w-xs"
size="lg"
multiple
>
{({ open }) => (
<>
<Combobox.MultiSelect
open={open}
label="Large"
counter={selected2.length}
placeholder="Choose an option"
displayValue={({ label }) => label}
onClose={console.log}
>
<ControlsChevronDownSmall />
</Combobox.MultiSelect>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople2.length === 0 && query2 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople2.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem
isActive={active}
isSelected={selected}
data-testid={`test-${index}`}
>
<MenuItem.Title>{person.label}</MenuItem.Title>
<MenuItem.Checkbox isSelected={selected} />
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>Informative message holder</Combobox.Hint>
</>
)}
</Combobox>
</div>
);
};
export default Example;
MultiSelect with "Select all" option
Multiselect can be utilized for choosing all
options, as demonstrated in the example below.
Informative message holder
"use client";
import React, { useCallback, useState } from "react";
import { Combobox, MenuItem } from "@heathmont/moon-core-tw";
import { ControlsChevronDownSmall } from "@heathmont/moon-icons-tw";
import { boolean } from "zod";
const people = [
{ id: 1, label: "Wade Cooper", value: "Wade Cooper" },
{ id: 2, label: "Arlene Mccoy", value: "Arlene Mccoy" },
{ id: 3, label: "Devon Webb", value: "Devon Webb" },
{ id: 4, label: "Tom Cook", value: "Tom Cook" },
{ id: 5, label: "Tanya Fox", value: "Tanya Fox" },
{ id: 6, label: "Hellen Schmidt", value: "Hellen Schmidt" },
];
const filter = (
query: string,
people: { id: number; label: string; value: string }[],
) => {
return query === ""
? people
: people.filter(({ value }) =>
value
.toLowerCase()
.replace(/\s+/g, "")
.includes(query.toLowerCase().replace(/\s+/g, "")),
);
};
const Example = () => {
const [selected, setSelected] = useState([]);
const [isAllSelected, setIsAllSelected] = useState(false);
const [query, setQuery] = useState<string>("");
const filteredPeople = filter(query, people);
const onChange = useCallback(
(value: unknown) => {
const selectedItems = value as [];
const allSelected = selectedItems.filter((item) => {
return Array.isArray(item);
});
if (allSelected.length > 0) {
const items = allSelected.at(0) as unknown as [];
if (isAllSelected) {
setSelected([]);
} else {
setSelected(items);
}
setIsAllSelected(!isAllSelected);
} else {
setSelected(selectedItems);
setIsAllSelected(selectedItems.length === people.length);
}
},
[setSelected, isAllSelected],
);
const onClear = useCallback(() => {
setSelected([]);
setIsAllSelected(false);
}, [setSelected, setIsAllSelected]);
return (
<div className="flex flex-col items-center lg:flex-row lg:justify-center lg:items-end w-full gap-4">
<Combobox
value={selected}
onChange={onChange}
onQueryChange={setQuery}
onClear={onClear}
className="w-full max-w-xs"
size="md"
multiple
>
{({ open }) => (
<>
<Combobox.MultiSelect
open={open}
counter={selected.length}
placeholder="Choose an option"
displayValue={({ label }) => label}
onClose={console.log}
>
<ControlsChevronDownSmall />
</Combobox.MultiSelect>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople.length === 0 && query !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
<>
{!query.length && (
<Combobox.Option value={people} key={-1}>
{({ active }) => (
<MenuItem isActive={active}>
<MenuItem.Title>Select all</MenuItem.Title>
<MenuItem.Checkbox isSelected={isAllSelected} />
</MenuItem>
)}
</Combobox.Option>
)}
{filteredPeople.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem isActive={active} isSelected={selected}>
<MenuItem.Title>{person.label}</MenuItem.Title>
<MenuItem.Checkbox isSelected={selected} />
</MenuItem>
)}
</Combobox.Option>
))}
</>
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>Informative message holder</Combobox.Hint>
</>
)}
</Combobox>
</div>
);
};
export default Example;
MultiSelect with inner label
Using the multiple
prop with the <Combobox.InsetMultiSelect />
sub-component allows <Combobox />
to support multiple option selection.
<Combobox> <Combobox.InsetMultiSelect>...</Combobox.InsetMultiSelect> <Combobox.Options> <Combobox.Option>...</Combobox.Option> </Combobox.Options> <Combobox.Hint>...</Combobox.Hint> </Combobox>
Informative message holder
"use client";
import React, { useCallback, useState } from "react";
import { Combobox, MenuItem } from "@heathmont/moon-core-tw";
import { ControlsChevronDownSmall } from "@heathmont/moon-icons-tw";
const people = [
{ id: 1, label: "Wade Cooper", value: "Wade Cooper" },
{ id: 2, label: "Arlene Mccoy", value: "Arlene Mccoy" },
{ id: 3, label: "Devon Webb", value: "Devon Webb" },
{ id: 4, label: "Tom Cook", value: "Tom Cook" },
{ id: 5, label: "Tanya Fox", value: "Tanya Fox" },
{ id: 6, label: "Hellen Schmidt", value: "Hellen Schmidt" },
];
const filter = (
query: string,
people: { id: number; label: string; value: string }[],
) => {
return query === ""
? people
: people.filter(({ value }) =>
value
.toLowerCase()
.replace(/\s+/g, "")
.includes(query.toLowerCase().replace(/\s+/g, "")),
);
};
const Example = () => {
const [selected, setSelected] = useState([]);
const [query, setQuery] = useState<string>("");
const filteredPeople = filter(query, people);
return (
<div className="flex w-full max-w-xs items-center">
<Combobox
value={selected}
onChange={setSelected}
onQueryChange={setQuery}
onClear={useCallback(() => setSelected([]), [setSelected])}
multiple
>
{({ open }) => (
<>
<Combobox.InsetMultiSelect
open={open}
label="Select label"
counter={selected.length}
placeholder="Choose an option"
displayValue={({ label }) => label}
>
<ControlsChevronDownSmall />
</Combobox.InsetMultiSelect>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople.length === 0 && query !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem isActive={active} isSelected={selected}>
<MenuItem.Title>{person.label}</MenuItem.Title>
<MenuItem.Checkbox isSelected={selected} />
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>Informative message holder</Combobox.Hint>
</>
)}
</Combobox>
</div>
);
};
export default Example;
VisualMultiSelect
To display selected items along with their full content, use the <Combobox.VisualMultiSelect />
sub-component.
For proper alignment of options upon each selection or deselection, utilize the forceUpdate
property in <Combobox.VisualMultiSelect />
.
<Combobox> <Combobox.VisualMultiSelect>...</Combobox.VisualMultiSelect> <Combobox.Options> <Combobox.Option>...</Combobox.Option> </Combobox.Options> <Combobox.Hint>...</Combobox.Hint> </Combobox>
Without tracking the state of the input field
When the state of the input field changes, use `forceUpdate`.
"use client";
import React, { useCallback, useState } from "react";
import { Combobox, MenuItem } from "@heathmont/moon-core-tw";
import { ControlsChevronDownSmall } from "@heathmont/moon-icons-tw";
const people = [
{ id: 1, label: "Wade Cooper", value: "Wade Cooper" },
{ id: 2, label: "Arlene Mccoy", value: "Arlene Mccoy" },
{ id: 3, label: "Devon Webb", value: "Devon Webb" },
{ id: 4, label: "Tom Cook", value: "Tom Cook" },
{ id: 5, label: "Tanya Fox", value: "Tanya Fox" },
{ id: 6, label: "Hellen Schmidt", value: "Hellen Schmidt" },
];
const filter = (
query: string,
people: { id: number; label: string; value: string }[],
) => {
return query === ""
? people
: people.filter(({ value }) =>
value
.toLowerCase()
.replace(/\s+/g, "")
.includes(query.toLowerCase().replace(/\s+/g, "")),
);
};
const Example = () => {
const [selected1, setSelected1] = useState([]);
const [selected2, setSelected2] = useState([]);
const [query1, setQuery1] = useState<string>("");
const [query2, setQuery2] = useState<string>("");
const filteredPeople1 = filter(query1, people);
const filteredPeople2 = filter(query2, people);
const onRemoveItem1 = useCallback(
(index: unknown) => {
setSelected1(selected1.filter(({ id }) => id !== index));
},
[selected1],
);
const onRemoveItem2 = useCallback(
(index: unknown) => {
setSelected2(selected2.filter(({ id }) => id !== index));
},
[selected2],
);
return (
<div className="flex flex-col lg:flex-row lg:justify-center items-start w-full gap-4">
<Combobox
value={selected1}
onChange={setSelected1}
onQueryChange={setQuery1}
onClear={onRemoveItem1}
className="w-full max-w-xs"
multiple
>
{({ open }) => (
<>
<Combobox.VisualMultiSelect
open={open}
label=""
placeholder="Choose an option"
displayValue={({ label }) => label}
>
<ControlsChevronDownSmall />
</Combobox.VisualMultiSelect>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople1.length === 0 && query1 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople1.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem isActive={active} isSelected={selected}>
<MenuItem.Title>{person.label}</MenuItem.Title>
<MenuItem.Checkbox isSelected={selected} />
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>
Without tracking the state of the input field
</Combobox.Hint>
</>
)}
</Combobox>
<Combobox
value={selected2}
onChange={setSelected2}
onQueryChange={setQuery2}
onClear={onRemoveItem2}
className="w-full max-w-xs"
multiple
>
{({ open }) => (
<>
<Combobox.VisualMultiSelect
open={open}
label=""
placeholder="Choose an option"
displayValue={({ label }) => label}
forceUpdate
>
<ControlsChevronDownSmall />
</Combobox.VisualMultiSelect>
<Combobox.Transition>
<Combobox.Options>
{filteredPeople2.length === 0 && query2 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople2.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem isActive={active} isSelected={selected}>
<MenuItem.Title>{person.label}</MenuItem.Title>
<MenuItem.Checkbox isSelected={selected} />
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>
When the state of the input field changes, use `forceUpdate`.
</Combobox.Hint>
</>
)}
</Combobox>
</div>
);
};
export default Example;
Alignment of controls for options with long names
Control the width of <Combobox.Options />
using the menuWidth
property. The alignment of options with lengthy names is managed by default, as illustrated in the examples below.
Example with checkboxes
Example with radio buttons
"use client";
import { Combobox, MenuItem } from "@heathmont/moon-core-tw";
import { ControlsChevronDownSmall } from "@heathmont/moon-icons-tw";
import { useCallback, useState } from "react";
const books = [
{ id: 1, label: "To Kill a Mockingbird", value: "To Kill a Mockingbird" },
{ id: 2, label: "The Catcher in the Rye", value: "The Catcher in the Rye" },
{ id: 3, label: "The Spy", value: "The Spy" },
{
id: 4,
label: "The Pit and the Pendulum",
value: "The Pit and the Pendulum",
},
{ id: 5, label: "Dandelion Wine", value: "Dandelion Wine" },
{ id: 6, label: "The Old Man and the Sea", value: "The Old Man and the Sea" },
];
const filter = (
query: string,
people: { id: number; label: string; value: string }[],
) => {
return query === ""
? people
: people.filter(({ value }) =>
value
.toLowerCase()
.replace(/\s+/g, "")
.includes(query.toLowerCase().replace(/\s+/g, "")),
);
};
const Example = () => {
const [selected0, setSelected0] = useState([]);
const [selected1, setSelected1] = useState({});
const [query0, setQuery0] = useState<string>("");
const [query1, setQuery1] = useState<string>("");
const filteredPeople0 = filter(query0, books);
const filteredPeople1 = filter(query1, books);
return (
<div className="flex flex-col lg:flex-row lg:justify-center items-center w-full gap-4">
<Combobox
value={selected0}
onChange={setSelected0}
onQueryChange={setQuery0}
onClear={useCallback(() => setSelected0([]), [setSelected0])}
className="w-full max-w-xs"
multiple
>
{({ open }) => (
<>
<Combobox.MultiSelect
open={open}
counter={selected0.length}
placeholder="Choose an option"
displayValue={({ label }) => label}
onClose={console.log}
>
<ControlsChevronDownSmall />
</Combobox.MultiSelect>
<Combobox.Transition>
<Combobox.Options
menuWidth="w-48"
className="rounded-moon-s-md box-border bg-goku shadow-moon-lg py-2 px-1 my-2"
>
{filteredPeople0.length === 0 && query0 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople0.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem isActive={active} isSelected={selected}>
<MenuItem.Title>{person.label}</MenuItem.Title>
<MenuItem.Checkbox
isSelected={selected}
className="relative mx-1 top-auto"
/>
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>Example with checkboxes</Combobox.Hint>
</>
)}
</Combobox>
<Combobox
value={selected1}
onChange={setSelected1}
onQueryChange={setQuery1}
className="w-full max-w-xs"
>
{({ open }) => (
<>
<Combobox.Select
open={open}
placeholder="Choose an option"
displayValue={({ label }) => label}
>
<ControlsChevronDownSmall />
</Combobox.Select>
<Combobox.Transition>
<Combobox.Options
menuWidth="w-48"
className="rounded-moon-s-md box-border bg-goku shadow-moon-lg py-2 px-1 my-2"
>
{filteredPeople1.length === 0 && query1 !== "" ? (
<div className="relative cursor-default select-none py-2 px-4 text-trunks">
Nothing found.
</div>
) : (
filteredPeople1.map((person, index) => (
<Combobox.Option value={person} key={index}>
{({ selected, active }) => (
<MenuItem isActive={active} isSelected={selected}>
<MenuItem.Title>{person.label}</MenuItem.Title>
<MenuItem.Radio
isSelected={selected}
className="mx-1"
/>
</MenuItem>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox.Transition>
<Combobox.Hint>Example with radio buttons</Combobox.Hint>
</>
)}
</Combobox>
</div>
);
};
export default Example;
Combobox
These are props specific to the Combobox component:
Name | Type | Default |
---|---|---|
value* | "unknown" | - |
onChange* | (value: unknown) => void | - |
onQueryChange* | (value: unknown) => void | - |
onClear | enum | - |
isError | boolean | - |
disabled | boolean | - |
size | "sm" | "md" | "lg" | "xl" | string | md |
className | string | - |
position | enum | bottom |
open | boolean | - |
multiple | boolean | - |
nullable | boolean | - |
Properties indicated with * are required.
Combobox.Trigger
These are props specific to the Combobox.Trigger component:
Name | Type | Default |
---|---|---|
className | string | - |
onClose | (value: unknown) => void; | - |
Combobox.Input / Combobox.InsetInput / Combobox.VisualSelectInput
These are props specific to the Combobox.Input / Combobox.InsetInput / Combobox.VisualSelectInput component:
Name | Type | Default |
---|---|---|
onChange* | (value: unknown) => void | - |
onQueryChange* | (value: string) => void | - |
displayValue | (value: unknown) => string | - |
label | string | - |
placeholder | string | - |
type | enum | text |
className | string | - |
Properties indicated with * are required.
Combobox.Button
These are props specific to the Combobox.Button component:
Name | Type | Default |
---|---|---|
className | string | - |
open | boolean | - |
Combobox.Options
These are props specific to the Combobox.Options component:
Name | Type | Default |
---|---|---|
menuWidth | string | - |
className | string | - |
Combobox.Option
These are props specific to the Combobox.Option component:
Name | Type | Default |
---|---|---|
value | "unknown" | - |
active | boolean | - |
selected | boolean | - |
Combobox.Transition
These are props specific to the Combobox.Transition component:
Name | Type | Default |
---|---|---|
onQueryChange* | (value: string) => void | - |
Properties indicated with * are required.
Combobox.Select / Combobox.InsetSelect
These are props specific to the Combobox.Select / Combobox.InsetSelect component:
Name | Type | Default |
---|---|---|
onChange* | (value: unknown) => void | - |
onQueryChange* | (value: string) => void | - |
displayValue | (value: unknown) => string | - |
label | "JSX.Element" | string | - |
placeholder | "JSX.Element" | string | - |
className | string | - |
open | boolean | - |
onClose | (value: unknown) => void; | - |
Properties indicated with * are required.
Combobox.MultiSelect / Combobox.InsetMultiSelect / Combobox.VisualMultiSelect
These are props specific to the Combobox.MultiSelect / Combobox.InsetMultiSelect / Combobox.VisualMultiSelect component:
Name | Type | Default |
---|---|---|
onChange* | (value: unknown) => void | - |
onQueryChange* | (value: string) => void | - |
displayValue | (value: unknown) => string | - |
label | "JSX.Element" | string | - |
placeholder | "JSX.Element" | string | - |
className | string | - |
counter | "Number" | - |
open | boolean | - |
onClose | (value: unknown) => void; | - |
Properties indicated with * are required.
Combobox.VisualMultiSelect
These are props specific to the Combobox.VisualMultiSelect component:
Name | Type | Default |
---|---|---|
forceUpdate | boolean | - |
Combobox.Counter
These are props specific to the Combobox.Counter component:
Name | Type | Default |
---|---|---|
counter* | number | - |
className | string | - |
open | boolean | - |
Properties indicated with * are required.
Combobox.SelectedItem
These are props specific to the Combobox.SelectedItem component:
Name | Type | Default |
---|---|---|
index* | number | string | - |
label* | number | string | - |
className | string | - |
open | boolean | - |
Properties indicated with * are required.
Combobox.Hint
These are props specific to the Combobox.Hint component:
Name | Type | Default |
---|---|---|
className | boolean | - |