Data input
Select
Choose one option from a list: the browser's native select, or a custom Select with a styled list.
| Class name | Type | Description |
|---|---|---|
selectplf:select | Component | A native select |
select-triggerplf:select-trigger | Part | The button the script adds after a custom select |
select-valueplf:select-value | Part | The chosen label the script adds |
select-chevronplf:select-chevron | Part | The trigger's chevron the script adds |
select-popupplf:select-popup | Part | The option list the script adds |
select-groupplf:select-group | Part | An option group the script adds |
select-group-labelplf:select-group-label | Part | A group's label the script adds |
select-optionplf:select-option | Part | An option the script adds |
select-labelplf:select-label | Part | An option's label the script adds |
select-checkplf:select-check | Part | An option's check mark the script adds |
select-statusplf:select-status | Part | The screen reader status the script adds |
select-smplf:select-sm | Size | Small |
select-lgplf:select-lg | Size | Large |
<select class="select" aria-label="Example select">
<option>Choose an option</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select><select class="plf:select" aria-label="Example select">
<option>Choose an option</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>import { Select } from '@pelatform/react';
export function Example() {
return (
<Select aria-label="Example select">
<option>Choose an option</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</Select>
);
}import { Select } from '@pelatform/react/prefixed';
export function Example() {
return (
<Select aria-label="Example select">
<option>Choose an option</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</Select>
);
}<select class="select select-sm" aria-label="Small select">
<option>Small</option>
</select>
<select class="select" aria-label="Default select">
<option>Default</option>
</select>
<select class="select select-lg" aria-label="Large select">
<option>Large</option>
</select><select class="plf:select plf:select-sm" aria-label="Small select">
<option>Small</option>
</select>
<select class="plf:select" aria-label="Default select">
<option>Default</option>
</select>
<select class="plf:select plf:select-lg" aria-label="Large select">
<option>Large</option>
</select>import { Select } from '@pelatform/react';
export function Example() {
return (
<>
<Select size="sm" aria-label="Small select">
<option>Small</option>
</Select>
<Select aria-label="Default select">
<option>Default</option>
</Select>
<Select size="lg" aria-label="Large select">
<option>Large</option>
</Select>
</>
);
}import { Select } from '@pelatform/react/prefixed';
export function Example() {
return (
<>
<Select size="sm" aria-label="Small select">
<option>Small</option>
</Select>
<Select aria-label="Default select">
<option>Default</option>
</Select>
<Select size="lg" aria-label="Large select">
<option>Large</option>
</Select>
</>
);
}<select class="select" aria-label="Disabled select" disabled>
<option>Disabled</option>
</select><select class="plf:select" aria-label="Disabled select" disabled>
<option>Disabled</option>
</select>import { Select } from '@pelatform/react';
export function Example() {
return (
<Select aria-label="Disabled select" disabled>
<option>Disabled</option>
</Select>
);
}import { Select } from '@pelatform/react/prefixed';
export function Example() {
return (
<Select aria-label="Disabled select" disabled>
<option>Disabled</option>
</Select>
);
}<div class="field">
<label class="field-label" for="select-status">Status</label>
<select class="select" id="select-status" name="status" data-plf-select>
<option value="draft">Draft</option>
<option value="published">Published</option>
<option value="archived" disabled>Archived</option>
</select>
</div><div class="plf:field">
<label class="plf:field-label" for="select-status">Status</label>
<select class="plf:select" id="select-status" name="status" data-plf-select>
<option value="draft">Draft</option>
<option value="published">Published</option>
<option value="archived" disabled>Archived</option>
</select>
</div>import { Field, Select } from '@pelatform/react';
export function Example() {
return (
<Field label="Status">
<Select custom name="status">
<option value="draft">Draft</option>
<option value="published">Published</option>
<option value="archived" disabled>Archived</option>
</Select>
</Field>
);
}import { Field, Select } from '@pelatform/react/prefixed';
export function Example() {
return (
<Field label="Status">
<Select custom name="status">
<option value="draft">Draft</option>
<option value="published">Published</option>
<option value="archived" disabled>Archived</option>
</Select>
</Field>
);
}<div class="field">
<label class="field-label" for="select-category">Category</label>
<select class="select" id="select-category" name="category" data-plf-select>
<optgroup label="Content">
<option value="articles">Articles</option>
<option value="guides">Guides</option>
</optgroup>
<optgroup label="Admin">
<option value="reports">Reports</option>
<option value="users">Users</option>
</optgroup>
</select>
</div><div class="plf:field">
<label class="plf:field-label" for="select-category">Category</label>
<select class="plf:select" id="select-category" name="category" data-plf-select>
<optgroup label="Content">
<option value="articles">Articles</option>
<option value="guides">Guides</option>
</optgroup>
<optgroup label="Admin">
<option value="reports">Reports</option>
<option value="users">Users</option>
</optgroup>
</select>
</div>import { Field, Select } from '@pelatform/react';
export function Example() {
return (
<Field label="Category">
<Select custom name="category">
<optgroup label="Content">
<option value="articles">Articles</option>
<option value="guides">Guides</option>
</optgroup>
<optgroup label="Admin">
<option value="reports">Reports</option>
<option value="users">Users</option>
</optgroup>
</Select>
</Field>
);
}import { Field, Select } from '@pelatform/react/prefixed';
export function Example() {
return (
<Field label="Category">
<Select custom name="category">
<optgroup label="Content">
<option value="articles">Articles</option>
<option value="guides">Guides</option>
</optgroup>
<optgroup label="Admin">
<option value="reports">Reports</option>
<option value="users">Users</option>
</optgroup>
</Select>
</Field>
);
}<select class="select select-sm" aria-label="Small custom select" data-plf-select>
<option>Small</option>
<option>Default</option>
<option>Large</option>
</select>
<select class="select" aria-label="Default custom select" data-plf-select>
<option>Small</option>
<option selected>Default</option>
<option>Large</option>
</select>
<select class="select select-lg" aria-label="Large custom select" data-plf-select>
<option>Small</option>
<option>Default</option>
<option selected>Large</option>
</select><select class="plf:select plf:select-sm" aria-label="Small custom select" data-plf-select>
<option>Small</option>
<option>Default</option>
<option>Large</option>
</select>
<select class="plf:select" aria-label="Default custom select" data-plf-select>
<option>Small</option>
<option selected>Default</option>
<option>Large</option>
</select>
<select class="plf:select plf:select-lg" aria-label="Large custom select" data-plf-select>
<option>Small</option>
<option>Default</option>
<option selected>Large</option>
</select>import { Select } from '@pelatform/react';
export function Example() {
return (
<>
<Select custom size="sm" aria-label="Small custom select">
<option>Small</option>
<option>Default</option>
<option>Large</option>
</Select>
<Select custom aria-label="Default custom select" defaultValue="Default">
<option>Small</option>
<option>Default</option>
<option>Large</option>
</Select>
<Select custom size="lg" aria-label="Large custom select" defaultValue="Large">
<option>Small</option>
<option>Default</option>
<option>Large</option>
</Select>
</>
);
}import { Select } from '@pelatform/react/prefixed';
export function Example() {
return (
<>
<Select custom size="sm" aria-label="Small custom select">
<option>Small</option>
<option>Default</option>
<option>Large</option>
</Select>
<Select custom aria-label="Default custom select" defaultValue="Default">
<option>Small</option>
<option>Default</option>
<option>Large</option>
</Select>
<Select custom size="lg" aria-label="Large custom select" defaultValue="Large">
<option>Small</option>
<option>Default</option>
<option>Large</option>
</Select>
</>
);
}Dependent selects
The list follows the select’s options, so the script replaces the cities when the country changes.
<div class="grid gap-4 sm:grid-cols-2">
<div class="field">
<label class="field-label" for="select-country">Country</label>
<select class="select" id="select-country" name="country" data-plf-select>
<option value="">Choose a country…</option>
<option value="id">Indonesia</option>
<option value="jp">Japan</option>
<option value="nl">Netherlands</option>
</select>
</div>
<div class="field">
<label class="field-label" for="select-city">City</label>
<select class="select" id="select-city" name="city" disabled data-plf-select>
<option value="">Choose a country first</option>
</select>
</div>
</div>
<script>
const cities = {
id: ['Jakarta', 'Bandung', 'Surabaya'],
jp: ['Tokyo', 'Osaka', 'Kyoto'],
nl: ['Amsterdam', 'Rotterdam', 'Utrecht'],
};
const country = document.getElementById('select-country');
const city = document.getElementById('select-city');
function listCities() {
const names = cities[country.value] || [];
city.replaceChildren(new Option(names.length ? 'Choose a city…' : 'Choose a country first', ''), ...names.map(name => new Option(name)));
city.disabled = !names.length;
}
country.addEventListener('change', listCities);
// A browser that restores the chosen country, as Firefox does on reload, gets its cities too.
listCities();
</script><div class="plf:grid plf:gap-4 plf:sm:grid-cols-2">
<div class="plf:field">
<label class="plf:field-label" for="select-country">Country</label>
<select class="plf:select" id="select-country" name="country" data-plf-select>
<option value="">Choose a country…</option>
<option value="id">Indonesia</option>
<option value="jp">Japan</option>
<option value="nl">Netherlands</option>
</select>
</div>
<div class="plf:field">
<label class="plf:field-label" for="select-city">City</label>
<select class="plf:select" id="select-city" name="city" disabled data-plf-select>
<option value="">Choose a country first</option>
</select>
</div>
</div>
<script>
const cities = {
id: ['Jakarta', 'Bandung', 'Surabaya'],
jp: ['Tokyo', 'Osaka', 'Kyoto'],
nl: ['Amsterdam', 'Rotterdam', 'Utrecht'],
};
const country = document.getElementById('select-country');
const city = document.getElementById('select-city');
function listCities() {
const names = cities[country.value] || [];
city.replaceChildren(new Option(names.length ? 'Choose a city…' : 'Choose a country first', ''), ...names.map(name => new Option(name)));
city.disabled = !names.length;
}
country.addEventListener('change', listCities);
// A browser that restores the chosen country, as Firefox does on reload, gets its cities too.
listCities();
</script>'use client';
import { useState } from 'react';
import { Field, Select } from '@pelatform/react';
const cities: Record<string, string[]> = {
id: ['Jakarta', 'Bandung', 'Surabaya'],
jp: ['Tokyo', 'Osaka', 'Kyoto'],
nl: ['Amsterdam', 'Rotterdam', 'Utrecht'],
};
export function Example() {
const [country, setCountry] = useState('');
const names = cities[country] ?? [];
return (
<div className="grid gap-4 sm:grid-cols-2">
<Field label="Country">
<Select custom name="country" onValueChange={setCountry}>
<option value="">Choose a country…</option>
<option value="id">Indonesia</option>
<option value="jp">Japan</option>
<option value="nl">Netherlands</option>
</Select>
</Field>
<Field label="City">
<Select custom name="city" disabled={!names.length}>
<option value="">{names.length ? 'Choose a city…' : 'Choose a country first'}</option>
{names.map(name => <option key={name}>{name}</option>)}
</Select>
</Field>
</div>
);
}'use client';
import { useState } from 'react';
import { Field, Select } from '@pelatform/react/prefixed';
const cities: Record<string, string[]> = {
id: ['Jakarta', 'Bandung', 'Surabaya'],
jp: ['Tokyo', 'Osaka', 'Kyoto'],
nl: ['Amsterdam', 'Rotterdam', 'Utrecht'],
};
export function Example() {
const [country, setCountry] = useState('');
const names = cities[country] ?? [];
return (
<div className="plf:grid plf:gap-4 plf:sm:grid-cols-2">
<Field label="Country">
<Select custom name="country" onValueChange={setCountry}>
<option value="">Choose a country…</option>
<option value="id">Indonesia</option>
<option value="jp">Japan</option>
<option value="nl">Netherlands</option>
</Select>
</Field>
<Field label="City">
<Select custom name="city" disabled={!names.length}>
<option value="">{names.length ? 'Choose a city…' : 'Choose a country first'}</option>
{names.map(name => <option key={name}>{name}</option>)}
</Select>
</Field>
</div>
);
}JavaScript
The custom Select needs JavaScript. The Full script (pelatform-ui.min.js) starts every component on page load, with no code of your own; with ESM imports, call init() yourself, as in Installation. For markup you add later, see Dynamic content.
<link rel="stylesheet" href="pelatform-ui.min.css">
<script src="pelatform-ui.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.min.css">
<link rel="stylesheet" href="components/form.min.css">
<link rel="stylesheet" href="components/select.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/select.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script><link rel="stylesheet" href="pelatform-ui.prefixed.min.css">
<script src="pelatform-ui.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.prefixed.min.css">
<link rel="stylesheet" href="components/form.prefixed.min.css">
<link rel="stylesheet" href="components/select.prefixed.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/select.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script>| Attribute | Purpose |
|---|---|
data-plf-select |
On a single select: adds the trigger and list after it; the trigger takes the select’s classes, style and hidden |
data-plf-placeholder |
On the select: text for an option with an empty label; Select an option… by default |
disabled |
On the select: disables the trigger too |
| Method | Behavior |
|---|---|
PLF. |
Returns the instance, creating it for valid markup |
PLF. |
Returns the instance or null |
instance., instance. |
Open or close the list; return false when nothing changes or the event is canceled |
instance. |
Chooses an option and closes the list; returns false for an unknown, disabled or current value |
instance. |
Shows a value your code set through ., without events; options your code adds or replaces show by themselves |
instance. |
Removes the trigger and list; the select keeps its value |
| Event | When |
|---|---|
plf:, plf: |
Before and after the list opens; the first is cancelable |
plf:, plf: |
Before and after the list closes; the first is cancelable |
plf:, plf: |
Before and after a new value; the first is cancelable |
Each event’s detail:
| Property | Events | Value |
|---|---|---|
instance |
All | The Select instance |
reason |
All | api, trigger or keyboard |
trigger |
All | The trigger button or the clicked option, the select for a native input or change, or null from a method or a press or focus outside |
previousValue |
before-change, change |
The value before the change |
value |
before-change, change |
The new value |
A choice also fires native input and change on the select.
Keyboard
| Key | Action |
|---|---|
| Enter, Space, Arrow Down, Arrow Up | Open the list; while open, Enter and Space choose the active option |
| Arrow Down, Arrow Up, Home, End | Move through enabled options |
| Letters | Move to the next option that starts with them |
| Tab | Chooses the active option and moves on |
| Escape | Closes the list without choosing |
React
Select from @pelatform/react renders the native select around its option and optgroup children, with custom also the trigger and list the script adds; inside a Field it takes the field’s label, help and error.
| Prop | Values | Default |
|---|---|---|
size |
sm, lg |
The default size |
custom |
true adds the trigger and list beside the select, which keeps the value |
false |
onValueChange |
(value), called when the value changes |
None |
placeholder |
With custom, the trigger’s text for an option with an empty label |
Select an option… |
open |
With custom, true or false, which the application changes in onOpenChange |
Its own state |
defaultOpen |
With custom, true starts open when the application does not set open |
false |
onOpenChange |
With custom, (open, reason), called when the list opens or closes: trigger (the trigger, an option or a press outside) or keyboard (a key or the focus leaving) |
None |
The other props are a native select’s, className and ref included; with custom, the trigger takes id, which the label names, and ref stays on the select.
CSS variables
Override on a root or a container; on the select itself they reach the trigger, not the list. The trigger takes the Input variables, as the native select does.
| Variable | Default |
|---|---|
--plf-select-indicator |
The theme’s muted chevron-down, as an image on a single native select; replace it when you change the text color |
--plf-select-icon-size |
--plf-control-icon-size (16px); 14px with select-sm, 18px with select-lg |
--plf-select-popup-width |
At least the trigger’s width |
--plf-select-popup-max-height |
18rem (288px) |
--plf-select-popup-border |
--plf-color-border-subtle |
--plf-select-popup-bg |
--plf-color-surface-elevated |
A native select draws its chevron as an image in the theme’s muted gray, which an override of --plf-color-text-muted does not reach; set the same chevron with your color, # written %23. An image cannot use light-dark(), so for a dark root set it again, in your dark color, in a [data-plf-theme="dark"] rule and an auto one:
.my-plugin[data-plf-root] {
--plf-select-indicator: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23475569' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}