Devie UI

The Select component provides a dropdown menu for selecting a single value from a list of options. It extends Base UI's Select with consistent styling that matches the design system.

Installation

Here is the component implementation code that you can copy to your project:

// Select component based on Base UI with Field-consistent styling
// https://base-ui.com/react/components/select
 
import { Select as BaseSelect } from "@base-ui-components/react/select";
import clsx from "clsx";
import { Check, ChevronDown } from "lucide-react";
import type { ReactNode } from "react";
import styles from "./Select.module.scss";
 
// Root component
const Root = BaseSelect.Root;
 
// Trigger component
interface TriggerProps extends BaseSelect.Trigger.Props {
  className?: string;
}
 
function Trigger({ className, ...props }: TriggerProps) {
  return (
    <BaseSelect.Trigger
      className={clsx(styles.trigger, className)}
      {...props}
    />
  );
}
 
// Value component
interface ValueProps extends BaseSelect.Value.Props {
  className?: string;
}
 
function Value({ className, ...props }: ValueProps) {
  return (
    <BaseSelect.Value className={clsx(styles.value, className)} {...props} />
  );
}
 
// Icon component
interface IconProps extends BaseSelect.Icon.Props {
  className?: string;
}
 
function Icon({ className, children, ...props }: IconProps) {
  return (
    <BaseSelect.Icon className={clsx(styles.icon, className)} {...props}>
      {children || <ChevronDown size={16} />}
    </BaseSelect.Icon>
  );
}
 
// Portal component
const Portal = BaseSelect.Portal;
 
// Positioner component
interface PositionerProps extends BaseSelect.Positioner.Props {
  className?: string;
}
 
function Positioner({ className, ...props }: PositionerProps) {
  return (
    <BaseSelect.Positioner
      className={clsx(styles.positioner, className)}
      {...props}
    />
  );
}
 
// Popup component
interface PopupProps extends BaseSelect.Popup.Props {
  className?: string;
}
 
function Popup({ className, ...props }: PopupProps) {
  return (
    <BaseSelect.Popup className={clsx(styles.popup, className)} {...props} />
  );
}
 
// Item component
interface ItemProps extends BaseSelect.Item.Props {
  className?: string;
}
 
function Item({ className, ...props }: ItemProps) {
  return (
    <BaseSelect.Item className={clsx(styles.item, className)} {...props} />
  );
}
 
// ItemIndicator component
interface ItemIndicatorProps extends BaseSelect.ItemIndicator.Props {
  className?: string;
  children?: ReactNode;
}
 
function ItemIndicator({ className, children, ...props }: ItemIndicatorProps) {
  return (
    <BaseSelect.ItemIndicator
      className={clsx(styles.itemIndicator, className)}
      {...props}
    >
      {children || <Check size={14} />}
    </BaseSelect.ItemIndicator>
  );
}
 
// ItemText component
interface ItemTextProps extends BaseSelect.ItemText.Props {
  className?: string;
}
 
function ItemText({ className, ...props }: ItemTextProps) {
  return (
    <BaseSelect.ItemText
      className={clsx(styles.itemText, className)}
      {...props}
    />
  );
}
 
// Group component
interface GroupProps extends BaseSelect.Group.Props {
  className?: string;
}
 
function Group({ className, ...props }: GroupProps) {
  return (
    <BaseSelect.Group className={clsx(styles.group, className)} {...props} />
  );
}
 
// GroupLabel component
interface GroupLabelProps extends BaseSelect.GroupLabel.Props {
  className?: string;
}
 
function GroupLabel({ className, ...props }: GroupLabelProps) {
  return (
    <BaseSelect.GroupLabel
      className={clsx(styles.groupLabel, className)}
      {...props}
    />
  );
}
 
// Separator component
interface SeparatorProps extends BaseSelect.Separator.Props {
  className?: string;
}
 
function Separator({ className, ...props }: SeparatorProps) {
  return (
    <BaseSelect.Separator
      className={clsx(styles.separator, className)}
      {...props}
    />
  );
}
 
// Backdrop component
const Backdrop = BaseSelect.Backdrop;
 
const Select = {
  Root,
  Trigger,
  Value,
  Icon,
  Portal,
  Backdrop,
  Positioner,
  Popup,
  Item,
  ItemIndicator,
  ItemText,
  Group,
  GroupLabel,
  Separator,
};
 
export default Select;

Use Cases

Simple select

A basic select with a placeholder and a list of options. The trigger shows the selected value or the placeholder when nothing is selected.

Grouped options

Use Select.Group and Select.GroupLabel to organize options into logical groups. Add Select.Separator between groups for visual distinction.

Disabled state

The select can be disabled entirely using the disabled prop on the Root, or individual items can be disabled using the disabled prop on each Item.