Skip to content
Components, switch product

Select

<gui-select> picks one option from a list. It renders a native <select>, so the list opens the way the browser and the platform open it, with its label, hint and errors connected for assistive technology.

For a long list, or one the user searches by typing, use gui-dropdown.

Code of the example above
SelectBasic.tsx
import { GuiSelect } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
const countries = ['Portugal', 'Spain', 'France', 'Italy'];
export function SelectBasic() {
return <GuiSelect label="Country" options={countries} />;
}

The element’s entry point is @golemui/gui-components/select, which registers <gui-select> and nothing else. To install the package and load the stylesheet, see Installation.

The @golemui/gui-components/react components have typed props and an on* prop for each event. React 18 or later.

CountryField.tsx
import { useState } from 'react';
import { GuiSelect } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
const countries = ['Portugal', 'Spain', 'France'];
export function CountryField() {
const [country, setCountry] = useState('');
return (
<GuiSelect
label="Country"
name="country"
options={countries}
value={country}
onGuiChange={(event) => setCountry(event.detail.value ?? '')}
/>
);
}

In HTML, options can also be a JSON attribute, as in the examples below.

Properties

Property Type Default Description
localeId attribute locale-id string 'en' BCP 47 locale for formatting and parsing, such as en-US or es.
value OptionValue – The value of the selected option.
icon string – Icon class name shown inside the control, for example from an icon font.
autocomplete string – The autocomplete hint passed to the inner native control.
options OptionInput[] [] The options: values, which are both the text and the value, { label, value } objects, or any objects read through label-field and value-field.
placeholder string – Text shown while the control is empty.
invalidOptionMessage attribute invalid-option-message string – Error when value matches no option. {value} is the value.
labelField attribute label-field string – For options given as objects, the key of the text to show.
valueField attribute value-field string – For options given as objects, the key of the value.
uid string – The id of the element's control. The ids of its label, hint and errors derive from it. Optional: when it is not set, the element generates one from its tag name and a page-wide counter, e.g. gui-textinput-3. Generated ids depend on creation order, so a server-rendered element must set uid or its ids will not match after hydration.

Form control properties

Every field has these, from the base class of the form controls.

Property Type Default Description
name string – The name the value is submitted under. Without it the element stays out of its form. Reflected, like a native control's: the form reads a single value's name from the attribute.
label string – The visible label, which also names the control for assistive technology. Without one the control has no accessible name; the date, time, range, tags and file fields warn about it in development builds.
hint string – Help text shown under the label and announced as the control's description.
errors string[] [] Error messages to show, typically from your own validation. They show as soon as they are set, unless touched is false.
touched boolean – Whether the user has interacted with the control. Leave it unset unless you validate on interaction: false holds errors back until it becomes true.
required boolean false The control needs a value: it is marked as required and, with a name, blocks submission.
readOnly attribute readonly boolean – The value can be read and focused but not changed.
disabled boolean – Disabled by its own attribute or by a disabled <fieldset> around it. Setting the property sets the attribute, like a native control's.
form read-only, no attribute HTMLFormElement | null – The <form> the element belongs to, if any.
validity read-only, no attribute ValidityState – The element's validity, like a native control's.
validationMessage read-only, no attribute string – The message the browser shows for an invalid value.

Methods

Method Description
checkValidity(): boolean –
reportValidity(): boolean –

Events

Every event bubbles and is composed, so you can listen on any ancestor.

Event Description
gui-input The user changed the value. detail.value is the new value.
gui-change The user committed the value. detail.value is the committed value.
gui-blur Focus left the control.
gui-input-error The element rejected what the user entered, such as an impossible date or a value out of bounds. detail.message is the error; show it through errors.

Until the user picks an option, the select shows a first entry that can’t be picked: “Select an option”. placeholder replaces its text.

Code of the example above
SelectPlaceholder.tsx
import { GuiSelect } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
const languages = ['English', 'Español', 'Français', 'Português'];
export function SelectPlaceholder() {
return (
<GuiSelect
label="Language"
placeholder="Choose a language"
options={languages}
/>
);
}

options takes the list of options. Each one is a string or a number, which is both its text and its value, or a { label, value } object, to show a text other than the value. When your options are objects with other keys, label-field names the key of the text and value-field the key of the value.

value is the value of the selected option. An attribute is always text, so to select an option whose value is a number, set the value property.

Code of the example above
SelectOptions.tsx
import { GuiSelect } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
const plans = [
{ label: 'Free', value: 'free' },
{ label: 'Team', value: 'team' },
{ label: 'Enterprise', value: 'enterprise' },
];
const zones = [
{ city: 'Lisbon', zone: 'Europe/Lisbon' },
{ city: 'New York', zone: 'America/New_York' },
{ city: 'Tokyo', zone: 'Asia/Tokyo' },
];
export function SelectOptions() {
return (
<div className="gui-grid gui-grid--row">
<div className="gui-grid__cell">
<GuiSelect label="Plan" value="team" options={plans} />
</div>
<div className="gui-grid__cell">
<GuiSelect
label="Time zone"
labelField="city"
valueField="zone"
options={zones}
/>
</div>
</div>
);
}

icon takes the class names of an icon and shows it at the start of the field. The icon is decorative and hidden from assistive technology. See Icons for the ways to draw it.

Code of the example above
SelectIcon.tsx
import { GuiSelect } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
const offices = ['Lisbon', 'Berlin', 'Toronto'];
// Any icon font works: icon sets these classes on the icon element.
// This one draws a Material Icons ligature.
const iconStyles = `
.icon-place::before {
content: 'place';
font-family: 'Material Icons';
}
`;
export function SelectIcon() {
return (
<>
<GuiSelect label="Office" icon="icon-place" options={offices} />
<style>{iconStyles}</style>
</>
);
}

When value matches none of the options, for example a value saved before an option was removed, the select shows its placeholder and fires gui-input-error with a message. The select doesn’t show the message itself: put it in errors. It fires again with an empty message when new options make the value valid. Picking an option fires gui-change instead, so clear the error there too.

Code of the example above
SelectInvalidOption.tsx
import { useState } from 'react';
import { GuiSelect } from '@golemui/gui-components/react';
import type { OptionValue } from '@golemui/gui-components';
import '@golemui/gui-components/index.css';
const warehouses = ['Lisbon', 'Rotterdam', 'Hamburg'];
export function SelectInvalidOption() {
// A value saved before the Antwerp warehouse closed.
const [warehouse, setWarehouse] = useState<OptionValue | undefined>(
'Antwerp',
);
const [errors, setErrors] = useState<string[]>([]);
return (
<GuiSelect
id="warehouse-select"
label="Warehouse"
options={warehouses}
value={warehouse}
errors={errors}
// The value matches no option, or, with an empty message, matches one again.
onGuiInputError={(event) =>
setErrors(event.detail.message ? [event.detail.message] : [])
}
// The user picked a valid option.
onGuiChange={(event) => {
setWarehouse(event.detail.value);
setErrors([]);
}}
/>
);
}

While the options are still empty, for example while they load, no value is invalid. The default message is Invalid selection: '{value}' is not a valid option., and invalid-option-message replaces it on one select, with {value} standing for the value. To translate it everywhere, see i18n.

A disabled select can’t be focused or changed, and a form doesn’t submit it. A readonly select can be focused, but it doesn’t open and ignores every key but Tab, and a form submits its value.

Code of the example above
SelectDisabledReadonly.tsx
import { GuiSelect } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function SelectDisabledReadonly() {
return (
<div className="gui-grid gui-grid--row">
<div className="gui-grid__cell">
<GuiSelect
label="Currency"
value="EUR"
options={['EUR', 'USD', 'GBP']}
disabled
/>
</div>
<div className="gui-grid__cell">
<GuiSelect
label="Workspace"
value="Acme"
options={['Acme', 'Globex']}
readOnly
/>
</div>
</div>
);
}

With a name, <gui-select> submits the value of the selected option under that name, and nothing while none is selected. required blocks the submit until the user picks one. Submit the form below, then pick a country and submit again.

Code of the example above
SelectNativeForm.tsx
import { useState, type SubmitEvent } from 'react';
import { GuiButton, GuiSelect } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
const countries = ['Portugal', 'Spain', 'France', 'Italy'];
export function SelectNativeForm() {
const [result, setResult] = useState('');
const onSubmit = (event: SubmitEvent<HTMLFormElement>) => {
event.preventDefault();
const data = Object.fromEntries(new FormData(event.currentTarget));
setResult(`Submitted ${JSON.stringify(data)}`);
};
return (
<form id="shipping" onSubmit={onSubmit}>
<div className="gui-grid gui-grid--row">
<div className="gui-grid__cell gui-grid__cell--span-3">
<GuiSelect
label="Ship to"
name="country"
placeholder="Choose a country"
options={countries}
required
/>
</div>
<div className="gui-grid__cell">
<GuiButton type="submit" label="Continue" />
</div>
</div>
<p id="shipping-result" role="status">
{result}
</p>
</form>
);
}

How the elements submit, validate and reset is in Native forms.

<gui-select> renders into the light DOM, so you can style every part with plain CSS. These are the parts and their public classes:

gui-select
<gui-select class="gui-field">
<label class="gui-label" for="country" id="country_label">
<span class="gui-label__text">
Country<span aria-hidden="true"> *</span>
</span>
<div class="gui-widget-hint" id="country_hint" aria-hidden="true">
Where we ship your order.
</div>
</label>
<div class="gui-widget">
<select
id="country"
class="gui-widget-input gui-select--icon"
aria-describedby="country_hint"
aria-invalid="true"
aria-errormessage="country_errors"
aria-required="true"
>
<option value="" disabled>Select an option</option>
<option value="Portugal">Portugal</option>
<option value="Spain">Spain</option>
<!-- One option per item of options. -->
</select>
<span class="gui-select__arrow">
<span class="gui-caret" aria-hidden="true"></span>
</span>
<span
class="gui-widget-icon icon-place"
data-icon="icon-place"
aria-hidden="true"
></span>
</div>
<div class="gui-validator" id="country_errors" role="alert">
<div class="gui-validator__error">Choose a country.</div>
</div>
</gui-select>
ClassPart
gui-fieldThe element itself. Lines its parts up with the fields next to it in a gui-grid row.
gui-labelThe label, with the text and the hint.
gui-label__textThe label text and the required marker.
gui-widget-hintThe hint.
gui-widgetThe box around the control.
gui-widget-inputThe <select>, the part that looks like a field.
gui-select--iconOn the <select> when there is an icon: it makes room for it.
gui-select__arrowThe box of the arrow, at the end of the field.
gui-caretThe arrow. See Carets.
gui-widget-iconThe icon, with your icon classes.
gui-validatorThe errors container. It is always there, so the alert role announces new errors.
gui-validator__errorOne error message.

The ids derive from uid, or from a generated id such as gui-select-3 when you don’t set one. Select by class or by tag, not by id.

The select reads these design tokens. Set them on :root to change every select, or on any element to change the selects inside it:

TokenWhat it styles
--gui-widget-sizeThe height of the field, and the icon’s box.
--gui-radius-mdThe corner radius.
--gui-bg-surfaceThe field’s background.
--gui-border-defaultThe border.
--gui-text-defaultThe text, the label, the arrow and the icon.
--gui-font-familyThe font.
--gui-font-smThe text size of the label and the field.
--gui-font-xsThe text size of the hint and the errors.
--gui-text-secondaryThe hint.
--gui-space-3The space between the arrow and the field’s end.
--gui-border-focusThe border while focused.
--gui-shadow-focusThe focus ring.
--gui-intent-errorThe border and the messages when there are errors.
--gui-shadow-focus-errorThe focus ring when there are errors.
--gui-bg-disabledThe background when disabled.
--gui-text-disabledThe text when disabled.
--gui-space-1The space between the label, hint and errors.

The arrow is a caret: --gui-caret-shape and --gui-caret-image change it, with every other caret. See Carets.

styles.css
/* Taller, rounder fields everywhere. */
:root {
--gui-widget-size: 48px;
--gui-radius-md: 12px;
}

The open list of options is the browser’s own, so these tokens don’t style it.

To restyle beyond the tokens, see Overriding styles; for dark mode and the Clay theme, see Themes and dark mode.

  • Name. The label names the select. Without one the select has no accessible name.
  • Description. The hint is the select’s description, through aria-describedby.
  • Errors. While errors show, the select has aria-invalid="true" and points at them with aria-errormessage, and screen readers announce new errors as they appear.
  • Required and read-only. required sets aria-required and readonly sets aria-readonly; the visual marker is hidden from screen readers.
  • Placeholder. The first entry, “Select an option” or your placeholder, is a disabled option, so the user can’t pick it back once they have picked a value.
KeyAction
TabMoves focus to the select, and away from it.
Every other keyWorks as in any native select: opens the list, moves through the options and picks one, which fires gui-change.

A read-only select ignores every key but Tab.

What every element shares, such as translating the browser messages, is in Accessibility.