Component Library

← Back to gallery

Select field

Category: Forms · Tags: select, dropdown, validation, hint, error

Admins can invite and remove members.

How to use it

Use to pick one option from a list of roughly seven or more known values, such as a role, country or time zone. With fewer options, show them all as a radio group; when people need to type to search a long list, use a combobox. Start with an empty "Choose a role" option only when there is no safe default. After copying, change the ids (label for, hint, error and aria-describedby) and the validation message to say how to fix the problem.

Accessibility

A native <select> with a <label for>, so it is announced with its name, opened with the platform's own keyboard and touch picker, and needs no custom listbox script. aria-describedby points at the hint, and when "Continue" finds no choice the script adds the error message to aria-describedby, sets aria-invalid="true" and moves focus to the select; choosing a role clears both. The error is shown as text with a "!" marker and a thicker red border, not by colour alone, and focus shows a 2px outline. Text is #0f172a on #ffffff (17.85:1), the hint #475569 on #ffffff (7.58:1) and the error #b91c1c on #ffffff (6.47:1).

Source (copy-ready)

<form class="dcl-selectfield" novalidate>
  <label class="dcl-selectfield__label" for="dcl-selectfield-role">Team role</label>
  <p class="dcl-selectfield__hint" id="dcl-selectfield-role-hint">Admins can invite and remove members.</p>
  <p class="dcl-selectfield__error" id="dcl-selectfield-role-error" hidden><span class="dcl-selectfield__mark" aria-hidden="true">!</span> Choose a role to continue.</p>
  <select class="dcl-selectfield__select" id="dcl-selectfield-role" name="role" aria-describedby="dcl-selectfield-role-hint">
    <option value="">Choose a role</option>
    <option value="viewer">Viewer</option>
    <option value="commenter">Commenter</option>
    <option value="editor">Editor</option>
    <option value="billing">Billing manager</option>
    <option value="auditor">Auditor</option>
    <option value="admin">Admin</option>
    <option value="owner">Owner</option>
  </select>
  <button type="submit" class="dcl-selectfield__submit">Continue</button>
</form>
<style>
.dcl-selectfield { display: flex; flex-direction: column; align-items: flex-start; gap: 0.35rem; max-width: 22rem; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-selectfield__label { font-weight: 600; color: #0f172a; background-color: #ffffff; }
.dcl-selectfield__hint { margin: 0; font-size: 0.9rem; color: #475569; background-color: #ffffff; }
.dcl-selectfield__error { display: flex; align-items: center; gap: 0.4rem; margin: 0; font-weight: 600; color: #b91c1c; background-color: #ffffff; }
.dcl-selectfield__error[hidden] { display: none; }
.dcl-selectfield__mark { display: inline-flex; align-items: center; justify-content: center; width: 1.2rem; height: 1.2rem; border-radius: 50%; font-size: 0.8rem; color: #ffffff; background-color: #b91c1c; }
.dcl-selectfield__select { width: 100%; max-width: 100%; min-height: 2.75rem; padding: 0.4rem 0.6rem; border: 1px solid #64748b; border-radius: 0.375rem; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-selectfield__select[aria-invalid="true"] { border: 2px solid #b91c1c; }
.dcl-selectfield__select:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
.dcl-selectfield__submit { margin-top: 0.5rem; min-height: 2.75rem; padding: 0 1.25rem; border: none; border-radius: 0.5rem; cursor: pointer; font: inherit; font-weight: 600; color: #ffffff; background-color: #1d4ed8; }
.dcl-selectfield__submit:hover { color: #ffffff; background-color: #1e40af; }
.dcl-selectfield__submit:focus-visible { outline: 2px solid #0f172a; outline-offset: 2px; }
</style>
<script>
(function () {
  document.querySelectorAll(".dcl-selectfield").forEach(function (root) {
    if (root.dataset.dclBound) { return; }
    root.dataset.dclBound = "true";
    var select = root.querySelector(".dcl-selectfield__select");
    var hint = root.querySelector(".dcl-selectfield__hint");
    var error = root.querySelector(".dcl-selectfield__error");
    if (!select) { return; }
    function setError(on) {
      var ids = [];
      if (hint && hint.id) { ids.push(hint.id); }
      if (on && error && error.id) { ids.push(error.id); }
      if (error) { error.hidden = !on; }
      if (on) { select.setAttribute("aria-invalid", "true"); } else { select.removeAttribute("aria-invalid"); }
      if (ids.length) { select.setAttribute("aria-describedby", ids.join(" ")); }
    }
    root.addEventListener("submit", function (event) {
      event.preventDefault();
      setError(!select.value);
      if (!select.value) { select.focus(); }
    });
    select.addEventListener("change", function () {
      if (select.value) { setError(false); }
    });
  });
})();
</script>

Was this useful? Send feedback.