Skip to contentAsheeUIAsheeUI

Dropdown

One option from a list, in a dropdown that searches, portals and reports the choice.

A dropdown asks for one value out of a set that is too long to keep on the page. What separates it from a radio group is not the number of options but their cost, because the list stays out of the way until it is asked for, and it can be searched, styled apart from the trigger and rendered over the page's clipping. The trigger is a Button, so it takes the button's variants, sizes and colours, and the field around it is the field family's label, description and message.

Usage

import { Dropdown } from "@asheeui/web";

const speeds = [
  { label: "Standard", value: "standard" },
  { label: "Express", value: "express" },
];

<Dropdown
  label="Delivery speed"
  options={speeds}
  value={speed}
  onValueChange={setSpeed}
/>

Dropdown and Dropmenu are the same component. This page calls it a dropdown, and both names import: the component was renamed from Select to Dropmenu in 1.0.0, and Dropdown is the alias that matches what the documentation says. One thing keeps the original name: the configuration section, components.dropmenu, because a key names the module rather than the export. A theme written for either name configures the same trigger.

The trigger draws the label of the option whose value matches, so a set value is stated as a name rather than as a code. onValueChange reports the value itself; onChange reports a synthetic change event shaped like the browser's own <select>, for a form library that listens for one. Pass whichever your state wants, or both.

placeholder is what the trigger says while no option matches, "Dropdown..." unless you state otherwise. initialValue is the label shown when no option matches value, and it is drawn as written rather than looked up among the options, so a real selection belongs in value.

Search and the menu

isSearch puts a search field at the top of the menu, focused as the menu opens, and typing filters the options by label. It is off unless you ask for it. An empty result is not a blank panel: the menu states "No options found" in a status region, so the outcome is announced. searchPlaceholder and searchInputName name the field, and belowList renders under the options, an "Add new" action or a note about where the list comes from.

The menu is a component with its own tokens, and the menu prop overrides them one key at a time: itemVariant and itemColor for options at rest, activeItemVariant and activeItemColor for the selected one, radius and size for the options themselves. It renders into a portal at the document body by default, so a dropdown inside a scrolling table or a clipped panel is not cut off; menu={{ portal: false }} keeps it inside its parent when a container needs to hold it. An option marked disabled stays in the list, announces that it is unavailable and cannot be chosen.

Props

The trigger is a button, so it reads the field family's web defaults, size: "md" and variant: "bordered", while radius is not registered: it follows the application's defaultRadius, and color follows defaultColor. Stating either under components.dropmenu pins it for every dropdown in the product.

The menu resolves its own tokens rather than borrowing the trigger's: itemVariant falls back to ghost and activeItemVariant to faded, the two option colours fall back to primary with the application's defaultColor in between, radius follows the application's defaultRadius and then md, and size is md. portal and lockScroll are both on. Write any of them under components.dropmenu.menu to change them once, or in the menu prop to change them for one instance.

Accessibility

  • The trigger is a real <button type="button"> with aria-haspopup="listbox" and aria-expanded, and the field's label names it. The description and the validation message are referenced with aria-describedby, and an error status sets aria-invalid.
  • The list is a role="listbox"; each option is a role="option" with aria-selected, and a disabled option carries aria-disabled and cannot be chosen. The checkmark beside the selected option is decoration on top of that state.
  • ↑ and ↓ on the trigger open the menu. Inside, the arrows move the current option and wrap, Home and End jump to the edges, typed characters jump to the first option whose label starts with them, and Escape closes the menu and returns focus to the trigger. Every one of those moves skips a disabled option.
  • A search with no matches announces "No options found" through a role="status" region, so an empty result is stated rather than left as a blank panel.

The label, description and message behave as the field anatomy describes.