Components
Select
Leverages the List component to provide markup for the Select popover.
customizable-select, overlay.Partial supportMissing: customizable-select, overlay.Anatomy
<Select>Container element.
slot="label"The label for the field.
slot="description"Description text displayed above the field.
.ui-fieldThe boxed select area.
slot="header"Content above the select, inside the border, with a divider.
slot="prefix"Content at the inline-start of the field, inside the border.
<select>The select. Its options are in a popover list.
slot="suffix"Content at the inline-end of the field, inside the border.
slot="footer"Content below the select, inside the border, with a divider.
slot="end-text"Supporting text displayed below the field.
Variants
---import { Select } from "@opui/astro"---
<Select label="Label"> <option value="">-</option> <option>Outlined (default)</option> <option>Option Two</option> <option>Option Three</option></Select>
<Select label="Label" variant="filled"> <option value="">-</option> <option>Filled</option> <option>Option Two</option> <option>Option Three</option></Select><label class="ui-select"><span class="ui-label" id="select-label-4">Label</span><span class="ui-field"><select aria-labelledby="select-label-4"><button><selectedcontent></button><div class="ui-list"><option value="">-</option><option>Outlined (default)</option><option>Option Two</option><option>Option Three</option></div></select></span></label><label class="ui-select ui-filled"><span class="ui-label" id="select-label-5">Label</span><span class="ui-field"><select aria-labelledby="select-label-5"><button><selectedcontent></button><div class="ui-list"><option value="">-</option><option>Filled</option><option>Option Two</option><option>Option Three</option></div></select></span></label>End text
.ui-end-text: end text element
---import { Select } from "@opui/astro"---
<Select label="Label" endText="Supporting text"> <option value="">-</option> <option>Outlined (default)</option> <option>Option Two</option> <option>Option Three</option></Select>
<Select label="Label" variant="filled" endText="Supporting text"> <option value="">-</option> <option>Filled</option> <option>Option Two</option> <option>Option Three</option></Select><label class="ui-select"><span class="ui-label" id="select-label-6">Label</span><span class="ui-field"><select aria-labelledby="select-label-6"><button><selectedcontent></button><div class="ui-list"><option value="">-</option><option>Outlined (default)</option><option>Option Two</option><option>Option Three</option></div></select></span><span id="end-text-2" class="ui-end-text">Supporting text</span></label><label class="ui-select ui-filled"><span class="ui-label" id="select-label-7">Label</span><span class="ui-field"><select aria-labelledby="select-label-7"><button><selectedcontent></button><div class="ui-list"><option value="">-</option><option>Filled</option><option>Option Two</option><option>Option Three</option></div></select></span><span id="end-text-3" class="ui-end-text">Supporting text</span></label>Affix
Use the prefix and suffix slots to affix icons or short text alongside the select inside the field's border.
---import { Select } from "@opui/astro"---
<Select label="Currency"> <Fragment slot="prefix">¢</Fragment> <option value="">-</option> <option>EUR</option> <option>EUR</option> <option>SEK</option></Select>
<Select label="Country"> <svg slot="prefix" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <circle cx="12" cy="12" r="10"></circle> <path d="M2 12h20"></path> <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" ></path> </svg> <option value="">-</option> <option>Sweden</option> <option>Norway</option> <option>Denmark</option></Select><label class="ui-select"><span class="ui-label" id="select-label-8">Currency</span><span class="ui-field"><select aria-labelledby="select-label-8"><button><selectedcontent></button><div class="ui-list"><option value="">-</option><option>EUR</option><option>EUR</option><option>SEK</option></div></select><span class="ui-prefix">¢</span></span></label><label class="ui-select"><span class="ui-label" id="select-label-9">Country</span><span class="ui-field"><select aria-labelledby="select-label-9"><button><selectedcontent></button><div class="ui-list"><option value="">-</option><option>Sweden</option><option>Norway</option><option>Denmark</option></div></select><span class="ui-prefix"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M2 12h20"></path><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg></span></span></label>Validation
- Set
requiredon the component to toggle required styles on the select. - Use the
errorprop to toggle invalid styles. It renders thedata-invalidattribute on the root element. Make use of the end text to give extra feedback on the error.
---import { Select } from "@opui/astro"---
<div class="example-row"> <Select label="Label" required> <option value="">-</option> <option>Pick me!</option> <option>No me!!</option> <option>Come on!</option> </Select>
<Select label="Label" variant="filled" required> <option value="">-</option> <option>Pick me!</option> <option>No me!!</option> <option>Come on!</option> </Select></div>
<div class="example-row"> <Select label="Label" error endText="Supporting text"> <option value="">-</option> <option selected>Wrong option</option> <option>Also wrong!</option> <option>Nothing's right!</option> </Select>
<Select label="Label" variant="filled" error endText="Supporting text"> <option value="">-</option> <option selected>Wrong option</option> <option>Also wrong!</option> <option>Nothing's right!</option> </Select></div><div class="example-row"><label class="ui-select"><span class="ui-label" id="select-label-10">Label</span><span class="ui-field"><select aria-labelledby="select-label-10" required><button><selectedcontent></button><div class="ui-list"><option value="">-</option><option>Pick me!</option><option>No me!!</option><option>Come on!</option></div></select></span></label><label class="ui-select ui-filled"><span class="ui-label" id="select-label-11">Label</span><span class="ui-field"><select aria-labelledby="select-label-11" required><button><selectedcontent></button><div class="ui-list"><option value="">-</option><option>Pick me!</option><option>No me!!</option><option>Come on!</option></div></select></span></label></div><div class="example-row"><label class="ui-select" data-invalid><span class="ui-label" id="select-label-12">Label</span><span class="ui-field"><select aria-labelledby="select-label-12"><button><selectedcontent></button><div class="ui-list"><option value="">-</option><option selected>Wrong option</option><option>Also wrong!</option><option>Nothing's right!</option></div></select></span><span id="end-text-4" class="ui-end-text">Supporting text</span></label><label class="ui-select ui-filled" data-invalid><span class="ui-label" id="select-label-13">Label</span><span class="ui-field"><select aria-labelledby="select-label-13"><button><selectedcontent></button><div class="ui-list"><option value="">-</option><option selected>Wrong option</option><option>Also wrong!</option><option>Nothing's right!</option></div></select></span><span id="end-text-5" class="ui-end-text">Supporting text</span></label></div>Spread
Use the spread boolean prop to display the label and description on the left with the select on the right. The layout collapses to a column on narrow containers.
---import { Select } from "@opui/astro"---
<Select spread> <Fragment slot="label">Country</Fragment> <Fragment slot="description">Select your country of residence</Fragment> <option value="">Select a country</option> <option>Denmark</option> <option>Finland</option> <option>Iceland</option> <option>Norway</option> <option>Sweden</option></Select>
<Select spread variant="filled"> <Fragment slot="label">Language</Fragment> <Fragment slot="description">Choose your preferred language</Fragment> <Fragment slot="end-text">This affects UI translations</Fragment> <option value="">Select a language</option> <option>Danish</option> <option>Finnish</option> <option>Icelandic</option> <option>Norwegian</option> <option>Swedish</option></Select>
<Select spread required> <Fragment slot="label">Required</Fragment> <Fragment slot="description">You must select an option</Fragment> <option value="">Select an option</option> <option>Option 1</option> <option>Option 2</option></Select>
<Select spread disabled> <Fragment slot="label">Disabled</Fragment> <Fragment slot="description">This select is disabled</Fragment> <option>Option 1</option></Select>
<Select spread error> <Fragment slot="label">Invalid Select</Fragment> <Fragment slot="description">This select has an error</Fragment> <Fragment slot="end-text">Please select a valid option.</Fragment> <option>Option 1</option></Select>
<Select spread> <Fragment slot="label">Currency</Fragment> <Fragment slot="description">Used for billing</Fragment> <Fragment slot="prefix">¢</Fragment> <option value="">-</option> <option>EUR</option> <option>EUR</option> <option>SEK</option></Select>
<Select spread variant="filled"> <Fragment slot="label">Region</Fragment> <Fragment slot="description">Affects data residency and latency</Fragment> <Fragment slot="prefix"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <circle cx="12" cy="12" r="10"></circle> <path d="M2 12h20"></path> <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" ></path> </svg> </Fragment> <Fragment slot="end-text">Cannot be changed after deploy</Fragment> <option value="">-</option> <option>eu-north-1</option> <option>us-east-1</option> <option>ap-southeast-1</option></Select><label class="ui-select ui-spread"><span class="ui-label" id="select-label-14">Country</span><span class="ui-start-text">Select your country of residence</span><span class="ui-field"><select aria-labelledby="select-label-14"><button><selectedcontent></button><div class="ui-list"><option value="">Select a country</option><option>Denmark</option><option>Finland</option><option>Iceland</option><option>Norway</option><option>Sweden</option></div></select></span></label><label class="ui-select ui-filled ui-spread"><span class="ui-label" id="select-label-15">Language</span><span class="ui-start-text">Choose your preferred language</span><span class="ui-field"><select aria-labelledby="select-label-15"><button><selectedcontent></button><div class="ui-list"><option value="">Select a language</option><option>Danish</option><option>Finnish</option><option>Icelandic</option><option>Norwegian</option><option>Swedish</option></div></select></span><span id="end-text-6" class="ui-end-text">This affects UI translations</span></label><label class="ui-select ui-spread"><span class="ui-label" id="select-label-16">Required</span><span class="ui-start-text">You must select an option</span><span class="ui-field"><select aria-labelledby="select-label-16" required><button><selectedcontent></button><div class="ui-list"><option value="">Select an option</option><option>Option 1</option><option>Option 2</option></div></select></span></label><label class="ui-select ui-spread"><span class="ui-label" id="select-label-17">Disabled</span><span class="ui-start-text">This select is disabled</span><span class="ui-field"><select aria-labelledby="select-label-17" disabled><button><selectedcontent></button><div class="ui-list"><option>Option 1</option></div></select></span></label><label class="ui-select ui-spread" data-invalid><span class="ui-label" id="select-label-18">Invalid Select</span><span class="ui-start-text">This select has an error</span><span class="ui-field"><select aria-labelledby="select-label-18"><button><selectedcontent></button><div class="ui-list"><option>Option 1</option></div></select></span><span id="end-text-7" class="ui-end-text">Please select a valid option.</span></label><label class="ui-select ui-spread"><span class="ui-label" id="select-label-19">Currency</span><span class="ui-start-text">Used for billing</span><span class="ui-field"><select aria-labelledby="select-label-19"><button><selectedcontent></button><div class="ui-list"><option value="">-</option><option>EUR</option><option>EUR</option><option>SEK</option></div></select><span class="ui-prefix">¢</span></span></label><label class="ui-select ui-filled ui-spread"><span class="ui-label" id="select-label-20">Region</span><span class="ui-start-text">Affects data residency and latency</span><span class="ui-field"><select aria-labelledby="select-label-20"><button><selectedcontent></button><div class="ui-list"><option value="">-</option><option>eu-north-1</option><option>us-east-1</option><option>ap-southeast-1</option></div></select><span class="ui-prefix"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M2 12h20"></path><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg></span></span><span id="end-text-8" class="ui-end-text">Cannot be changed after deploy</span></label>Sizes
---import { Select } from "@opui/astro"---
<Select label="Small" size="small"> <option value="">Small</option> <option>Option Two</option> <option>Option Three</option></Select><Select label="Default"> <option value="">Default</option> <option>Option Two</option> <option>Option Three</option></Select><label class="ui-select ui-small"><span class="ui-label" id="select-label-21">Small</span><span class="ui-field"><select aria-labelledby="select-label-21"><button><selectedcontent></button><div class="ui-list"><option value="">Small</option><option>Option Two</option><option>Option Three</option></div></select></span></label><label class="ui-select"><span class="ui-label" id="select-label-22">Default</span><span class="ui-field"><select aria-labelledby="select-label-22"><button><selectedcontent></button><div class="ui-list"><option value="">Default</option><option>Option Two</option><option>Option Three</option></div></select></span></label>Classic select
Bog-standard native HTML <select> without customized option list.
---import { ClassicSelect } from "@opui/astro"---
<ClassicSelect label="Label"> <option value="">-</option> <option>Option 1</option> <option>Option 2</option></ClassicSelect>
<ClassicSelect label="Label" variant="filled"> <option value="">-</option> <option>Option 1</option> <option>Option 2</option></ClassicSelect><label class="ui-select" ><span class="ui-label" id="select-label-23">Label</span ><span class="ui-field" ><select aria-labelledby="select-label-23" id="select-1"> <option value="">-</option> <option>Option 1</option> <option>Option 2</option> </select></span ></label><label class="ui-select ui-filled" ><span class="ui-label" id="select-label-24">Label</span ><span class="ui-field" ><select aria-labelledby="select-label-24" id="select-2"> <option value="">-</option> <option>Option 1</option> <option>Option 2</option> </select></span ></label>API
Select API
| Prop | Type | Default | Description |
|---|---|---|---|
dense | boolean | false | Packs the options tighter. |
description | string | - | Description text displayed above the field. |
endText | string | - | Supporting text displayed below the field. |
error | boolean | false | Shows error styles. |
id | string | - | The id of the <select>. |
items | Item[] | [] | The options, as { text, value } objects. |
label | string | - | The label for the field. |
size | "small" | - | The size of the element. |
spread | boolean | false | Pushes the label and description to one side and the select to the other. |
variant | "outlined", "filled" | "outlined" | The variant to use. |
Slots
| Slot | Description |
|---|---|
default | Extra <option> and <optgroup> elements. |
description | Description text displayed above the field. |
end-text | Supporting text displayed below the field. |
footer | Content below the select, inside the border, with a divider. |
header | Content above the select, inside the border, with a divider. |
label | The label for the field. |
prefix | Content at the inline-start of the field, inside the border. |
suffix | Content at the inline-end of the field, inside the border. |
Other attributes, such as disabled, multiple, name and required, go to the <select>.
Classic Select API
| Prop | Type | Default | Description |
|---|---|---|---|
endText | string | - | Supporting text displayed below the field. |
error | boolean | false | Shows error styles. |
id | string | - | The id of the <select>. Generated when omitted. |
items | Item[] | [] | The options, as { text, value } objects. |
label | string | - | The label for the field. |
size | "small" | - | The size of the element. |
variant | "outlined", "filled" | "outlined" | The variant to use. |
Slots
| Slot | Description |
|---|---|
default | Extra <option> and <optgroup> elements. |
Other attributes, such as disabled, multiple, name and required, go to the <select>.
Browser support
customizable-select, overlay.Partial supportMissing: customizable-select, overlay.See also the full browser support guide.
Installation
Dependencies
@layer components.extended { /*- Common styling for input, textarea and select- Form related styles such as: label, supporting text, error handling*/ :where(.ui-text-field, .ui-textarea, .ui-select) { --_motion: var(--motion, 1); --_accent-color: var(--primary); --_bg-color: var(--surface-default); --_border-color: var(--field-border-color); --_field-padding-block: var(--size-2); --_field-padding-inline: var(--size-2); --_filled-border-color: var(--text-primary); --_height: var(--field-size); --_label-color: var(--text-muted); --_end-text-color: var(--text-muted);
display: grid; gap: var(--size-1) 0; position: relative;
/* Field - the actual input with a border around it */ .ui-field { background-color: var(--_bg-color); border: var(--field-border-width) solid var(--_border-color); border-radius: var(--field-border-radius); display: grid; grid-column: 1/-1; grid-row: 2; grid-template-areas: "header header header" "prefix input suffix" "footer footer footer"; grid-template-columns: auto 1fr auto; min-block-size: var(--_height);
transition: border-color calc(0.2s * var(--_motion)) cubic-bezier(0.4, 0, 0.2, 1); }
:where(.ui-field) :where(input, textarea, select) { background: transparent; border: 0; color: var(--text-primary); font-family: var(--font-sans); font-size: var(--font-size-1); grid-area: input; inline-size: 100%; line-height: var(--font-lineheight-1); min-inline-size: 0; padding: var(--_field-padding-block) var(--_field-padding-inline);
&:focus, &:focus-visible { outline: 0; } }
/* Affixes */ .ui-prefix, .ui-suffix { align-items: center; color: var(--_label-color); display: inline-flex; padding-inline: var(--_field-padding-inline); white-space: nowrap; }
.ui-prefix { grid-area: prefix; }
.ui-suffix { grid-area: suffix; }
.ui-header, .ui-footer { align-items: center; color: var(--_label-color); display: flex; font-size: var(--font-size-0); gap: var(--size-2); padding: var(--size-1) var(--_field-padding-inline); }
.ui-header { border-block-end: 1px solid var(--_border-color); grid-area: header; }
.ui-footer { border-block-start: 1px solid var(--_border-color); grid-area: footer; }
/* Remove padding on the input when affixes are next to it */ .ui-field:has(> .ui-prefix) :where(input, textarea, select) { padding-inline-start: 0; }
.ui-field:has(> .ui-suffix) :where(input, textarea, select) { padding-inline-end: 0; }
/* Textarea - keep affix aligned with the first line */ &.ui-textarea .ui-field { .ui-prefix, .ui-suffix { align-self: start; padding-block: var(--_field-padding-block); } }
/* Required/Invalid */ &:has(:invalid) { .ui-label:after { color: var(--red); content: "*"; margin: -0.25em auto auto 0.25em; } }
/* File */ &:has(input[type="file"]) { cursor: pointer;
input { align-self: flex-start; block-size: var(--_height); box-shadow: none; color: var(--text-primary); cursor: inherit; max-inline-size: 100%; padding: 0; transition: font-size 0.2s var(--ease-3);
&::-webkit-file-upload-button, &::file-selector-button { background-color: var(--surface-tonal); block-size: calc(100% - var(--size-2) * 2); border: none; border-radius: var(--field-border-radius); cursor: pointer; margin-block-start: var(--size-2); margin-inline-end: 1ex; margin-inline-start: var(--size-2); } }
/* Variants */ &.ui-filled { input { &::-webkit-file-upload-button, &::file-selector-button { background-color: var(--surface-default); block-size: calc(100% - var(--size-2) * 2); border-radius: var(--field-border-radius); cursor: pointer; margin-block-start: var(--size-2); } } }
/* Sizes */ &.ui-small { input { font-size: var(--font-size-05);
&::-webkit-file-upload-button, &::file-selector-button { block-size: calc(100% - var(--size-2)); margin-block-start: var(--size-1); } } } }
/* Autosuggest */ &:has(input[list]) { .ui-label { /* Make sure chevron is visible */ inline-size: calc(100% - var(--size-6)); } }
/* Select */ &:has(select) { .ui-label { /* Make sure chevron is visible */ inline-size: calc(100% - var(--size-6)); } }
/* Customizable Select */ &:has(select button) { select { padding: 0;
button { outline: 0; padding: var(--_field-padding-block) var(--size-8) var(--_field-padding-block) var(--_field-padding-inline); } } }
/* Non-customizable Select */ &:has(select):not(:has(button)) { select { padding: var(--_field-padding-block) var(--size-8) var(--_field-padding-block) var(--_field-padding-inline); } }
/* Input - color */ &:has(input[type="color"]) { .ui-field { block-size: var(--_height); inline-size: var(--_height); min-block-size: 0; overflow: hidden; }
input { appearance: none; background: none; block-size: var(--_height); inline-size: var(--_height); overflow: hidden; padding: 0;
&::-webkit-color-swatch { border: none; }
&::-webkit-color-swatch-wrapper { padding: 0; } } }
/* Element states */ &:hover { &:not([data-invalid]) { .ui-field { border-color: var(--text-primary); } } }
&:focus-within { .ui-field { border-color: var(--_accent-color); outline-offset: -2px; } }
/* Label */ .ui-label { color: var(--text-primary); font-size: var(--font-size-05); font-weight: 600; grid-column: 1/-1; grid-row: 1; padding-inline: 0 1ex; }
:where(.ui-start-text) { color: var(--_end-text-color); font-size: var(--font-size-0); grid-column: 1/-1; grid-row: 2; line-height: 1.5; }
&:not(.ui-spread):has(> .ui-start-text) { .ui-field { grid-row: 3; }
:where(.ui-end-text) { grid-row: 4; } }
/* End text */ :where(.ui-end-text) { color: var(--_end-text-color); font-size: var(--font-size-0); grid-column: 1/-1; grid-row: 3; line-height: 1.5; }
/* Auto-fit */ &.ui-auto-fit { inline-size: auto;
.ui-field { inline-size: auto; }
:where(& input, & textarea) { field-sizing: content; inline-size: auto; min-inline-size: 25ch; } }
/* Validation */ &[data-invalid], &:has(:user-invalid) { --_accent-color: var(--color-9); --_border-color: var(--color-9); --_filled-border-color: var(--color-9); --_label-color: var(--color-9); --_end-text-color: var(--color-9); }
/* * Variant: Filled */ &.ui-filled { --_bg-color: var(--surface-tonal);
&:not(:has([disabled], :has(input[type="color"]))) { /* Hover */ &:hover { --_bg-color: light-dark( oklch(from var(--surface-tonal) calc(l * 0.93) c h), oklch(from var(--surface-tonal) calc(l * 1.1) c h) ); } } }
/* Disabled */ &:where(:has([disabled])) { cursor: not-allowed; opacity: 0.64; user-select: none;
&::before { display: none; }
.ui-field { cursor: not-allowed; }
:where(input, textarea, select) { cursor: not-allowed;
* { pointer-events: none; } }
.ui-label, .ui-start-text, .ui-end-text { cursor: not-allowed; } }
/* Read-only */ &:where(:has([readonly])) { &::before { display: none; }
:where(input, textarea, select) { cursor: not-allowed;
* { pointer-events: none; } } }
/* Sizes */ &.ui-small { --_field-padding-block: var(--size-2); --_height: var(--field-size-small);
&:has(input[type="color"]) { .ui-label { line-height: 1.5; } } }
/* Orientation */ &.ui-spread { align-items: start; column-gap: var(--size-4); container-type: inline-size; grid-template-columns: 1fr auto;
.ui-label { font-weight: 600; grid-column: 1; grid-row: 1; }
.ui-start-text { color: var(--_end-text-color); font-size: var(--font-size-0); grid-column: 1; grid-row: 2; line-height: 1.5; }
.ui-field { align-self: start; grid-column: 2; grid-row: 1 / span 2; }
.ui-field:has(textarea) { grid-row: 1 / span 3;
+ .ui-end-text { grid-row: 4; } }
:where(textarea) { min-inline-size: 30ch; }
:where(.ui-end-text) { grid-column: 2; text-align: end; }
/* Collapse to column on narrow containers */ @container (width < 400px) { column-gap: 0; grid-template-columns: 1fr;
.ui-label { grid-column: 1; grid-row: 1; }
.ui-start-text { grid-column: 1; grid-row: 2; }
.ui-field, .ui-field:has(textarea) { grid-column: 1; grid-row: 3; }
:where(.ui-end-text) { grid-column: 1; grid-row: 4; text-align: start; } } } }}@layer components.extended { :where(.ui-select select) { --_motion: var(--motion, 1); position: relative;
/* Default arrow */ &::picker-icon { block-size: 0; border-block-start: 5px solid; border-inline: 5px solid transparent; color: currentColor; content: ""; inline-size: 0; inset: 50% var(--size-3) auto auto; pointer-events: none; position: absolute; translate: 0 -50%; }
&:open { &::picker-icon { rotate: 180deg; } }
/* Select popover */ &::picker(select) { /* Animation on-stage styles */ border: 0; box-shadow: var(--shadow-2); opacity: 1; padding: 0; scale: 1;
/* Animation starting styles */ @starting-style { opacity: 0; transform: scale(0.9); }
transition: display calc(0.2s * var(--_motion)) allow-discrete, opacity calc(0.2s * var(--_motion)) var(--ease-3), overlay calc(0.2s * var(--_motion)) allow-discrete, scale calc(0.2s * var(--_motion)) var(--ease-3); }
/* Animation off-stage styles */ &:not(:open)::picker(select) { opacity: 0; scale: 0.9; }
button { align-items: center; background-color: transparent; display: flex; inline-size: 100%; margin: 0; position: relative;
selectedcontent { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } }
.ui-list { --_bg-color: var(--surface-filled); border: var(--field-border-width) solid var(--field-border-color); border-radius: var(--field-border-radius);
/* Groups */ [role="group"] { label { background-color: light-dark(var(--gray-3), var(--gray-13)); color: light-dark( oklch(from var(--text-primary) calc(l * 0.75) c h), oklch(from var(--text-primary) calc(l * 1.25) c h) ); font-weight: 500; overflow: hidden; padding-inline: var(--size-2); text-overflow: ellipsis; white-space: nowrap; }
&:not(:first-child), option:first-of-type { margin-block-start: var(--size-2); }
option:last-of-type, &:last-child { option:last-of-type { margin-block-end: 0; } } }
/* Option */ option { /* Checkmark */ /* TODO - checkmark should be the final version of the checkmark API. Follow the development of this and remove redundant psuedo stuff. */ &::check { display: none; }
&::checkmark { display: none; }
&::before { display: none; }
&:focus-visible { outline-offset: -1px; } } } }
:where(.ui-select:has(select)) { /* Size */ &.ui-small { button { padding-inline: var(--size-2) var(--size-7);
&::after { inset-inline-end: var(--size-2); } } }
/* * Non-experimental Select * * Hack to get the arrow working. Pseudo elements aren't allowed on the `<select>` element, so need to add it on the `.select` class instead. Noting this down if an `:after` element would be needed on a `.select`. */ &:not(:has(button)) { select { appearance: none; }
/* Arrow */ &::after { align-self: center; block-size: 0; border-block-start: 5px solid; border-inline: 5px solid transparent; content: ""; display: inline-block; flex-shrink: 0; grid-column: 1/-1; grid-row: 2; inline-size: 0; inset-inline-end: var(--size-3); justify-self: end; pointer-events: none; position: relative; }
&:not(.ui-spread):has(> .ui-start-text)::after { grid-row: 3; } } }
select:has(button), ::picker(select) { appearance: base-select; }}@layer components.extended { /*Lists meant to be used stand-alone or as part of Select elements
Intended use-case: - ul.ui-list > li - .ui-select > .ui-list > option*/ :where(.ui-list) { --_bg-color: var(--surface-filled); --_bg-color-hover: oklch(from var(--primary) l c h / 15%);
background-color: var(--_bg-color); color: var(--text-primary); list-style: none; padding: var(--size-2) 0;
@media (pointer: coarse) { &, * { user-select: none; } }
/* Background color */ &.ui-transparent { --_bg-color: transparent; }
&.ui-default { --_bg-color: var(--surface-default); }
&.ui-tonal { --_bg-color: var(--surface-tonal); }
/* Borders on all list items */ &.ui-bordered { :where(li + li, option + option) { margin-block-start: var(--size-3);
&::before { block-size: 1px; border-block-start: 1px solid var(--border-color); content: ""; display: block; inline-size: 100%; inset: calc(-1 * var(--size-2)) 0 auto 0; position: absolute; visibility: visible; /* override select > option:before style */ } } }
/* Dense - less gaps and spacing */ &.ui-dense { :where(li, option) { gap: var(--size-2); min-block-size: var(--size-7); padding: var(--size-1) var(--size-2);
&.ui-border-top { margin-block-start: var(--size-2);
&::before { inset: calc(-1 * var(--size-1)) 0 auto 0; } }
/* Clickable list item */ &:has(> a, > button, > label) { min-block-size: auto; padding: 0; }
& > :where(a, button, label) { gap: var(--size-2); min-block-size: var(--size-7); padding: var(--size-1) var(--size-2); }
/* Checkbox / Radio */ & > label { .ui-end { padding-inline-end: 0.125rem; }
&:where(.ui-checkbox, .ui-radio) { --_input-size: var(--size-3); } }
/* Leading and trailing content */ .ui-start, .ui-end { .ui-avatar { max-inline-size: var(--size-6); }
.ui-icon-button, svg { max-inline-size: var(--size-4); }
.ui-checkbox, .ui-radio { --_input-size: var(--size-3); } } } }
/* Gutterless */ &.ui-gutterless { :where(li, option) { padding-inline: 0;
& > :where(a, button, label) { padding-inline: 0; } } }
/* List item */ :where(li, option, [role="group"] > label) { align-items: center; background-color: var(--_bg-color); display: flex; font-size: var(--font-size-05); gap: var(--size-3); isolation: isolate; min-block-size: 40px; padding: var(--size-2) var(--size-3); position: relative;
&::before { display: none; /* removing checkmark from option */ }
* { font-size: inherit; }
/* Clickable list item */ &:has(> a, > button, > label) { background: transparent; display: block; min-block-size: auto; padding: 0;
&[aria-selected="true"] > :where(a, button, label), &[aria-selected="true"], &:has(> [aria-current="page"]) > :where(a, button, label) { background-color: var(--_bg-color-hover); } }
/* Select option */ &:where(option) { align-items: center; background-color: var(--_bg-color); color: inherit; cursor: pointer; display: flex; gap: var(--size-3); inline-size: 100%; margin: 0; min-block-size: 40px; padding: var(--size-2) var(--size-3); text-align: start; text-decoration: none; z-index: 0;
&:hover { background-color: var(--_bg-color-hover); }
&[aria-selected="true"] { background-color: var(--_bg-color-hover); color: var(--primary); }
&:checked { background-color: oklch(from var(--primary) l c h / 30%); } }
& > a, & > button, & > label { align-items: center; background-color: var(--_bg-color); color: inherit; cursor: pointer; display: flex; gap: var(--size-3); inline-size: 100%; margin: 0; min-block-size: 40px; outline-offset: -3px; padding: var(--size-2) var(--size-3); text-align: start; text-decoration: none; z-index: 0;
&:hover { background-color: var(--_bg-color-hover); }
&[aria-selected="true"], &[aria-current="page"] { background-color: var(--_bg-color-hover); } }
/* Checkbox / Radio / Switch */ & > label { &:where(.ui-checkbox, .ui-radio, .ui-switch) { display: flex; }
.ui-end { padding-inline-end: var(--size-1); }
&:where(.ui-checkbox, .ui-radio) { inline-size: 100%; }
/* Switches look comically big in lists, so it's better to use a smaller variant */ &.ui-switch { --_dot-size: 0.75rem; --_track-height: var(--size-4); --_track-width: 2.5rem; } }
/* Video */ &:has(video) { padding: 0.75rem var(--size-3) 0.75rem 0; }
/* Border between list items */ &.ui-border-top { margin-block-start: var(--size-3);
&::before { block-size: 1px; border-block-start: 1px solid var(--border-color); content: ""; display: block; inline-size: 100%; inset: calc(-1 * var(--size-2)) 0 auto 0; position: absolute; } }
/* Text */ .ui-text { flex: 1; line-height: 1.6;
:where(h1, h2, h3, h4, h5, h6, p, span) { color: inherit; font-weight: 400; }
p + p { color: var(--text-muted); font-size: var(--font-size-0); } }
/* Leading content */ .ui-start { align-items: center; align-self: center; display: grid; z-index: 1;
&:has(svg) { max-inline-size: var(--size-5); }
svg { padding-block-start: 0.125rem; }
img { aspect-ratio: 1; inline-size: 56px; object-fit: cover; }
video { aspect-ratio: 16/9; block-size: 64px; object-fit: cover; } }
/* Trailing content */ .ui-end { align-items: center; display: flex; font-size: var(--font-size-0); text-align: end; z-index: 1;
&:not(:has(a, button, input)) { pointer-events: none; }
kbd { background-color: transparent; border: 0; color: inherit; opacity: 0.6; }
svg { inline-size: 100%; max-inline-size: var(--size-5); } }
/* Inset */ &.ui-inset { .ui-text { padding-inline-start: calc(var(--size-5) + var(--size-3)); }
/* Safety measure so it won't look bad if there for some reason should exist a leading element inside. */ .ui-start { display: none; } } } }}