MC Select

A dropdown component for selecting one option from a list.

Overview

The mc-select is a core component of the MicroClub UI library. Built on the @base-ui/react select primitive, it combines world-class accessibility with MicroClub's signature styling via Tailwind CSS v4.

As a part of the MicroClub library, this component is designed for instant deployment into your codebase, giving you full ownership of its look and behavior.

Preview

Loading...
'use client';import { UserIcon } from 'lucide-react';import {  McSelect,  McSelectContent,  McSelectGroup,  McSelectGroupLabel,  McSelectItem,  McSelectLabel,  McSelectTrigger,  McSelectValue,} from '@/registry/ui/mc-select';export default function McSelectDemo() {  return (    <div className="w-fit">      <McSelect defaultValue="designer">        <McSelectLabel>Role</McSelectLabel>        <McSelectTrigger>          <McSelectValue placeholder="Select a role" />        </McSelectTrigger>        <McSelectContent>          <McSelectGroup>            <McSelectGroupLabel>Team roles</McSelectGroupLabel>            <McSelectItem value="designer" supportingText="Design interfaces">              <span className="flex items-center gap-2">                <UserIcon className="h-4 w-4" />                Designer              </span>            </McSelectItem>            <McSelectItem value="developer" supportingText="Build product features">              Developer            </McSelectItem>            <McSelectItem value="product" supportingText="Define product strategy">              Product            </McSelectItem>          </McSelectGroup>        </McSelectContent>      </McSelect>    </div>  );}

Add to Your Project

Deploy the mc-select directly to your components/ui directory:

npx mcoli-ui@latest add mc-select
pnpm dlx mcoli-ui@latest add mc-select
yarn dlx mcoli-ui@latest add mc-select
bunx mcoli-ui@latest add mc-select

Manual Setup

If you prefer manual control:

Install the core dependencies
npm install @base-ui/react lucide-react
pnpm add @base-ui/react lucide-react
yarn add @base-ui/react lucide-react
bun add @base-ui/react lucide-react
Copy the source code
'use client';import * as React from 'react';import { Select as SelectPrimitive } from '@base-ui/react/select';import { CheckIcon, ChevronDownIcon, ChevronUpIcon, SearchIcon } from 'lucide-react';import { cn } from '@/lib/utils';export type McSelectVariant =  | 'default'  | 'icon-leading'  | 'avatar-leading'  | 'dot-leading'  | 'search';const McSelect = SelectPrimitive.Root;function McSelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {  return (    <SelectPrimitive.Group      data-slot="select-group"      className={cn('scroll-my-1', className)}      {...props}    />  );}function McSelectValue({ className, ...props }: SelectPrimitive.Value.Props) {  return (    <SelectPrimitive.Value      data-slot="select-value"      className={cn('flex flex-1 text-left', className)}      {...props}    />  );}function McSelectLabel({ className, children }: { className?: string; children: React.ReactNode }) {  return (    <label      className={cn(        'mb-1.5 block text-sm font-medium leading-5',        'text-muted-foreground',        className      )}    >      {children}    </label>  );}interface McSelectTriggerProps extends SelectPrimitive.Trigger.Props {  size?: 'sm' | 'default';  variant?: McSelectVariant;  leadingIcon?: React.ReactNode;  leadingAvatar?: React.ReactNode;  dotColor?: string;}function McSelectTrigger({  className,  size = 'default',  variant = 'default',  leadingIcon,  leadingAvatar,  dotColor,  children,  ...props}: McSelectTriggerProps) {  const isSearch = variant === 'search';  return (    <SelectPrimitive.Trigger      data-slot="select-trigger"      data-size={size}      data-variant={variant}      className={cn(        'flex w-80 items-center gap-2 rounded-lg outline-none select-none',        'px-3.5 py-2.5 text-sm whitespace-nowrap',        'transition-all duration-150',        isSearch          ? ['bg-input', 'border border-border', 'shadow-sm']          : ['bg-input', 'border border-border', 'shadow-sm'],        'data-placeholder:text-muted-foreground',        'data-popup-open:border-ring',        'data-popup-open:ring-4 data-popup-open:ring-ring',        'focus-visible:border-ring',        'focus-visible:ring-4 focus-visible:ring-ring',        'disabled:cursor-not-allowed disabled:opacity-50',        'aria-invalid:border-destructive aria-invalid:ring-4 aria-invalid:ring-destructive',        'data-[size=default]:h-11',        'data-[size=sm]:h-9 data-[size=sm]:rounded-md data-[size=sm]:px-3 data-[size=sm]:py-1.5',        "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",        '*:data-[slot=select-value]:flex *:data-[slot=select-value]:flex-1 *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2',        className      )}      {...props}    >      {variant === 'search' && <SearchIcon className="size-4 shrink-0 text-muted-foreground" />}      {variant === 'icon-leading' && leadingIcon && <McSelectIcons>{leadingIcon}</McSelectIcons>}      {variant === 'avatar-leading' && leadingAvatar && (        <span className="pointer-events-none flex size-5 shrink-0 items-center justify-center overflow-hidden rounded-full">          {leadingAvatar}        </span>      )}      {variant === 'dot-leading' && (        <span className="dot size-2 shrink-0 rounded-full bg-green-500" />      )}      {children}      {!isSearch && (        <SelectPrimitive.Icon          render={            <ChevronDownIcon              className={cn(                'ml-auto size-4 shrink-0 text-muted-foreground',                'transition-transform duration-200',                'data-popup-open:rotate-180'              )}            />          }        />      )}    </SelectPrimitive.Trigger>  );}function McSelectIcons({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) {  return (    <span      data-slot="select-icons"      className={cn(        'pointer-events-none flex shrink-0 items-center justify-center',        'text-muted-foreground',        '[&_svg]:size-4 [&_svg]:shrink-0',        '[&_.dot]:size-2 [&_.dot]:rounded-full',        className      )}      {...props}    />  );}function McSelectContent({  className,  children,  side = 'bottom',  sideOffset = 8,  scrollbar = false,  align = 'center',  alignOffset = 0,  alignItemWithTrigger = true,  ...props}: SelectPrimitive.Popup.Props &  Pick<    SelectPrimitive.Positioner.Props,    'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'  > & {    scrollbar?: boolean;  }) {  return (    <SelectPrimitive.Portal>      <SelectPrimitive.Positioner        side={side}        sideOffset={sideOffset}        align={align}        alignOffset={alignOffset}        alignItemWithTrigger={alignItemWithTrigger}        className="isolate z-50"      >        <SelectPrimitive.Popup          className={cn(            'relative z-50 w-80 h-80',            'rounded-lg',            'bg-card',            'border border-border',            'shadow-lg',            'overflow-hidden',            'data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95',            'data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',            'data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2',            className          )}          {...props}        >          <SelectPrimitive.List            className={cn(              'p-0 h-full overflow-y-auto',              scrollbar && 'overflow-x-auto',              scrollbar                ? 'pr-2 [&::-webkit-scrollbar]:w-[8px] [&::-webkit-scrollbar]:h-[312px] [&::-webkit-scrollbar]:opacity-100 [&::-webkit-scrollbar-thumb]:w-[16px] [&::-webkit-scrollbar-thumb]:h-[320px] [&::-webkit-scrollbar-thumb]:p-[4px] [&::-webkit-scrollbar-thumb]:gap-[10px] [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border mr-1 [&::-webkit-scrollbar-thumb]:opacity-100'                : '[&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]'            )}          >            {children}          </SelectPrimitive.List>        </SelectPrimitive.Popup>      </SelectPrimitive.Positioner>    </SelectPrimitive.Portal>  );}function McSelectGroupLabel({ className, ...props }: SelectPrimitive.GroupLabel.Props) {  return (    <SelectPrimitive.GroupLabel      data-slot="select-label"      className={cn(        'px-2 py-1.5 text-xs font-medium text-muted-foreground tracking-wide',        className      )}      {...props}    />  );}function McSelectItem({  className,  children,  supportingText,  leadingIcon,  leadingAvatar,  dotColor,  ...props}: SelectPrimitive.Item.Props & {  supportingText?: string;  leadingIcon?: React.ReactNode;  leadingAvatar?: React.ReactNode;  dotColor?: string;}) {  return (    <SelectPrimitive.Item      data-slot="select-item"      className={cn(        'group relative flex w-full cursor-default items-center gap-2',        'rounded-none px-2 py-2.5 text-sm outline-none select-none',        'transition-colors duration-100',        'data-highlighted:bg-accent data-highlighted:text-accent-foreground',        'data-highlighted=true:bg-accent data-highlighted=true:text-accent-foreground',        'data-selected:bg-accent data-selected:text-accent-foreground',        'data-selected=true:bg-accent data-selected=true:text-accent-foreground',        'data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground',        'data-[state=checked=true]:bg-accent data-[state=checked=true]:text-accent-foreground',        'data-disabled:pointer-events-none data-disabled:opacity-50',        "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg]:text-muted-foreground",        '[&_.dot]:size-2 [&_.dot]:rounded-full [&_.dot]:shrink-0',        className      )}      {...props}    >      {leadingIcon && (        <span className="pointer-events-none flex size-4 shrink-0 items-center justify-center text-muted-foreground">          {leadingIcon}        </span>      )}      {leadingAvatar && (        <span className="pointer-events-none flex size-5 shrink-0 items-center justify-center overflow-hidden rounded-full">          {leadingAvatar}        </span>      )}      {dotColor && <span className="dot size-2 shrink-0 rounded-full bg-green-500" />}      <SelectPrimitive.ItemText        className={cn(          'flex flex-1 min-w-0 whitespace-nowrap',          supportingText ? 'flex-row items-center gap-1' : 'flex-row items-center gap-2'        )}      >        <span className="text-sm font-medium leading-5 truncate group-data-highlighted:text-accent-foreground group-data-highlighted=true:text-accent-foreground group-data-selected:text-accent-foreground group-data-selected=true:text-accent-foreground group-data-[state=checked]:text-accent-foreground group-data-[state=checked=true]:text-accent-foreground">          {children}        </span>        {supportingText && (          <span className="text-xs text-muted-foreground leading-4 truncate group-data-highlighted:text-accent-foreground group-data-highlighted=true:text-accent-foreground group-data-selected:text-accent-foreground group-data-selected=true:text-accent-foreground group-data-[state=checked]:text-accent-foreground group-data-[state=checked=true]:text-accent-foreground">            {supportingText}          </span>        )}      </SelectPrimitive.ItemText>      <SelectPrimitive.ItemIndicator        render={          <span className="pointer-events-none flex size-4 shrink-0 items-center justify-center text-accent-foreground">            <CheckIcon className="size-4" />          </span>        }      />    </SelectPrimitive.Item>  );}function McSelectSeparator({ className, ...props }: SelectPrimitive.Separator.Props) {  return (    <SelectPrimitive.Separator      data-slot="select-separator"      className={cn('pointer-events-none mx-0 my-1 h-px bg-border', className)}      {...props}    />  );}function McSelectScrollUpButton({  className,  ...props}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {  return (    <SelectPrimitive.ScrollUpArrow      data-slot="select-scroll-up-button"      className={cn(        "top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",        className      )}      {...props}    >      <ChevronUpIcon />    </SelectPrimitive.ScrollUpArrow>  );}function McSelectScrollDownButton({  className,  ...props}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {  return (    <SelectPrimitive.ScrollDownArrow      data-slot="select-scroll-down-button"      className={cn(        "bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4 bg-accent-red-50 ",        className      )}      {...props}    >      <ChevronDownIcon />    </SelectPrimitive.ScrollDownArrow>  );}export {  McSelect,  McSelectContent,  McSelectGroup,  McSelectGroupLabel,  McSelectIcons,  McSelectItem,  McSelectLabel,  McSelectScrollDownButton,  McSelectScrollUpButton,  McSelectSeparator,  McSelectTrigger,  McSelectValue,};
Adjust import paths

Ensure your @/lib/utils or equivalent classname helper is correctly imported.

Usage

import {
  McSelect,
  McSelectContent,
  McSelectGroup,
  McSelectGroupLabel,
  McSelectItem,
  McSelectLabel,
  McSelectTrigger,
  McSelectValue,
} from '@/components/ui/mc-select';

export default function Example() {
  return (
    <McSelect defaultValue="designer">
      <McSelectLabel>Role</McSelectLabel>
      <McSelectTrigger>
        <McSelectValue placeholder="Select a role" />
      </McSelectTrigger>

      <McSelectContent>
        <McSelectGroup>
          <McSelectGroupLabel>Team roles</McSelectGroupLabel>

          <McSelectItem value="designer">Designer</McSelectItem>
          <McSelectItem value="developer">Developer</McSelectItem>
        </McSelectGroup>
      </McSelectContent>
    </McSelect>
  );
}

Examples

Variants

The select trigger supports multiple variants for different use cases.

<McSelect defaultValue="designer">
  <McSelectTrigger variant="default">
    <McSelectValue placeholder="Select a role" />
  </McSelectTrigger>
  <McSelectContent>
    <McSelectItem value="designer">Designer</McSelectItem>
    <McSelectItem value="developer">Developer</McSelectItem>
  </McSelectContent>
</McSelect>

With Icon Leading

Use the icon-leading variant to display an icon before the value.

import { User } from 'lucide-react';

<McSelect defaultValue="designer">
  <McSelectTrigger variant="icon-leading" leadingIcon={<User />}>
    <McSelectValue placeholder="Select a role" />
  </McSelectTrigger>
  <McSelectContent>
    <McSelectItem value="designer">Designer</McSelectItem>
    <McSelectItem value="developer">Developer</McSelectItem>
  </McSelectContent>
</McSelect>;

With Avatar Leading

Use the avatar-leading variant to display an avatar before the value.

import { Avatar } from 'lucide-react';

<McSelect defaultValue="designer">
  <McSelectTrigger variant="avatar-leading" leadingAvatar={<Avatar />}>
    <McSelectValue placeholder="Select a role" />
  </McSelectTrigger>
  <McSelectContent>
    <McSelectItem value="designer">Designer</McSelectItem>
    <McSelectItem value="developer">Developer</McSelectItem>
  </McSelectContent>
</McSelect>;

With Dot Leading

Use the dot-leading variant to display a colored dot before the value.

<McSelect defaultValue="online">
  <McSelectTrigger variant="dot-leading" dotColor="bg-green-500">
    <McSelectValue placeholder="Select status" />
  </McSelectTrigger>
  <McSelectContent>
    <McSelectItem value="online">Online</McSelectItem>
    <McSelectItem value="away">Away</McSelectItem>
    <McSelectItem value="offline">Offline</McSelectItem>
  </McSelectContent>
</McSelect>

Search Variant

The search variant is optimized for search input scenarios.

<McSelect defaultValue="designer">
  <McSelectTrigger variant="search">
    <McSelectValue placeholder="Search roles..." />
  </McSelectTrigger>
  <McSelectContent>
    <McSelectItem value="designer">Designer</McSelectItem>
    <McSelectItem value="developer">Developer</McSelectItem>
    <McSelectItem value="manager">Manager</McSelectItem>
  </McSelectContent>
</McSelect>

With Supporting Text

Add supporting text below each item for additional context.

<McSelect defaultValue="designer">
  <McSelectTrigger>
    <McSelectValue placeholder="Select a role" />
  </McSelectTrigger>
  <McSelectContent>
    <McSelectItem value="designer" supportingText="Creative">
      Designer
    </McSelectItem>
    <McSelectItem value="developer" supportingText="Technical">
      Developer
    </McSelectItem>
    <McSelectItem value="manager" supportingText="Leadership">
      Manager
    </McSelectItem>
  </McSelectContent>
</McSelect>

Sizes

Choose between default and sm sizes.

<McSelect defaultValue="designer">
  <McSelectTrigger size="default">
    <McSelectValue placeholder="Default size" />
  </McSelectTrigger>
  <McSelectContent>
    <McSelectItem value="designer">Designer</McSelectItem>
  </McSelectContent>
</McSelect>

<McSelect defaultValue="designer">
  <McSelectTrigger size="sm">
    <McSelectValue placeholder="Small size" />
  </McSelectTrigger>
  <McSelectContent>
    <McSelectItem value="designer">Designer</McSelectItem>
  </McSelectContent>
</McSelect>

Disabled Item

Selected items are automatically disabled to prevent re-selection.

<McSelect defaultValue="designer">
  <McSelectTrigger>
    <McSelectValue placeholder="Select a role" />
  </McSelectTrigger>
  <McSelectContent>
    <McSelectItem value="designer">Designer (current)</McSelectItem>
    <McSelectItem value="developer">Developer</McSelectItem>
    <McSelectItem value="manager">Manager</McSelectItem>
  </McSelectContent>
</McSelect>

With Separator

Group items with a separator for visual separation.

<McSelect defaultValue="designer">
  <McSelectTrigger>
    <McSelectValue placeholder="Select a role" />
  </McSelectTrigger>
  <McSelectContent>
    <McSelectGroup>
      <McSelectGroupLabel>Team roles</McSelectGroupLabel>
      <McSelectItem value="designer">Designer</McSelectItem>
      <McSelectItem value="developer">Developer</McSelectItem>
    </McSelectGroup>
    <McSelectSeparator />
    <McSelectGroup>
      <McSelectGroupLabel>Lead roles</McSelectGroupLabel>
      <McSelectItem value="manager">Manager</McSelectItem>
      <McSelectItem value="director">Director</McSelectItem>
    </McSelectGroup>
  </McSelectContent>
</McSelect>

API Reference

McSelect Props

PropTypeDefaultDescription
defaultValuestringundefinedThe default selected value
valuestringundefinedThe controlled selected value
onValueChange(value: string) => voidundefinedCallback when value changes
classNamestringundefinedAdditional CSS classes

All other props from @base-ui/react/select are supported.

McSelectTrigger Props

PropTypeDefaultDescription
size"sm" | "default""default"The size of the trigger
variant"default" | "icon-leading" | "avatar-leading" | "dot-leading" | "search""default"The visual variant
leadingIconReactNodeundefinedIcon to display (for icon-leading variant)
leadingAvatarReactNodeundefinedAvatar to display (for avatar-leading variant)
dotColorstringundefinedColor class for the dot (for dot-leading variant)
classNamestringundefinedAdditional CSS classes

All other props from @base-ui/react/select are supported.

McSelectContent Props

PropTypeDefaultDescription
side"top" | "bottom""bottom"The side to open the popup from
sideOffsetnumber8The offset in pixels from the trigger
align"start" | "center" | "end""center"The alignment of the popup
alignOffsetnumber0The offset in pixels from the alignment
alignItemWithTriggerbooleantrueWhether to align the popup with the trigger
scrollbarbooleanfalseWhether to show the scrollbar
classNamestringundefinedAdditional CSS classes

All other props from @base-ui/react/select are supported.

McSelectItem Props

PropTypeDefaultDescription
valuestring-The value of the item
supportingTextstringundefinedSupporting text to display below the label
leadingIconReactNodeundefinedIcon to display before the label
leadingAvatarReactNodeundefinedAvatar to display before the label
dotColorstringundefinedColor class for the dot
classNamestringundefinedAdditional CSS classes

The selected item is automatically styled with a blue background and becomes non-clickable.

All other props from @base-ui/react/select are supported.

McSelectGroupLabel Props

PropTypeDefaultDescription
classNamestringundefinedAdditional CSS classes

McSelectLabel Props

PropTypeDefaultDescription
classNamestringundefinedAdditional CSS classes
childrenReactNode-Label content

McSelectValue Props

PropTypeDefaultDescription
placeholderstringundefinedPlaceholder text when empty
classNamestringundefinedAdditional CSS classes

McSelectSeparator Props

PropTypeDefaultDescription
classNamestringundefinedAdditional CSS classes

On this page