Inspector Select

Settings row that pairs a label with its current choice inside one pill, opening a width-matched dropdown below: option icons, colour swatches, descriptions and grouped sections, on top of the accessible shadcn select.

Installation

Adds the component and everything it depends on to your project.

npx shadcn add https://ui.beste.co/component/r-base/inspector-select

New here? Read the installation guide.

Usage

The import and the props worth knowing about, in one place.

import { InspectorSelect } from "@/components/beste/component/inspector-select";

// Plain strings are enough for a simple list
<InspectorSelect label="Easing" options={["linear", "ease-in", "ease-out"]} defaultValue="ease-out" />

// Controlled, with a commit-shaped handler
<InspectorSelect
  label="Blend mode"
  value={blendMode}
  onValueChange={setBlendMode}
  onOpenChange={(open) => console.log("menu open:", open)}
  options={[
    { value: "normal", label: "Normal" },
    // Consecutive options sharing a group become one section, so the order
    // you write is the order that renders.
    { value: "multiply", label: "Multiply", group: "Darken", description: "Keeps the darker pixels" },
    { value: "screen", label: "Screen", group: "Lighten" },
    { value: "hue", label: "Hue", group: "Component", disabled: true },
  ]}
/>

// Icons and colour swatches, per option
<InspectorSelect
  label="Palette"
  icon={Palette}                 // row icon, before the label
  defaultValue="sunset"
  options={[
    { value: "sunset", label: "Sunset", swatch: "#f97316" },
    { value: "ocean", label: "Ocean", swatch: "#0ea5e9" },
  ]}
/>

<InspectorSelect
  label="Font"
  tone="outline"                 // "muted" (default) | "outline" | "ghost"
  size="sm"                      // "sm" | "default" | "lg"
  menuWidth="auto"               // "trigger" (default) matches the row width
  placeholder="Inherit"          // shown until something is selected
  emptyMessage="No fonts found"
  options={fonts}
/>

// Takes part in a form through the underlying select
<InspectorSelect label="Region" name="region" options={["EU", "US", "APAC"]} disabled={isSaving} />

Playground

Turn the props on the right; the snippet under them is what you would write to get what you see.

Usage
import { InspectorSelect } from "@/components/beste/component/inspector-select";

<InspectorSelect
  label="Font"
  options={["Inter","Geist","Söhne","Untitled Sans"]}
  defaultValue="Inter"
/>

Keyboard and gestures

Every one of these is in the component already. They are listed because a props table cannot mention a gesture, so nothing else on this page can tell you they exist.

Enter / SpaceOpen the menu.
Arrow keysMove through the options; the value follows as you go.
Type a letterJump to the next option starting with it.
EscapeClose and keep what was there before.

Props

Read from the component's own type, so this cannot drift from what it accepts.

PropTypeDefaultDescription
label*stringLabel rendered on the left, inside the row.
iconLucideIconOptional leading icon shown before the label.
options(string | InspectorSelectOption)[]Plain strings for simple lists, or objects for icons, swatches and groups.
valuestringControlled value. Pair it with `onValueChange`.
defaultValuestringInitial value in uncontrolled mode.
onValueChange(value: string) => void
onOpenChange(open: boolean) => voidFires when the menu opens or closes.
placeholderstring"Select"Shown in place of the value until something is selected.
emptyMessagestring"No options"Menu text when there is nothing to choose from.
menuWidth"trigger" | "auto""trigger"Menu width: match the row, or size to the longest option.
disabledbooleanBlock interaction and dim the row.
tone"muted" | "outline" | "ghost""muted"Surface treatment: filled (default), hairline outline, or bare until hover.
size"sm" | "default" | "lg""default"Row height preset.
namestringName of the underlying form control, so the row can take part in a form.
idstringId of the trigger, for an external `<label htmlFor>`.
classNamestring
aria-labelstringAccessible name. Omit it and the row reads as "{label}, {value}", which is usually what you want — the visible label and the value are both announced.

More Inspector components

View all Inspector

Inspector Switch

Settings row with a label on the left and a switch on the right, sharing the family's pill: the label takes the row's spare width, so a press almost anywhere toggles it.

Inspector Color

Settings row that keeps the label, the colour value and a swatch on one pill; pressing the row opens the OKLCH colour picker below it, which is where the value is edited.

Inspector Input

Settings row with a label on the left and a plain text field on the right, sharing the family's pill: the whole row is the label, so pressing anywhere puts the caret in the field.

Dark Mode

Inspector Segmented

Settings row whose choices sit side by side on the right, with a marker that slides between them: the readable alternative to a switch when the two states deserve names, and the compact one to a select at three or four options.

Borders

Inspector Toggles

Settings row holding a set of pills that are each on or off, so any number of them can be on at once: what inspector-segmented is for one choice out of several, this is for several choices at once, and what inspector-switch is for a single flag, this is for a handful that belong together.

Inspector Variants

The choice that has to be seen to be made: a template, a layout, a chart type. Each option is a picture, normally a rendered screenshot passed as a URL, or markup for the choices no photograph would show. The row is the trigger, the grid is width-matched to it, and the captions under the pictures can be dropped for a set whose pictures say everything.