Skip to content

Single Select

Populate a single select from button options. close-on-select closes the flyout and scroll-to-selected keeps the selection visible.

Code

The following example assumes that you have imported the component and set up the theme.

Single Select
<div class="select-container">
<label for="countries-select-trigger">Select your country:</label>
<sv-select class="countries-select" close-on-select scroll-to-selected>
<sv-flyout class="select-flyout">
<!-- Trigger -->
<button id="countries-select-trigger" type="button" class="trigger" slot="trigger">
<!-- The selected option will be cloned into here -->
<span data-select-clone></span>
<!-- Show this when no option is selected -->
<span style="flex: 1; text-align: start" data-select-no-value>Please select a country</span>
<svg class="option-icon" aria-hidden="false" viewBox="0 -960 960 960" xmlns="http://www.w3.org/2000/svg">
<path d="M480-360 280-560h400L480-360Z"></path>
</svg>
</button>
</sv-flyout>
</sv-select>
</div>
<!-- Option template -->
6 collapsed lines
<template id="select-option-template">
<button type="button" class="option" value="{value}">
<img alt="" aria-hidden="true" src="" />
<span></span>
</button>
</template>
<!-- Dynamic data -->
<script type="module">
38 collapsed lines
import { countriesData } from "./countries-data";
/** @type {Flyout} */
const countriesFlyout = document.querySelector(".select-flyout");
/** @type {HTMLTemplateElement} */
const optionTemplate = document.querySelector("#select-option-template");
const flagsDir = new URL("@assets/flags/", import.meta.url);
// Filling the flyout with options elements
for (const country of countriesData) {
/** @type {DocumentFragment} */
const clone = optionTemplate.content.cloneNode(true);
/** @type {HTMLButtonElement} */
const button = clone.querySelector(".option");
/** @type {HTMLImageElement} */
const img = clone.querySelector("img");
/** @type {HTMLSpanElement} */
const label = clone.querySelector("span");
// option button
button.classList.add("option");
button.value = country.code;
// country flag image
img.src = `${flagsDir.href}${country.code}.${_img.png}`;
img.loading = "lazy";
img.alt = country.label_en;
// country label
label.textContent = country.label_en;
countriesFlyout.append(button);
}
</script>
<style>
.select-container {
5 collapsed lines
display: flex;
flex-direction: column;
gap: 0.5em;
max-inline-size: 350px;
margin: auto;
.option {
12 collapsed lines
display: flex;
gap: 0.5em;
align-items: center;
padding: 0.5em;
font-family: inherit;
font-size: 1rem;
color: var(--sv-gray-2);
cursor: pointer;
background-color: transparent;
border: none;
border-radius: var(--sv-bdr-rad-md);
transition: background-color 200ms;
&[aria-selected="true"] {
color: var(--sv-accent-content);
background-color: var(--sv-accent);
}
&[disabled] {
cursor: not-allowed;
filter: saturate(40%) brightness(50%);
}
@media (hover: hover) and (pointer: fine) {
&:not([disabled], [aria-selected="true"]):hover {
background-color: var(--sv-gray-5);
}
}
}
.trigger {
13 collapsed lines
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0.5em;
font-family: inherit;
font-size: 1rem;
color: var(--sv-gray-2);
cursor: pointer;
background-color: var(--sv-gray-6);
border: solid var(--sv-bdr-sz-sm) var(--sv-gray-5);
border-radius: var(--sv-bdr-rad-md);
transition: border-color 200ms ease;
.option-icon {
5 collapsed lines
inline-size: 1.5em;
block-size: 1.5em;
fill: currentcolor;
rotate: 90deg;
transition: rotate 200ms ease;
}
.option {
padding: 0;
}
}
sv-flyout::part(content) {
/* Match the flyout’s inline size to the anchor (trigger). */
inline-size: var(--sv-anchor-width);
/*
Define the smallest useful flyout for this list.
When a position has less space, the anchor tries the next fallback.
*/
min-block-size: 200px;
/* Let long content shrink and scroll so the current position can fit. */
max-block-size: min(var(--sv-anchor-available-height), 500px);
}
sv-flyout:state(--open) {
.trigger {
border: solid var(--sv-bdr-sz-sm) var(--sv-accent);
}
.option-icon {
rotate: 0deg;
}
}
.countries-select {
8 collapsed lines
.option {
gap: 1em;
img {
inline-size: 1.5em;
block-size: 1.5em;
}
}
}
}
</style>