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

Native Select

A styled native select element — the browser renders the dropdown, so it stays fast and mobile-friendly.

Playground

—size
—disabled
—aria-invalid
Usage
Wrap plain options in NativeSelect — each NativeSelectOption renders a real <option>, and the browser draws the dropdown itself. Use it over Select when you want native behavior, better performance, or mobile-optimized pickers; reach for Select when you need custom styling inside the list, animations, or complex interactions.
import {
  NativeSelect,
  NativeSelectOptGroup,
  NativeSelectOption,
} from "@workspace/ui/components/native-select"

<NativeSelect defaultValue="">
  <NativeSelectOption value="" disabled>
    Select a fruit
  </NativeSelectOption>
  <NativeSelectOption value="apple">Apple</NativeSelectOption>
  <NativeSelectOption value="banana">Banana</NativeSelectOption>
  <NativeSelectOption value="blueberry">Blueberry</NativeSelectOption>
  <NativeSelectOption value="grapes">Grapes</NativeSelectOption>
  <NativeSelectOption value="pineapple">Pineapple</NativeSelectOption>
</NativeSelect>
Groups
Organize long lists with NativeSelectOptGroup — it renders a native <optgroup>, so the browser indents the options and shows the required label as a non-selectable heading.
<NativeSelect defaultValue="">
  <NativeSelectOption value="" disabled>
    Select a department
  </NativeSelectOption>
  <NativeSelectOptGroup label="Engineering">
    <NativeSelectOption value="frontend">Frontend</NativeSelectOption>
    <NativeSelectOption value="backend">Backend</NativeSelectOption>
  </NativeSelectOptGroup>
  <NativeSelectOptGroup label="Sales">
    <NativeSelectOption value="account-exec">
      Account Executive
    </NativeSelectOption>
    <NativeSelectOption value="sales-dev">
      Sales Development
    </NativeSelectOption>
  </NativeSelectOptGroup>
  <NativeSelectOptGroup label="Operations">
    <NativeSelectOption value="finance">Finance</NativeSelectOption>
    <NativeSelectOption value="people">People</NativeSelectOption>
  </NativeSelectOptGroup>
</NativeSelect>
Sizes
The control comes in two sizes — default (36px) and sm (32px). Set size on NativeSelect; the sm size matches sm inputs and buttons in dense layouts.
<NativeSelect size="sm" defaultValue="apple">
  <NativeSelectOption value="apple">Apple</NativeSelectOption>
  {/* … */}
</NativeSelect>

<NativeSelect defaultValue="apple">
  <NativeSelectOption value="apple">Apple</NativeSelectOption>
  {/* … */}
</NativeSelect>
With Field
Pair the select with a Field, FieldLabel and FieldDescription in forms. Props pass through to the underlying <select>, so point the label's htmlFor at the component's id as usual.

Used for emails and in-app copy.

<Field>
  <FieldLabel htmlFor="language">Language</FieldLabel>
  <NativeSelect id="language" defaultValue="en" className="w-full">
    <NativeSelectOption value="en">English</NativeSelectOption>
    <NativeSelectOption value="pt">Português</NativeSelectOption>
    <NativeSelectOption value="es">Español</NativeSelectOption>
  </NativeSelect>
  <FieldDescription>
    Used for emails and in-app copy.
  </FieldDescription>
</Field>
Controlled
The component is a plain <select>, so control it the React way — defaultValue for uncontrolled usage, or value and onChange reading event.target.value.

Active theme: System

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

<NativeSelect
  value={value}
  onChange={(event) => setValue(event.target.value)}
  className="w-44"
>
  <NativeSelectOption value="light">Light</NativeSelectOption>
  <NativeSelectOption value="dark">Dark</NativeSelectOption>
  <NativeSelectOption value="system">System</NativeSelectOption>
</NativeSelect>
Disabled
Set disabled on NativeSelect to disable the whole control — the wrapper dims to 50% opacity — or on a single NativeSelectOption to make just that option unselectable.
<NativeSelect disabled defaultValue="">
  <NativeSelectOption value="">Unavailable</NativeSelectOption>
</NativeSelect>

<NativeSelect defaultValue="">
  <NativeSelectOption value="" disabled>
    Shipping speed
  </NativeSelectOption>
  <NativeSelectOption value="standard">Standard</NativeSelectOption>
  <NativeSelectOption value="express" disabled>
    Express (sold out)
  </NativeSelectOption>
</NativeSelect>
Invalid
Set aria-invalid on NativeSelect to show the invalid styles — a destructive border and ring on the control. 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>
  <NativeSelect id="country" aria-invalid defaultValue="" className="w-full">
    <NativeSelectOption value="" disabled>
      Select a country
    </NativeSelectOption>
    <NativeSelectOption value="br">Brazil</NativeSelectOption>
    <NativeSelectOption value="us">United States</NativeSelectOption>
    <NativeSelectOption value="jp">Japan</NativeSelectOption>
  </NativeSelect>
  <FieldDescription>A country is required.</FieldDescription>
</Field>
RTL
Pass dir="rtl" — it reaches the native <select> directly, and the component's logical properties keep the caret and padding mirrored. No provider needed: the browser handles text direction inside the dropdown itself.
<div dir="rtl">
  <NativeSelect dir="rtl" defaultValue="">
    <NativeSelectOption value="" disabled>
      اختر فاكهة
    </NativeSelectOption>
    <NativeSelectOption value="apple">تفاح</NativeSelectOption>
    <NativeSelectOption value="banana">موز</NativeSelectOption>
    <NativeSelectOption value="grapes">عنب</NativeSelectOption>
  </NativeSelect>
</div>
API Reference
NativeSelect renders a real <select> element inside a wrapper that positions the caret icon, so every native select prop — value, onChange, name, required, form — passes straight through. It ships no JavaScript of its own and stays server-compatible. Options are painted by the browser, styled with the Canvas / CanvasText system colors so the list stays readable in dark mode.
ComponentPropDefault
NativeSelectsize?: "sm" | "default""default"
NativeSelectvalue? · defaultValue?: string—
NativeSelectonChange?: (event: ChangeEvent) => void—
NativeSelectdisabled?: booleanfalse
NativeSelectrequired?: booleanfalse
NativeSelectname?: string—
NativeSelectdir?: "ltr" | "rtl"—
NativeSelectOptionvalue?: string—
NativeSelectOptiondisabled?: booleanfalse
NativeSelectOptGrouplabel: stringrequired
NativeSelectOptGroupdisabled?: booleanfalse

Tokens used

7 design tokens consumed by the Native 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
  • --primary

    Primary

    selection:bg-primary
  • --primary-foreground

    Primary Foreground

    selection:text-primary-foreground
  • --muted-foreground

    Muted Foreground

    placeholder:text-muted-foregroundtext-muted-foreground
  • --spacing

    Spacing unit (4px)

    h-9data-[size=sm]:h-8size-4py-1ps-2.5pe-8