Westy Design Systemv0.2.7
OverviewComponentsExamplesStorybook

Foundations

  • Typography
  • Colors
  • Spacing
  • Radius
  • Shadows
  • Icons

Components

  • Accordion
  • Alert
  • Alert Dialog
  • Aspect Ratio
  • Avatar
  • Badge
  • Breadcrumb
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Data Table
  • Date Picker
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • Field
  • Hover Card
  • Input
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Label
  • Menubar
  • Native Select
  • Navigation Menu
  • Pagination
  • Popover
  • Progress
  • Questionnaire
  • Radio Group
  • Resizable
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Skeleton
  • Slider
  • Sonner
  • Spinner
  • Switch
  • Table
  • Tabs
  • Textarea
  • Toggle
  • Toggle Group
  • Tooltip

Chat

  • Attachment
  • Bubble
  • Marker
  • Message
  • Message Scroller

Components

Select

Displays a list of options for the user to pick from — triggered by a button.

Playground

—size
—position
—disabled
—aria-invalid
Usage
Compose Select from its parts — a SelectTrigger holding the SelectValue, and a SelectContent with one SelectItem per option. The placeholder renders in muted text until a value is picked.
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from "@workspace/ui/components/select"

<Select>
  <SelectTrigger className="w-44">
    <SelectValue placeholder="Select a fruit" />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      <SelectLabel>Fruits</SelectLabel>
      <SelectItem value="apple">Apple</SelectItem>
      <SelectItem value="banana">Banana</SelectItem>
      <SelectItem value="blueberry">Blueberry</SelectItem>
      <SelectItem value="grapes">Grapes</SelectItem>
      <SelectItem value="pineapple">Pineapple</SelectItem>
    </SelectGroup>
  </SelectContent>
</Select>
Groups
Organize long lists with SelectGroup and a SelectLabel per group, and divide groups with a SelectSeparator. Labels are announced with each option by assistive technology.
<Select>
  <SelectTrigger className="w-44">
    <SelectValue placeholder="Pick a food" />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      <SelectLabel>Fruits</SelectLabel>
      <SelectItem value="apple">Apple</SelectItem>
      <SelectItem value="banana">Banana</SelectItem>
    </SelectGroup>
    <SelectSeparator />
    <SelectGroup>
      <SelectLabel>Vegetables</SelectLabel>
      <SelectItem value="carrot">Carrot</SelectItem>
      <SelectItem value="spinach">Spinach</SelectItem>
    </SelectGroup>
  </SelectContent>
</Select>
Scrollable
When the list is taller than the available viewport space, the content scrolls and the scroll buttons appear automatically — no extra props needed.
<Select>
  <SelectTrigger className="w-64">
    <SelectValue placeholder="Select a timezone" />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      <SelectLabel>North America</SelectLabel>
      <SelectItem value="est">Eastern Standard Time (EST)</SelectItem>
      {/* … */}
    </SelectGroup>
    <SelectGroup>
      <SelectLabel>Europe & Africa</SelectLabel>
      <SelectItem value="gmt">Greenwich Mean Time (GMT)</SelectItem>
      {/* … */}
    </SelectGroup>
  </SelectContent>
</Select>
Sizes
The trigger comes in two sizes — default (36px) and sm (32px). Set size on the SelectTrigger; the sm size matches sm inputs and buttons in dense layouts.
<Select>
  <SelectTrigger size="sm" className="w-44">
    <SelectValue placeholder="Small" />
  </SelectTrigger>
  <SelectContent>…</SelectContent>
</Select>

<Select>
  <SelectTrigger className="w-44">
    <SelectValue placeholder="Default" />
  </SelectTrigger>
  <SelectContent>…</SelectContent>
</Select>
With Field
Pair the select with a Field, FieldLabel and FieldDescription in forms. Point the label's htmlFor at the trigger's id so clicking it opens the select.

Used for emails and in-app copy.

<Field>
  <FieldLabel htmlFor="language">Language</FieldLabel>
  <Select defaultValue="en">
    <SelectTrigger id="language" className="w-full">
      <SelectValue />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="en">English</SelectItem>
      <SelectItem value="pt">Português</SelectItem>
      <SelectItem value="es">Español</SelectItem>
    </SelectContent>
  </Select>
  <FieldDescription>
    Used for emails and in-app copy.
  </FieldDescription>
</Field>
Controlled
Use defaultValue for uncontrolled selects, or value and onValueChange to control the selection — the callback receives the newly selected item's value string.

Active theme: System

const [value, setValue] = React.useState("system")

<Select value={value} onValueChange={setValue}>
  <SelectTrigger className="w-44">
    <SelectValue />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="light">Light</SelectItem>
    <SelectItem value="dark">Dark</SelectItem>
    <SelectItem value="system">System</SelectItem>
  </SelectContent>
</Select>
Disabled
Set disabled on the Select root to disable the whole control, or on a single SelectItem to make just that option unselectable.
<Select disabled>
  <SelectTrigger className="w-44">
    <SelectValue placeholder="Unavailable" />
  </SelectTrigger>
  <SelectContent>…</SelectContent>
</Select>

<Select>
  <SelectTrigger className="w-44">
    <SelectValue placeholder="Shipping speed" />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="standard">Standard</SelectItem>
    <SelectItem value="express" disabled>
      Express (sold out)
    </SelectItem>
  </SelectContent>
</Select>
Invalid
Set aria-invalid on the SelectTrigger to show the invalid styles — a destructive border and ring on the trigger. Inside a Field, add data-invalid to the wrapper so the label turns destructive too.

A country is required.

<Field data-invalid>
  <FieldLabel htmlFor="country">Country</FieldLabel>
  <Select>
    <SelectTrigger id="country" aria-invalid className="w-full">
      <SelectValue placeholder="Select a country" />
    </SelectTrigger>
    <SelectContent>…</SelectContent>
  </Select>
  <FieldDescription>A country is required.</FieldDescription>
</Field>
RTL
Pass dir="rtl" to the Select root — Radix stamps its own direction on the control (defaulting to LTR), and the prop keeps the caret, the item indicator and keyboard navigation mirrored. Alternatively, wrap the app in the design system's DirectionProvider.
<div dir="rtl">
  <Select dir="rtl">
    <SelectTrigger className="w-44">
      <SelectValue placeholder="اختر فاكهة" />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="apple">تفاح</SelectItem>
      <SelectItem value="banana">موز</SelectItem>
      <SelectItem value="grapes">عنب</SelectItem>
    </SelectContent>
  </Select>
</div>
API Reference
Select wraps the Radix Select primitive and accepts all of its props — uncontrolled via defaultValue or controlled via value + onValueChange. Inside a form the selected item's value submits under name like a native select. The content defaults to position="item-aligned", which overlays the selected item on the trigger like a native macOS select; use popper to drop it below the trigger instead.
ComponentPropDefault
Selectvalue?: string—
SelectdefaultValue?: string—
SelectonValueChange?: (value: string) => void—
Selectopen?: boolean · onOpenChange?: (open: boolean) => void—
Selectdisabled?: booleanfalse
Selectrequired?: booleanfalse
Selectname?: string—
Selectdir?: "ltr" | "rtl"—
SelectTriggersize?: "sm" | "default""default"
SelectContentposition?: "item-aligned" | "popper""item-aligned"
SelectContentside? · align? · sideOffset? (popper only)—
SelectItemvalue: stringrequired
SelectItemdisabled?: booleanfalse
SelectItemtextValue?: string—

Tokens used

11 design tokens consumed by the Select component.

  • --input

    Input

    border-inputdark:bg-input/30dark:hover:bg-input/50
  • --ring

    Ring

    focus-visible:border-ringfocus-visible:ring-ring/50
  • --destructive

    Destructive

    aria-invalid:border-destructivearia-invalid:ring-destructive/20dark:aria-invalid:border-destructive/50dark:aria-invalid:ring-destructive/40
  • --popover

    Popover

    bg-popover
  • --popover-foreground

    Popover Foreground

    text-popover-foreground
  • --foreground

    Foreground

    ring-foreground/10
  • --accent

    Accent

    focus:bg-accent
  • --accent-foreground

    Accent Foreground

    focus:text-accent-foreground
  • --muted-foreground

    Muted Foreground

    data-placeholder:text-muted-foregroundtext-muted-foreground
  • --border

    Border

    bg-border
  • --spacing

    Spacing unit (4px)

    data-[size=default]:h-9data-[size=sm]:h-8size-4py-1.5ps-2pe-8