Skip to content

Recipient Field

A To field where every chosen person becomes a tag. data-combobox-label keeps the email out of the tags, a custom filter matches it, and ::part(chip) styles the tags.

No one in your contacts matches

Code

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

Recipient Field
<div class="recipient-field">
37 collapsed lines
<label for="recipients">To</label>
<sv-combobox id="recipients" class="recipients" multiple name="to" placeholder="Add people">
<sv-flyout>
<button type="button" class="contact" aria-selected="true" value="maya@example.com">
<span data-combobox-label>Maya Chen</span>
<span class="email">maya@example.com</span>
</button>
<button type="button" class="contact" value="omar@example.com">
<span data-combobox-label>Omar Haddad</span>
<span class="email">omar@example.com</span>
</button>
<button type="button" class="contact" value="lena@example.com">
<span data-combobox-label>Lena Fischer</span>
<span class="email">lena@example.com</span>
</button>
<button type="button" class="contact" value="ravi@example.com">
<span data-combobox-label>Ravi Patel</span>
<span class="email">ravi@example.com</span>
</button>
<button type="button" class="contact" value="sofia@example.com">
<span data-combobox-label>Sofia Marino</span>
<span class="email">sofia@example.com</span>
</button>
<button type="button" class="contact" value="jonas@example.com">
<span data-combobox-label>Jonas Berg</span>
<span class="email">jonas@example.com</span>
</button>
<p class="empty" data-combobox-no-results>No one in your contacts matches</p>
</sv-flyout>
</sv-combobox>
</div>
<script type="application/x-typescript" module>
3 collapsed lines
const recipients = document.querySelector<Combobox>(".recipients")!;
recipients.filter = (option, query) => option.textContent.toLowerCase().includes(query.toLowerCase());
</script>
<style>
54 collapsed lines
.recipient-field {
display: grid;
gap: 0.5em;
inline-size: min(100%, 24rem);
margin-inline: auto;
color: var(--sv-gray-2);
label {
justify-self: start;
cursor: pointer;
}
.recipients::part(chip) {
padding: 0.25em 0.4em 0.25em 0.6em;
color: var(--sv-gray-1);
background-color: color-mix(in srgb, var(--sv-accent) 18%, transparent);
border-radius: var(--sv-bdr-rad-sm);
transition: background-color 160ms ease;
}
.recipients::part(chip):hover {
background-color: color-mix(in srgb, var(--sv-accent) 30%, transparent);
}
.recipients::part(chip-remove) {
fill: currentcolor;
}
.contact {
display: grid;
gap: 0.2em;
}
.email {
font-size: 0.8em;
color: var(--sv-gray-3);
}
.contact[aria-selected="true"] .email {
color: inherit;
}
.empty {
padding: 0.5em;
margin: 0;
color: var(--sv-gray-3);
}
@media (prefers-reduced-motion: reduce) {
.recipients::part(chip) {
transition: none;
}
}
}
</style>