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.
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} />;}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/select';
@Component({ selector: 'app-select-basic', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: `<gui-select label="Country" [options]="countries"></gui-select>`,})export class SelectBasicComponent { countries = ['Portugal', 'Spain', 'France', 'Italy'];}<script setup lang="ts">import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const countries = ['Portugal', 'Spain', 'France', 'Italy'];</script>
<template> <gui-select label="Country" :options.prop="countries"></gui-select></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const countries = ['Portugal', 'Spain', 'France', 'Italy'];
@customElement('select-basic')export class SelectBasic extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-select label="Country" .options=${countries}></gui-select> `; }}<gui-select label="Country" options='["Portugal", "Spain", "France", "Italy"]'></gui-select>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.
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 ?? '')} /> );}Add CUSTOM_ELEMENTS_SCHEMA to the component that uses the tags, and the stylesheet to styles in angular.json.
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { GuiEventMap, GuiSelectEvents } from '@golemui/gui-components';import '@golemui/gui-components/select';
type ChangeEvent = GuiEventMap<typeof GuiSelectEvents>['gui-change'];
@Component({ selector: 'app-country-field', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-select label="Country" name="country" [options]="countries" [value]="country" (gui-change)="onChange($event)" ></gui-select> `,})export class CountryFieldComponent { countries = ['Portugal', 'Spain', 'France'];
country = '';
onChange(event: Event) { this.country = (event as ChangeEvent).detail.value ?? ''; }}"styles": ["node_modules/@golemui/gui-components/lib/styles/index.css"]Tell Vue's compiler that gui-* tags are custom elements, and add the typings once for typed props and @gui-* handlers.
<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const countries = ['Portugal', 'Spain', 'France'];
const country = ref('');</script>
<template> <gui-select label="Country" name="country" :options.prop="countries" :value="country" @gui-change="country = $event.detail.value ?? ''" /></template>vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('gui-') } },});import type {} from '@golemui/gui-components/vue';The page's stylesheet styles the gui-* elements, so it only reaches them outside a shadow root. Render the host component into the light DOM too, as below, or load the stylesheet into its shadow root.
import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const countries = ['Portugal', 'Spain', 'France'];
@customElement('country-field')export class CountryField extends LitElement { @state() private country = '';
protected override createRenderRoot() { return this; }
override render() { return html` <gui-select label="Country" name="country" .options=${countries} .value=${this.country} @gui-change=${(event: CustomEvent) => (this.country = event.detail.value)} ></gui-select> `; }}With any bundler, such as Vite. Import the element once, anywhere, and use the tag in your markup.
<gui-select label="Country" name="country"></gui-select>
<script type="module" src="./main.js"></script>import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const element = document.querySelector('gui-select');element.options = ['Portugal', 'Spain', 'France'];
element.addEventListener('gui-change', (event) => { console.log(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. |
Examples
Section titled “Examples”Placeholder
Section titled “Placeholder”Until the user picks an option, the select shows a first entry that can’t be picked: “Select an
option”. placeholder replaces its text.
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} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/select';
@Component({ selector: 'app-select-placeholder', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-select label="Language" placeholder="Choose a language" [options]="languages" ></gui-select> `,})export class SelectPlaceholderComponent { languages = ['English', 'Español', 'Français', 'Português'];}<script setup lang="ts">import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const languages = ['English', 'Español', 'Français', 'Português'];</script>
<template> <gui-select label="Language" placeholder="Choose a language" :options.prop="languages" ></gui-select></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const languages = ['English', 'Español', 'Français', 'Português'];
@customElement('select-placeholder')export class SelectPlaceholder extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-select label="Language" placeholder="Choose a language" .options=${languages} ></gui-select> `; }}<gui-select label="Language" placeholder="Choose a language" options='["English", "Español", "Français", "Português"]'></gui-select>Options
Section titled “Options”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.
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> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/select';
@Component({ selector: 'app-select-options', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-select label="Plan" value="team" [options]="plans"></gui-select> </div> <div class="gui-grid__cell"> <gui-select label="Time zone" label-field="city" value-field="zone" [options]="timeZones" ></gui-select> </div> </div> `,})export class SelectOptionsComponent { plans = [ { label: 'Free', value: 'free' }, { label: 'Team', value: 'team' }, { label: 'Enterprise', value: 'enterprise' }, ];
timeZones = [ { city: 'Lisbon', zone: 'Europe/Lisbon' }, { city: 'New York', zone: 'America/New_York' }, { city: 'Tokyo', zone: 'Asia/Tokyo' }, ];}<script setup lang="ts">import type { Option } from '@golemui/gui-components';import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const plans: Option[] = [ { 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' },];</script>
<template> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-select label="Plan" value="team" :options.prop="plans"></gui-select> </div> <div class="gui-grid__cell"> <gui-select label="Time zone" label-field="city" value-field="zone" :options.prop="zones" ></gui-select> </div> </div></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const plans = [ { label: 'Free', value: 'free' }, { label: 'Team', value: 'team' }, { label: 'Enterprise', value: 'enterprise' },];const timeZones = [ { city: 'Lisbon', zone: 'Europe/Lisbon' }, { city: 'New York', zone: 'America/New_York' }, { city: 'Tokyo', zone: 'Asia/Tokyo' },];
@customElement('select-options')export class SelectOptions extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-select label="Plan" value="team" .options=${plans}></gui-select> </div> <div class="gui-grid__cell"> <gui-select label="Time zone" label-field="city" value-field="zone" .options=${timeZones} ></gui-select> </div> </div> `; }}<div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-select label="Plan" value="team" options='[ { "label": "Free", "value": "free" }, { "label": "Team", "value": "team" }, { "label": "Enterprise", "value": "enterprise" } ]' ></gui-select> </div> <div class="gui-grid__cell"> <gui-select label="Time zone" label-field="city" value-field="zone" options='[ { "city": "Lisbon", "zone": "Europe/Lisbon" }, { "city": "New York", "zone": "America/New_York" }, { "city": "Tokyo", "zone": "Asia/Tokyo" } ]' ></gui-select> </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.
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> </> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, ViewEncapsulation,} from '@angular/core';import '@golemui/gui-components/select';
@Component({ selector: 'app-select-icon', schemas: [CUSTOM_ELEMENTS_SCHEMA], encapsulation: ViewEncapsulation.None, template: ` <gui-select label="Office" icon="icon-place" [options]="offices" ></gui-select> `, styles: ` /* Any icon font works: icon sets these classes on the icon element. This one draws a Material Icons ligature. */ .icon-place::before { content: 'place'; font-family: 'Material Icons'; } `,})export class SelectIconComponent { offices = ['Lisbon', 'Berlin', 'Toronto'];}<script setup lang="ts">import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const offices = ['Lisbon', 'Berlin', 'Toronto'];</script>
<template> <gui-select label="Office" icon="icon-place" :options.prop="offices" ></gui-select></template>
<style>/* Any icon font works: icon sets these classes on the icon element. This one draws a Material Icons ligature. */.icon-place::before { content: 'place'; font-family: 'Material Icons';}</style>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const offices = ['Lisbon', 'Berlin', 'Toronto'];
@customElement('select-icon')export class SelectIcon extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-select label="Office" icon="icon-place" .options=${offices} ></gui-select>
<style> /* Any icon font works: icon sets these classes on the icon element. This one draws a Material Icons ligature. */ .icon-place::before { content: 'place'; font-family: 'Material Icons'; } </style> `; }}<gui-select label="Office" icon="icon-place" options='["Lisbon", "Berlin", "Toronto"]'></gui-select>
<style> /* Any icon font works: icon sets these classes on the icon element. This one draws a Material Icons ligature. */ .icon-place::before { content: 'place'; font-family: 'Material Icons'; }</style>A value with no option
Section titled “A value with no option”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.
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([]); }} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { GuiEventMap, GuiSelectEvents } from '@golemui/gui-components';import '@golemui/gui-components/select';
type SelectEvent<K extends keyof typeof GuiSelectEvents> = GuiEventMap< typeof GuiSelectEvents>[K];
@Component({ selector: 'app-select-invalid-option', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-select id="warehouse-select" label="Warehouse" [options]="warehouses" [value]="warehouse" [errors]="errors()" (gui-input-error)="onInputError($event)" (gui-change)="errors.set([])" ></gui-select> `,})export class SelectInvalidOptionComponent { warehouses = ['Lisbon', 'Rotterdam', 'Hamburg']; // A value saved before the Antwerp warehouse closed. warehouse = 'Antwerp'; errors = signal<string[]>([]);
// The value matches no option, or, with an empty message, matches one again. onInputError(event: Event) { const { message } = (event as SelectEvent<'gui-input-error'>).detail; this.errors.set(message ? [message] : []); }}<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const warehouses = ['Lisbon', 'Rotterdam', 'Hamburg'];const errors = ref<string[]>([]);</script>
<template> <!-- value: a value saved before the Antwerp warehouse closed. --> <gui-select id="warehouse-select" label="Warehouse" value="Antwerp" :options.prop="warehouses" :errors.prop="errors" @gui-input-error=" errors = $event.detail.message ? [$event.detail.message] : [] " @gui-change="errors = []" ></gui-select></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { GuiEventMap, GuiSelectEvents } from '@golemui/gui-components';import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
type SelectEvent<K extends keyof typeof GuiSelectEvents> = GuiEventMap< typeof GuiSelectEvents>[K];
const warehouses = ['Lisbon', 'Rotterdam', 'Hamburg'];
// A value saved before the Antwerp warehouse closed.const savedWarehouse = 'Antwerp';
@customElement('select-invalid-option')export class SelectInvalidOption extends LitElement { @state() private errors: string[] = [];
protected override createRenderRoot() { return this; }
// The value matches no option, or, with an empty message, matches one again. private onInputError(event: SelectEvent<'gui-input-error'>) { this.errors = event.detail.message ? [event.detail.message] : []; }
// The user picked a valid option. private onChange() { this.errors = []; }
override render() { return html` <gui-select id="warehouse-select" label="Warehouse" .options=${warehouses} .value=${savedWarehouse} .errors=${this.errors} @gui-input-error=${this.onInputError} @gui-change=${this.onChange} ></gui-select> `; }}<gui-select id="warehouse-select" label="Warehouse" options='["Lisbon", "Rotterdam", "Hamburg"]'></gui-select>
<script type="module"> const warehouse = document.querySelector('#warehouse-select');
// The value matches no option, or, with an empty message, matches one again. warehouse.addEventListener('gui-input-error', (event) => { warehouse.errors = event.detail.message ? [event.detail.message] : []; });
// The user picked a valid option. warehouse.addEventListener('gui-change', () => { warehouse.errors = []; });
// A value saved before the Antwerp warehouse closed. warehouse.value = 'Antwerp';</script>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.
Disabled and read-only
Section titled “Disabled and read-only”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.
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> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/select';
@Component({ selector: 'app-select-disabled-readonly', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-select label="Currency" value="EUR" [options]="currencies" disabled ></gui-select> </div> <div class="gui-grid__cell"> <gui-select label="Workspace" value="Acme" [options]="workspaces" readonly ></gui-select> </div> </div> `,})export class SelectDisabledReadonlyComponent { currencies = ['EUR', 'USD', 'GBP']; workspaces = ['Acme', 'Globex'];}<script setup lang="ts">import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const currencies = ['EUR', 'USD', 'GBP'];const workspaces = ['Acme', 'Globex'];</script>
<template> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-select label="Currency" value="EUR" :options.prop="currencies" :disabled="true" ></gui-select> </div> <div class="gui-grid__cell"> <gui-select label="Workspace" value="Acme" :options.prop="workspaces" :readOnly="true" ></gui-select> </div> </div></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const currencies = ['EUR', 'USD', 'GBP'];const workspaces = ['Acme', 'Globex'];
@customElement('select-disabled-readonly')export class SelectDisabledReadonly extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-select label="Currency" value="EUR" .options=${currencies} disabled ></gui-select> </div> <div class="gui-grid__cell"> <gui-select label="Workspace" value="Acme" .options=${workspaces} readonly ></gui-select> </div> </div> `; }}<div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-select label="Currency" value="EUR" options='["EUR", "USD", "GBP"]' disabled></gui-select> </div> <div class="gui-grid__cell"> <gui-select label="Workspace" value="Acme" options='["Acme", "Globex"]' readonly></gui-select> </div></div>In a form
Section titled “In a form”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.
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> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import '@golemui/gui-components/button';import '@golemui/gui-components/select';
@Component({ selector: 'app-select-native-form', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <form id="shipping" #form (submit)="onSubmit($event, form)"> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-select label="Ship to" name="country" placeholder="Choose a country" [options]="countries" required ></gui-select> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Continue"></gui-button> </div> </div> <p id="shipping-result" role="status">{{ result() }}</p> </form> `,})export class SelectNativeFormComponent { countries = ['Portugal', 'Spain', 'France', 'Italy']; result = signal('');
onSubmit(event: SubmitEvent, form: HTMLFormElement) { event.preventDefault(); const data = Object.fromEntries(new FormData(form)); this.result.set(`Submitted ${JSON.stringify(data)}`); }}<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/button';import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const countries = ['Portugal', 'Spain', 'France', 'Italy'];const result = ref('');
function onSubmit(event: SubmitEvent) { if (!(event.currentTarget instanceof HTMLFormElement)) return; const data = Object.fromEntries(new FormData(event.currentTarget)); result.value = `Submitted ${JSON.stringify(data)}`;}</script>
<template> <form id="shipping" @submit.prevent="onSubmit"> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-select label="Ship to" name="country" placeholder="Choose a country" :options.prop="countries" :required="true" ></gui-select> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Continue"></gui-button> </div> </div> <p id="shipping-result" role="status">{{ result }}</p> </form></template>import { LitElement, html } from 'lit';import { customElement, query, state } from 'lit/decorators.js';import '@golemui/gui-components/button';import '@golemui/gui-components/select';import '@golemui/gui-components/index.css';
const countries = ['Portugal', 'Spain', 'France', 'Italy'];
@customElement('select-native-form')export class SelectNativeForm extends LitElement { @query('#shipping') private form!: HTMLFormElement; @state() private result = '';
protected override createRenderRoot() { return this; }
private onSubmit(event: SubmitEvent) { event.preventDefault(); const data = Object.fromEntries(new FormData(this.form)); this.result = `Submitted ${JSON.stringify(data)}`; }
override render() { return html` <form id="shipping" @submit=${this.onSubmit}> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-select label="Ship to" name="country" placeholder="Choose a country" .options=${countries} required ></gui-select> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Continue"></gui-button> </div> </div> <p id="shipping-result" role="status">${this.result}</p> </form> `; }}<form id="shipping"> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-select label="Ship to" name="country" placeholder="Choose a country" options='["Portugal", "Spain", "France", "Italy"]' required ></gui-select> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Continue"></gui-button> </div> </div> <p id="shipping-result" role="status"></p></form>
<script type="module"> const form = document.querySelector('#shipping');
form.addEventListener('submit', (event) => { event.preventDefault(); const data = Object.fromEntries(new FormData(form)); document.querySelector('#shipping-result').textContent = `Submitted ${JSON.stringify(data)}`; });</script>How the elements submit, validate and reset is in Native forms.
Styling
Section titled “Styling”Anatomy
Section titled “Anatomy”<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 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>| Class | Part |
|---|---|
gui-field | The element itself. Lines its parts up with the fields next to it in a gui-grid row. |
gui-label | The label, with the text and the hint. |
gui-label__text | The label text and the required marker. |
gui-widget-hint | The hint. |
gui-widget | The box around the control. |
gui-widget-input | The <select>, the part that looks like a field. |
gui-select--icon | On the <select> when there is an icon: it makes room for it. |
gui-select__arrow | The box of the arrow, at the end of the field. |
gui-caret | The arrow. See Carets. |
gui-widget-icon | The icon, with your icon classes. |
gui-validator | The errors container. It is always there, so the alert role announces new errors. |
gui-validator__error | One 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.
Tokens
Section titled “Tokens”The select reads these design tokens. Set them on :root to change every select, or on any
element to change the selects inside it:
| Token | What it styles |
|---|---|
--gui-widget-size | The height of the field, and the icon’s box. |
--gui-radius-md | The corner radius. |
--gui-bg-surface | The field’s background. |
--gui-border-default | The border. |
--gui-text-default | The text, the label, the arrow and the icon. |
--gui-font-family | The font. |
--gui-font-sm | The text size of the label and the field. |
--gui-font-xs | The text size of the hint and the errors. |
--gui-text-secondary | The hint. |
--gui-space-3 | The space between the arrow and the field’s end. |
--gui-border-focus | The border while focused. |
--gui-shadow-focus | The focus ring. |
--gui-intent-error | The border and the messages when there are errors. |
--gui-shadow-focus-error | The focus ring when there are errors. |
--gui-bg-disabled | The background when disabled. |
--gui-text-disabled | The text when disabled. |
--gui-space-1 | The 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.
/* 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.
Accessibility
Section titled “Accessibility”- Name. The
labelnames the select. Without one the select has no accessible name. - Description. The
hintis the select’s description, througharia-describedby. - Errors. While errors show, the select has
aria-invalid="true"and points at them witharia-errormessage, and screen readers announce new errors as they appear. - Required and read-only.
requiredsetsaria-requiredandreadonlysetsaria-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.
| Key | Action |
|---|---|
| Tab | Moves focus to the select, and away from it. |
| Every other key | Works 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.