Skip to main content

Overview

The Dropdown component is built from composable pieces:
  • Dropdown.Root manages open state, selection, and keyboard interaction.
  • Dropdown.Select renders the trigger button with label, helper text, and icons.
  • Dropdown.Popover and Dropdown.Menu display the list of options with proper focus management.
  • Dropdown.Section groups options and can render checkbox-style multi selection.
  • Dropdown.Item registers an option and handles selection feedback.
Combine these primitives to build controlled or uncontrolled dropdowns with single or multiple selection.

Basic Usage

  • Code
  • Preview
import { useState } from 'react';
import { Dropdown } from '@peppermint-design/devreadykit';

const OPTIONS = [
  { value: 'option-1', label: 'Option 1' },
  { value: 'option-2', label: 'Option 2' },
  { value: 'option-3', label: 'Option 3' },
];

export default function App() {
  const [value, setValue] = useState<string | null>(null);

  return (
    <Dropdown.Root value={value} onChange={setValue} className="w-full max-w-xs">
      <Dropdown.Select
          label="Country/region"
          placeholder="Select country"
          helperText="Used for taxes and shipping."
      />
      <Dropdown.Popover>
        <Dropdown.Menu>
          <Dropdown.Section>
            {OPTIONS.map((option) => (
              <Dropdown.Item key={option.value} value={option.value}>
                {option.label}
              </Dropdown.Item>
            ))}
          </Dropdown.Section>
        </Dropdown.Menu>
      </Dropdown.Popover>
    </Dropdown.Root>
  );
}

Single Selection

  • Code
  • Preview
import { useState } from 'react';
import { Dropdown } from '@peppermint-design/devreadykit';

const COUNTRIES = [
  { value: 'us', label: 'United States' },
  { value: 'ca', label: 'Canada' },
  { value: 'uk', label: 'United Kingdom' },
  { value: 'de', label: 'Germany' },
  { value: 'fr', label: 'France' },
];

export default function CountrySelector() {
  const [country, setCountry] = useState<string | null>(null);

  return (
    <Dropdown.Root value={country} onChange={setCountry} className="w-full max-w-xs">
      <Dropdown.Select label="Country" placeholder="Select country" helperText="Pick one option" />
      <Dropdown.Popover>
        <Dropdown.Menu>
          <Dropdown.Section>
            {COUNTRIES.map((item) => (
              <Dropdown.Item key={item.value} value={item.value}>
                {item.label}
              </Dropdown.Item>
            ))}
          </Dropdown.Section>
        </Dropdown.Menu>
      </Dropdown.Popover>
    </Dropdown.Root>
  );
}

Multiple Selection

Use Dropdown.Root with multiple and render a Dropdown.Section with variant="checkbox" to display checkbox affordances. The trigger automatically shows the number of selected items, so render a summary elsewhere if you need labels.
  • Code
  • Preview
import { useState } from 'react';
import { Dropdown } from '@peppermint-design/devreadykit';

const TAGS = ['React', 'Vue', 'Angular', 'Svelte', 'Solid'];

export default function TagSelector() {
  const [tags, setTags] = useState<string[]>([]);

  return (
    <div className="space-y-3">
      <Dropdown.Root multiple value={tags} onChange={setTags} className="w-full max-w-xs">
        <Dropdown.Select label="Tags" placeholder="Select tags" helperText="Choose one or more tags" />
        <Dropdown.Popover>
          <Dropdown.Menu>
            <Dropdown.Section variant="checkbox">
              {TAGS.map((tag) => (
                <Dropdown.Item key={tag} value={tag}>
                  {tag}
                </Dropdown.Item>
              ))}
            </Dropdown.Section>
          </Dropdown.Menu>
        </Dropdown.Popover>
      </Dropdown.Root>

      {tags.length > 0 && (
        <div className="flex flex-wrap gap-2 text-sm text-neutral-40">
          {tags.map((tag) => (
            <span key={tag} className="rounded-full bg-primary-95 px-2 py-1 text-primary-50">
              {tag}
            </span>
          ))}
        </div>
      )}
    </div>
  );
}

With Groups

Render multiple Dropdown.Section blocks to visually separate categories. Add custom headings within a section as needed.
  • Code
  • Preview
import { useState } from 'react';
import { Dropdown } from '@peppermint-design/devreadykit';

const CATEGORIES = {
  Frontend: [
    { value: 'react', label: 'React' },
    { value: 'vue', label: 'Vue' },
    { value: 'angular', label: 'Angular' },
  ],
  Backend: [
    { value: 'node', label: 'Node.js' },
    { value: 'python', label: 'Python' },
    { value: 'go', label: 'Go' },
  ],
  Database: [
    { value: 'postgres', label: 'PostgreSQL' },
    { value: 'mysql', label: 'MySQL' },
    { value: 'mongodb', label: 'MongoDB' },
  ],
} as const;

export default function GroupedDropdown() {
  const [category, setCategory] = useState<string | null>(null);

  return (
    <Dropdown.Root value={category} onChange={setCategory} className="w-full max-w-xs">
      <Dropdown.Select label="Category" placeholder="Select a category" />
      <Dropdown.Popover className="w-[260px]">
        <Dropdown.Menu>
          {Object.entries(CATEGORIES).map(([groupLabel, items]) => (
            <Dropdown.Section key={groupLabel}>
              <p className="px-100 pb-100 text-200 font-semibold uppercase tracking-wide text-neutral-40">
                {groupLabel}
              </p>
              {items.map((item) => (
                <Dropdown.Item key={item.value} value={item.value}>
                  {item.label}
                </Dropdown.Item>
              ))}
            </Dropdown.Section>
          ))}
        </Dropdown.Menu>
      </Dropdown.Popover>
    </Dropdown.Root>
  );
}

Props

ComponentPropTypeDefaultDescription
Rootvaluestring | number | (string | number)[] | nullControlled selected value(s); pass an array when multiple is set
RootdefaultValuestring | number | (string | number)[] | nullUncontrolled initial value(s)
RootonChange(value: string | number | null) => void || (values: (string | number)[]) => voidFired when the selection changes; signature depends on multiple
RootmultiplebooleanfalseEnables multiple selection mode
RootclassNamestringApplies custom classes to the wrapper
SelectlabelReact.ReactNodeOptional field label rendered above the trigger
SelectrequiredbooleanfalseShows an asterisk next to the label
Selectplaceholderstring"Select"Placeholder text when nothing is selected
SelectstartIconReact.ReactNodeIcon displayed on the left of the trigger
SelecthelperTextReact.ReactNodeHelper text shown below the trigger
SelecterrorMessageReact.ReactNodeError text shown below the trigger with error styling
SelectclassNamestringAdditional classes for the trigger button
PopoverclassNamestringStyles for the popover container
PopoverchildrenReact.ReactNodePopover contents, usually a Dropdown.Menu
MenuclassNamestringStyles for the menu surface
MenumaxHeightnumber320Sets the max height before the menu scrolls
MenuchildrenReact.ReactNodeMenu sections and other content
SectionclassNamestringAdds spacing or borders around a section
Sectionvariant"default" | "checkbox""default"Renders checkbox visuals when set to "checkbox"
SectionchildrenReact.ReactNodeSection content such as Dropdown.Item elements
Itemvaluestring | numberValue emitted on selection; defaults to the text content
ItemdisabledbooleanfalseDisables the option
ItemstartIconReact.ReactNodeOptional leading icon when variant="default"
ItemclassNamestringCustom classes for the option
ItemchildrenReact.ReactNodeOption label/content

Examples

Multi-Select with Custom Display

  • Code
  • Preview
import { useState } from 'react';
import { Dropdown } from '@peppermint-design/devreadykit';

const skills = ['JavaScript', 'TypeScript', 'React', 'Node.js', 'Python', 'Go'];

export default function SkillsSelector() {
  const [selected, setSelected] = useState<string[]>([]);

  return (
    <div className="space-y-3">
      <label className="block text-sm font-medium text-neutral-40">Skills</label>

      <Dropdown.Root multiple value={selected} onChange={setSelected} className="w-full max-w-xs">
        <Dropdown.Select placeholder="Select skills" helperText="Pick the technologies you work with" />
        <Dropdown.Popover>
          <Dropdown.Menu>
            <Dropdown.Section variant="checkbox">
              {skills.map((skill) => (
                <Dropdown.Item key={skill} value={skill}>
                  {skill}
                </Dropdown.Item>
              ))}
            </Dropdown.Section>
          </Dropdown.Menu>
        </Dropdown.Popover>
      </Dropdown.Root>

      {selected.length > 0 && (
        <div className="flex flex-wrap gap-2 text-sm text-neutral-40">
          {selected.map((skill) => (
            <span key={skill} className="inline-flex items-center gap-1 rounded-full bg-primary-95 px-2 py-1 text-primary-50">
              {skill}
            </span>
          ))}
        </div>
      )}
    </div>
  );
}
I