Fields and selection

Select Aligned

A select whose panel opens on the trigger, with the selected row landing on the value it replaces.

⠋

Install

npx shadcn@latest add https://4af.selfsimilar.dev/r/select-aligned.json

Usage

import {
  AlignedSelect,
  AlignedSelectContent,
  AlignedSelectItem,
  AlignedSelectTrigger,
} from "@/components/ui/select-aligned";

<AlignedSelect size="xs" value={city} onValueChange={setCity}>
  <AlignedSelectTrigger variant="ghost" label="City" placeholder="Pick a city" />
  <AlignedSelectContent>
    <AlignedSelectItem value="Lisbon">Lisbon</AlignedSelectItem>
    <AlignedSelectItem value="Oslo">Oslo</AlignedSelectItem>
  </AlignedSelectContent>
</AlignedSelect>

API

Main exports: AlignedSelect, AlignedSelectTrigger, AlignedSelectContent, AlignedSelectItem, AlignedSelectGroup, AlignedSelectGroupLabel

A single-select whose panel opens over the trigger, with the selected row on top of the value it replaces, and scales out from that row. The trigger shows a ChevronsUpDown glyph, since the list can extend in both directions.

Both value / defaultValue and open / defaultOpen support controlled and uncontrolled use. Calling details.cancel() in onValueChange retains the accepted selection, including the row offered when reopening. Calling it in onOpenChange retains the previous open state.

Trigger variants

AlignedSelectTrigger accepts variant="default" | "ghost" | "plain". default is the full-width field. ghost uses Button's muted ghost colours with the default spacing, so the value and glyph stay aligned. plain removes the background, border, and horizontal padding, leaving the glyph directly beside the label. The focus ring remains for keyboard users. outline={false} is still accepted as an alias for variant="plain".

It also accepts an optional label, rendered as a dimmed prefix before the value. For example, label="Fruit" displays Fruit: Banana.

By default, default and ghost panels match the trigger width; plain panels fit their content. Set width="trigger" or width="content" on AlignedSelectContent to override.

<AlignedSelectTrigger variant="plain" label="City" placeholder="Pick a city" />

Overlapping the trigger

The overlap applies to pointer input only. Opened from the keyboard, or when there is no room, such as near a viewport edge or with a list too long to centre on the selected row, the panel opens as an ordinary dropdown below the trigger.

Row labels line up with the trigger's value because the panel's padding plus a row's padding equals the trigger's padding. If you restyle one, restyle the others to match. The selected check sits in a reserved slot at the end of the row, so it doesn't indent the labels.

Sections

Wrap rows in AlignedSelectGroup with an AlignedSelectGroupLabel for a dimmed heading. The heading is tied to its options, and a group hides itself when a search filters out all of its rows.

<AlignedSelectGroup>
  <AlignedSelectGroupLabel>Northern</AlignedSelectGroupLabel>
  <AlignedSelectItem value="Oslo">Oslo</AlignedSelectItem>
</AlignedSelectGroup>

Add searchable to AlignedSelectContent for a search field pinned above the list. It takes focus when the panel opens, with a row already active: the selected row, or the first row when nothing is chosen, so Enter takes it without an arrow press. Typing moves the active row to the top match. The active row carries data-panel-active. The arrow keys move focus into the list, and Base UI's own highlight takes over from there. The query clears when the panel closes. autoFocusFirstItem={false} opens the panel with no active row.

Filtered rows leave the list rather than hiding, so the panel resizes to what is left. Rows match on their text, which only works when the children are a plain string — give a row built from elements an explicit searchValue. emptyMessage replaces the list when a query matches nothing, and searchPlaceholder and searchLabel name the field.

Because the panel is positioned by the selected row, the search field sits that far above the trigger. With a selection deep in a long list there may be no room for the overlap, and the panel opens as an ordinary dropdown.

Sizing

Use size="xs" for a compact 24px trigger and popup rows, size="sm" for the 30px compact control, or size="md" for the 34px form-row control. The xs and sm trigger sizes use a 6px button radius; md uses 8px.