Not in the list? Type the name and create it.
import { CreatableSelect, type CreatableSelectOption } from "@hel/ui/creatable-select"
import { Field, FieldHelperText, FieldLabel } from "@hel/ui/field"
When to use it#
Use a creatable select for a field whose list grows with use: customers, categories, tags. Most of the time the right option is already there, and when it isn't, people add it on the spot, with no trip to another screen.
It is built on the Combobox, and does the creating for you. When typing finds nothing, the list offers a row such as Create "Acme". If you need a different layout or a more custom footer, build it from Combobox parts yourself. If the value must be one of a fixed set, use a Combobox on its own.
Import#
import { CreatableSelect } from "@hel/ui/creatable-select"Options#
options is a list of { value, label }, with an optional description and disabled. The value is what your app stores or the form sends, and the label is what people read. The value of the select is the chosen option's value, as a string, or null when nothing is chosen.
What happens on create#
Without onCreate, the text that was typed becomes both the label and the value of the new option, and it is selected right away. That is fine for tags.
With onCreate, you decide. The function gets the text and returns the new option, or a promise of one, and the select adds it to its list and chooses it. Return nothing to add no option.
Examples#
The app keeps the list#
When the list belongs to your app, such as customers loaded from a server, keep it in state and add to it in onCreate. Use value and onValueChange to read the choice.
Stored value: acme
import * as React from "react"
import { CreatableSelect, type CreatableSelectOption } from "@hel/ui/creatable-select"Saving on a server#
onCreate can be async, so the option is saved on your server first, and the select adds and chooses the option it returns once the promise resolves. The demo waits 600ms to stand in for the request.
import { CreatableSelect, type CreatableSelectOption } from "@hel/ui/creatable-select"
const CATEGORIES: CreatableSelectOption[] = [Several at once#
multiple shows the choices as chips, and the value becomes an array of strings. Good for tags, where each person might add a few new ones.
import { CreatableSelect, type CreatableSelectOption } from "@hel/ui/creatable-select"
const TAGS: CreatableSelectOption[] = [Rules for new options#
validateNew gets the typed text and returns false when it must not be offered as a new option, like a name that is too short. The create row is not shown for it.
import { CreatableSelect, type CreatableSelectOption } from "@hel/ui/creatable-select"
const TAGS: CreatableSelectOption[] = [In a form#
Give the select a name and it sends its value with the form. A multiple select sends each value on its own. Put it inside a Field with a label, as in the first example, or give it an aria-label, so the input has a name. disabled, readOnly, required and invalid work as on any field.
Labels#
The create row, the empty message and the chip remove buttons have English text. Add your own under messages in the Hel config, with the key creatableSelect. create and remove are functions that get the typed text or the label, so you can write them in any language.
Props#
| Prop | Type | Default |
|---|---|---|
options | { value, label, description?, disabled? }[] | required |
value / defaultValue | string or null, or string[] with multiple | none |
onValueChange | (value) => void | none |
multiple | boolean | false |
onCreate | (text) => option | void | Promise | text becomes the option |
validateNew | (text) => boolean | none |
placeholder | string | none |
name | string | none |
disabled / readOnly / required / invalid | boolean | false |