Components
Base Components
Chip Menu
A select control that looks like a chip (badge) and opens a dropdown menu. Built from Badge and DropdownMenu.
Example
The chip menu shows the current selection (or a placeholder) in a Badge and opens a dropdown when clicked.Installation
Install the following dependencies:
npm install lucide-reactCreate a chip-menu.tsx file and paste the following code into it.
"use client";import * as React from "react";import { ChevronDownIcon, ChevronLeftIcon, PlusCircleIcon } from "lucide-react";import { Badge } from "@/components/ui/badge";import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger,} from "@/components/ui/dropdown-menu";import { cn } from "@/lib/utils";export interface ChipMenuOption { value: string; label: React.ReactNode; disabled?: boolean;}export interface ChipMenuProps { /** Currently selected value (must match an option's value). */ value: string | null; /** Callback when selection changes. */ onValueChange: (value: string) => void; /** Menu options. */ options: ChipMenuOption[]; /** Placeholder when no value is selected. */ placeholder?: string; /** Badge variant. */ variant?: React.ComponentProps<typeof Badge>["variant"]; /** Optional class for the trigger badge. */ className?: string; /** Optional class for the dropdown content. */ contentClassName?: string; /** Whether the chip is disabled. */ disabled?: boolean;}function ChipMenu({ value, onValueChange, options, placeholder = "Select…", variant = "default", className, contentClassName, disabled = false,}: ChipMenuProps) { const selected = options.find((o) => o.value === value); const label = selected ? selected.label : ""; const [open, setOpen] = React.useState(false); const handleReset = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); onValueChange(""); }; return ( <DropdownMenu open={open} onOpenChange={setOpen}> {label ? ( <Badge variant={variant} className={cn( "py-[4px] cursor-pointer gap-1 pr-1 transition-opacity hover:opacity-90 data-[state=open]:opacity-90", !selected && "text-muted-foreground", className )} > <span className="px-[2px] text-chip-menu-placeholder" onClick={handleReset} > <PlusCircleIcon className={cn("size-3 opacity-70 transition-transform", label && "rotate-45")} />{" "} </span> {placeholder && ( <span className="text-chip-menu-placeholder">{placeholder}</span> )} <span className="px-[6px] text-chip-menu-placeholder">|</span> <DropdownMenuTrigger disabled={disabled} className="inline-flex items-center gap-1 h-auto border-0 bg-transparent p-0 outline-none focus:ring-0 focus-visible:ring-0" > <span className="text-primary">{label}</span> <ChevronDownIcon className="size-3 opacity-70" /> </DropdownMenuTrigger> </Badge> ) : ( <DropdownMenuTrigger disabled={disabled} className="inline-flex h-auto border-0 bg-transparent p-0 outline-none focus:ring-0 focus-visible:ring-0" > <Badge variant={variant} className={cn( "py-[4px] cursor-pointer gap-1 pr-1 transition-opacity hover:opacity-90 data-[state=open]:opacity-90", !selected && "text-muted-foreground", className )} > <span className="px-[2px] text-chip-menu-placeholder"> <PlusCircleIcon className="size-3 opacity-70" />{" "} </span> {placeholder && ( <span className="text-chip-menu-placeholder">{placeholder}</span> )} <span className="text-primary">{label}</span> <ChevronDownIcon className="size-3 opacity-70" /> </Badge> </DropdownMenuTrigger> )} <DropdownMenuContent className={contentClassName} align="start"> {options.map((opt) => ( <DropdownMenuItem key={opt.value} disabled={opt.disabled} onSelect={() => onValueChange(opt.value)} > {opt.label} </DropdownMenuItem> ))} </DropdownMenuContent> </DropdownMenu> );}export { ChipMenu };Check the import paths to ensure they match your project setup.
Usage
Control the selected value withvalue and onValueChange. Pass options as an array of { value, label, disabled? }.
import { ChipMenu } from "@/components/ui/chip-menu";
const [status, setStatus] = useState<string | null>(null);
<ChipMenu
value={status}
onValueChange={setStatus}
options={[
{ value: "new", label: "New" },
{ value: "active", label: "Active" },
{ value: "done", label: "Done" },
]}
placeholder="Status"
variant="default"
/>Examples
With placeholder
When no value is selected, the chip shows the placeholder in muted text.Variants
Use thevariant prop to match Badge variants (default, secondary, outline, etc.).
API Reference
ChipMenu
value*string | null
Default: -
Currently selected value (must match an option's value).
onValueChange*(value: string) => void
Default: -
Callback when selection changes.
options*ChipMenuOption[]
Default: -
Menu options.
placeholderstring
Default: Select…
Placeholder when no value is selected.
variant"link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | "info" | "positive" | "negative" | "warning" | "urgent" | null
Default: default
Badge variant.
classNamestring
Default: -
Optional class for the trigger badge.
contentClassNamestring
Default: -
Optional class for the dropdown content.
disabledboolean
Default: false
Whether the chip is disabled.
| Prop | Type | Default | Description |
|---|---|---|---|
| value* | string | null | - | Currently selected value (must match an option's value). |
| onValueChange* | (value: string) => void | - | Callback when selection changes. |
| options* | ChipMenuOption[] | - | Menu options. |
| placeholder | string | Select… | Placeholder when no value is selected. |
| variant | "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | "info" | "positive" | "negative" | "warning" | "urgent" | null | default | Badge variant. |
| className | string | - | Optional class for the trigger badge. |
| contentClassName | string | - | Optional class for the dropdown content. |
| disabled | boolean | false | Whether the chip is disabled. |
| Prop | Type | Description |
|---|---|---|
value | string | null | Currently selected option value. |
onValueChange | (value: string) => void | Called when the user picks an option. |
options | ChipMenuOption[] | { value, label, disabled? }[]. |
placeholder | string | Shown when no value is selected. Default: "Select…". |
variant | Badge variant | Badge style. Default: "default". |
className | string | Optional class for the trigger badge. |
contentClassName | string | Optional class for the dropdown content. |
disabled | boolean | Disables the chip and menu. |