Creatable Select

A searchable select where people can add the option that is not in the list yet.

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

PropTypeDefault
options{ value, label, description?, disabled? }[]required
value / defaultValuestring or null, or string[] with multiplenone
onValueChange(value) => voidnone
multiplebooleanfalse
onCreate(text) => option | void | Promisetext becomes the option
validateNew(text) => booleannone
placeholderstringnone
namestringnone
disabled / readOnly / required / invalidbooleanfalse