Overview
The Dropdown component is built from composable pieces:Dropdown.Rootmanages open state, selection, and keyboard interaction.Dropdown.Selectrenders the trigger button with label, helper text, and icons.Dropdown.PopoverandDropdown.Menudisplay the list of options with proper focus management.Dropdown.Sectiongroups options and can render checkbox-style multi selection.Dropdown.Itemregisters an option and handles selection feedback.
Basic Usage
- Code
- Preview
import { useState } from 'react';
import { Dropdown } from '@peppermint-design/devreadykit-custom';
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-custom';
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
UseDropdown.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-custom';
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 multipleDropdown.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-custom';
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
| Component | Prop | Type | Default | Description |
|---|---|---|---|---|
| Root | value | string | number | (string | number)[] | null | — | Controlled selected value(s); pass an array when multiple is set |
| Root | defaultValue | string | number | (string | number)[] | null | — | Uncontrolled initial value(s) |
| Root | onChange | (value: string | number | null) => void || (values: (string | number)[]) => void | — | Fired when the selection changes; signature depends on multiple |
| Root | multiple | boolean | false | Enables multiple selection mode |
| Root | className | string | — | Applies custom classes to the wrapper |
| Select | label | React.ReactNode | — | Optional field label rendered above the trigger |
| Select | required | boolean | false | Shows an asterisk next to the label |
| Select | placeholder | string | "Select" | Placeholder text when nothing is selected |
| Select | startIcon | React.ReactNode | — | Icon displayed on the left of the trigger |
| Select | helperText | React.ReactNode | — | Helper text shown below the trigger |
| Select | errorMessage | React.ReactNode | — | Error text shown below the trigger with error styling |
| Select | className | string | — | Additional classes for the trigger button |
| Popover | className | string | — | Styles for the popover container |
| Popover | children | React.ReactNode | — | Popover contents, usually a Dropdown.Menu |
| Menu | className | string | — | Styles for the menu surface |
| Menu | maxHeight | number | 320 | Sets the max height before the menu scrolls |
| Menu | children | React.ReactNode | — | Menu sections and other content |
| Section | className | string | — | Adds spacing or borders around a section |
| Section | variant | "default" | "checkbox" | "default" | Renders checkbox visuals when set to "checkbox" |
| Section | children | React.ReactNode | — | Section content such as Dropdown.Item elements |
| Item | value | string | number | — | Value emitted on selection; defaults to the text content |
| Item | disabled | boolean | false | Disables the option |
| Item | startIcon | React.ReactNode | — | Optional leading icon when variant="default" |
| Item | className | string | — | Custom classes for the option |
| Item | children | React.ReactNode | — | Option label/content |
Examples
Multi-Select with Custom Display
- Code
- Preview
import { useState } from 'react';
import { Dropdown } from '@peppermint-design/devreadykit-custom';
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>
);
}