👾 Pixel UI
Components

Select

A pixel-art styled select dropdown component with accessibility features

Overview

The Select component provides a pixel-art styled dropdown for choosing values from a list. It's built on Base UI's Select primitive with compound components for full customization and accessibility support.

Preview

Installation

See the Installation guide for setup instructions.

Usage

import { Select } from '@joacod/pixel-ui'

const fruits = [
  { value: '', label: 'Select a fruit...' }, // Placeholder item
  { value: 'apple', label: 'Apple' },
  { value: 'banana', label: 'Banana' },
  { value: 'orange', label: 'Orange' },
]

export default function Example() {
  return (
    <Select.Root items={fruits}>
      <Select.Trigger variant="primary">
        <Select.Value />
        <Select.Icon />
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner>
          <Select.Popup>
            <Select.List>
              {fruits.map((fruit) => (
                <Select.Item key={fruit.value} value={fruit.value}>
                  <Select.ItemText>{fruit.label}</Select.ItemText>
                </Select.Item>
              ))}
            </Select.List>
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  )
}

Variants

The Select trigger supports multiple color variants:

<Select.Trigger variant="primary">...</Select.Trigger>
<Select.Trigger variant="secondary">...</Select.Trigger>
<Select.Trigger variant="accent">...</Select.Trigger>
<Select.Trigger variant="error">...</Select.Trigger>

Sizes

Five size options are available:

<Select.Trigger size="xs">...</Select.Trigger>
<Select.Trigger size="sm">...</Select.Trigger>
<Select.Trigger size="md">...</Select.Trigger>
<Select.Trigger size="lg">...</Select.Trigger>
<Select.Trigger size="xl">...</Select.Trigger>

States

Disabled State

Disable user interaction:

<Select.Root items={fruits} disabled>
  <Select.Trigger>
    <Select.Value placeholder="Disabled select" />
    <Select.Icon />
  </Select.Trigger>
  <Select.Portal>
    <Select.Positioner>
      <Select.Popup>
        <Select.List>
          {fruits.map((fruit) => (
            <Select.Item key={fruit.value} value={fruit.value}>
              <Select.ItemText>{fruit.label}</Select.ItemText>
            </Select.Item>
          ))}
        </Select.List>
      </Select.Popup>
    </Select.Positioner>
  </Select.Portal>
</Select.Root>

Disabled Items

Disable specific items in the dropdown:

<Select.Root items={[
  { value: 'apple', label: 'Apple' },
  { value: 'banana', label: 'Banana', disabled: true },
  { value: 'orange', label: 'Orange' },
]}>
  <Select.Trigger>
    <Select.Value placeholder="Select a fruit..." />
    <Select.Icon />
  </Select.Trigger>
  <Select.Portal>
    <Select.Positioner>
      <Select.Popup>
        <Select.List>
          <Select.Item value="apple">
            <Select.ItemText>Apple</Select.ItemText>
          </Select.Item>
          <Select.Item value="banana" disabled>
            <Select.ItemText>Banana (Out of stock)</Select.ItemText>
          </Select.Item>
          <Select.Item value="orange">
            <Select.ItemText>Orange</Select.ItemText>
          </Select.Item>
        </Select.List>
      </Select.Popup>
    </Select.Positioner>
  </Select.Portal>
</Select.Root>

Controlled Select

Use controlled state for full control over the selected value:

import { Select } from '@joacod/pixel-ui'
import { useState } from 'react'

export default function Example() {
  const [value, setValue] = useState('')

  return (
    <div>
      <Select.Root
        items={fruits}
        value={value}
        onValueChange={(newValue) => setValue(newValue as string)}
      >
        <Select.Trigger>
          <Select.Value placeholder="Select a fruit..." />
          <Select.Icon />
        </Select.Trigger>
        <Select.Portal>
          <Select.Positioner>
            <Select.Popup>
              <Select.List>
                {fruits.map((fruit) => (
                  <Select.Item key={fruit.value} value={fruit.value}>
                    <Select.ItemText>{fruit.label}</Select.ItemText>
                  </Select.Item>
                ))}
              </Select.List>
            </Select.Popup>
          </Select.Positioner>
        </Select.Portal>
      </Select.Root>
      <p>Selected: {value || 'None'}</p>
    </div>
  )
}

Multiple Selection

Enable multi-select mode:

import { Select } from '@joacod/pixel-ui'
import { useState } from 'react'

export default function Example() {
  const [values, setValues] = useState<string[]>([])

  return (
    <Select.Root
      items={fruits}
      multiple
      value={values}
      onValueChange={(newValues) => setValues(newValues as string[])}
    >
      <Select.Trigger>
        <Select.Value placeholder="Select fruits..." />
        <Select.Icon />
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner>
          <Select.Popup>
            <Select.List>
              {fruits.map((fruit) => (
                <Select.Item key={fruit.value} value={fruit.value}>
                  <Select.ItemText>{fruit.label}</Select.ItemText>
                </Select.Item>
              ))}
            </Select.List>
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  )
}

Custom Icon

Replace the default dropdown arrow with a custom icon:

<Select.Root items={fruits}>
  <Select.Trigger>
    <Select.Value placeholder="Custom icon..." />
    <Select.Icon>🔽</Select.Icon>
  </Select.Trigger>
  {/* ... rest of the component */}
</Select.Root>

Positioning

Control the dropdown position:

<Select.Root items={fruits}>
  <Select.Trigger>
    <Select.Value placeholder="Select..." />
    <Select.Icon />
  </Select.Trigger>
  <Select.Portal>
    <Select.Positioner
      side="top"
      alignment="center"
      sideOffset={10}
    >
      <Select.Popup>
        <Select.List>
          {fruits.map((fruit) => (
            <Select.Item key={fruit.value} value={fruit.value}>
              <Select.ItemText>{fruit.label}</Select.ItemText>
            </Select.Item>
          ))}
        </Select.List>
      </Select.Popup>
    </Select.Positioner>
  </Select.Portal>
</Select.Root>

API Reference

Select.Root

Container component that manages the select state.

PropTypeDefaultDescription
itemsSelectItem[]-Array of items to display
multiplebooleanfalseAllow multiple selections
valuestring | string[]-Controlled value
defaultValuestring | string[]-Default uncontrolled value
onValueChange(value: string | string[], event) => void-Callback when value changes
disabledbooleanfalseDisable the entire select
namestring-Name for form submission
requiredbooleanfalseMark as required for form validation
classNamestring-Additional CSS classes

Select.Trigger

The clickable trigger button.

PropTypeDefaultDescription
variant"primary" | "secondary" | "accent" | ..."primary"Visual style variant
size"xs" | "sm" | "md" | "lg" | "xl""md"Trigger size
classNamestring-Additional CSS classes

Select.Value

Displays the selected value.

PropTypeDefaultDescription
classNamestring-Additional CSS classes

Select.Icon

The dropdown indicator icon.

PropTypeDefaultDescription
classNamestring-Additional CSS classes
childrenReact.ReactNode"â–¼"Custom icon (defaults to down arrow)

Select.Portal

Portals the dropdown to a different part of the DOM.

PropTypeDefaultDescription
containerHTMLElement-Container element to portal into
keepMountedbooleanfalseKeep popup mounted when closed

Select.Positioner

Manages dropdown positioning relative to the trigger.

PropTypeDefaultDescription
side"top" | "bottom" | "left" | "right""bottom"Side of trigger to place popup
alignment"start" | "center" | "end""start"Alignment relative to trigger
sideOffsetnumber5Distance from trigger
alignItemWithTriggerbooleantrueAlign selected item with trigger
classNamestring-Additional CSS classes

Select.Popup

The dropdown container.

PropTypeDefaultDescription
classNamestring-Additional CSS classes

Select.List

Container for the list of items.

PropTypeDefaultDescription
classNamestring-Additional CSS classes

Select.Item

Individual selectable item.

PropTypeDefaultDescription
valuestring-Unique value for the item
disabledbooleanfalseDisable this item
classNamestring-Additional CSS classes

Select.ItemText

Text content of an item (supports keyboard navigation).

PropTypeDefaultDescription
classNamestring-Additional CSS classes
childrenReact.ReactNode-The text content

Accessibility

  • Built on Base UI's Select primitive with full accessibility support
  • Full keyboard navigation:
    • Space/Enter: Open/close dropdown
    • Arrow Up/Down: Navigate items
    • Home/End: Jump to first/last item
    • Escape: Close dropdown
    • Type to search: Jump to items by typing
  • Proper ARIA attributes for screen readers
  • Automatic focus management
  • Supports disabled state for entire select or individual items
  • Works seamlessly with the Field component for form validation

Examples

With Field Component

Combine with Field for labels and validation:

import { Select, Field } from '@joacod/pixel-ui'

<Field.Root>
  <Field.Label>Favorite Fruit</Field.Label>
  <Field.Control>
    <Select.Root items={fruits} required>
      <Select.Trigger>
        <Select.Value placeholder="Select a fruit..." />
        <Select.Icon />
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner>
          <Select.Popup>
            <Select.List>
              {fruits.map((fruit) => (
                <Select.Item key={fruit.value} value={fruit.value}>
                  <Select.ItemText>{fruit.label}</Select.ItemText>
                </Select.Item>
              ))}
            </Select.List>
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  </Field.Control>
  <Field.Description>Choose your favorite fruit</Field.Description>
  <Field.Error>Please select a fruit</Field.Error>
</Field.Root>

Form Integration

<form>
  <Select.Root items={countries} name="country" required>
    <Select.Trigger>
      <Select.Value placeholder="Select your country..." />
      <Select.Icon />
    </Select.Trigger>
    <Select.Portal>
      <Select.Positioner>
        <Select.Popup>
          <Select.List>
            {countries.map((country) => (
              <Select.Item key={country.value} value={country.value}>
                <Select.ItemText>{country.label}</Select.ItemText>
              </Select.Item>
            ))}
          </Select.List>
        </Select.Popup>
      </Select.Positioner>
    </Select.Portal>
  </Select.Root>
  <button type="submit">Submit</button>
</form>