Skip to content

Country Picker

A country field built from data, with a flag beside every name. A custom filter matches the name anywhere, or the exact country code, so de finds Germany.

No country matches your search

Code

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

Country Picker
<div class="country-picker">
7 collapsed lines
<label for="country">Country</label>
<sv-combobox id="country" name="country" placeholder="Search by name or code">
<sv-flyout class="country-flyout">
<p class="empty" data-combobox-no-results>No country matches your search</p>
</sv-flyout>
</sv-combobox>
</div>
<template id="country-option">
4 collapsed lines
<button type="button" class="option">
<img alt="" loading="lazy" />
<span data-combobox-label></span>
</button>
</template>
<script type="application/x-typescript" module>
22 collapsed lines
import { countriesData } from "./countries-data.ts";
const countryCombobox = document.querySelector<Combobox>("#country")!;
const countryFlyout = document.querySelector(".country-flyout")!;
const optionTemplate = document.querySelector<HTMLTemplateElement>("#country-option")!;
const flagsDir = new URL("@assets/flags/", import.meta.url);
for (const country of countriesData) {
const option = optionTemplate.content.firstElementChild!.cloneNode(true) as HTMLButtonElement;
option.value = country.code;
option.querySelector("img")!.src = `${flagsDir.href}${country.code}.png`;
option.querySelector("[data-combobox-label]")!.textContent = country.label_en;
countryFlyout.append(option);
}
// Match the name anywhere, or the exact country code
countryCombobox.filter = (option, query) => {
const lowerCaseQuery = query.toLowerCase();
return option.value === lowerCaseQuery || option.textContent!.toLowerCase().includes(lowerCaseQuery);
};
</script>
<style>
42 collapsed lines
.country-picker {
display: grid;
gap: 0.5em;
inline-size: min(100%, 22rem);
margin-inline: auto;
color: var(--sv-gray-2);
label {
justify-self: start;
cursor: pointer;
}
.option {
display: flex;
gap: 0.75em;
align-items: center;
min-inline-size: 0;
img {
flex: none;
inline-size: 1.5em;
block-size: 1.5em;
border-radius: var(--sv-bdr-rad-sm);
}
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.country-flyout::part(content) {
max-block-size: min(var(--sv-anchor-available-height), 20rem);
}
.empty {
padding: 0.5em;
margin: 0;
color: var(--sv-gray-3);
}
}
</style>