Dropdown Menu
Menu surface with signal-wash highlights and destructive items.
[ interactive — use the trigger to open ]
"use client";import { useState } from "react";import { CrosshairIcon, RadioIcon, SatelliteIcon, TrashIcon } from "lucide-react";import { Button } from "@/registry/okibi/ui/button";import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger,} from "@/registry/okibi/ui/dropdown-menu";export default function DropdownMenuDemo() { const [autoTrace, setAutoTrace] = useState(true); return ( <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant="outline-tech">Sector Ops</Button> </DropdownMenuTrigger> <DropdownMenuContent align="start"> <DropdownMenuLabel>SEC // 0x4A2</DropdownMenuLabel> <DropdownMenuItem> <CrosshairIcon /> Breach Protocol </DropdownMenuItem> <DropdownMenuItem> <SatelliteIcon /> Trace Route </DropdownMenuItem> <DropdownMenuCheckboxItem checked={autoTrace} onCheckedChange={setAutoTrace} > <RadioIcon /> Auto-Trace </DropdownMenuCheckboxItem> <DropdownMenuSeparator /> <DropdownMenuItem variant="destructive"> <TrashIcon /> Purge Logs </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> );}npx shadcn@latest add https://okibi.cndr.dev/r/dropdown-menu.jsonInstallation
Install the theme first, then add the component:
npx shadcn@latest add https://okibi.cndr.dev/r/dropdown-menu.jsonInstall the required dependencies:
npm install @radix-ui/react-dropdown-menu lucide-reactCopy the source from the Code tab above into components/ui/dropdown-menu.tsx. It uses the cn helper and the okibi theme tokens — install the theme first if you haven't.
Usage
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
<DropdownMenu>
<DropdownMenuTrigger>Open</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>Purge logs</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>Anatomy
<DropdownMenu>
<DropdownMenuTrigger />
<DropdownMenuContent>
<DropdownMenuLabel /> {/* group caption (non-interactive) */}
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
Action <DropdownMenuShortcut /> {/* trailing mono hotkey */}
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSub> {/* nested submenu */}
<DropdownMenuSubTrigger />
<DropdownMenuSubContent />
</DropdownMenuSub>
</DropdownMenuContent>
</DropdownMenu>Examples
Checkboxes
DropdownMenuCheckboxItem keeps multi-select options (view toggles, filters) inside the menu, each with its own checked state.
[ interactive — use the trigger to open ]
"use client";import { useState } from "react";import { Button } from "@/registry/okibi/ui/button";import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger,} from "@/registry/okibi/ui/dropdown-menu";export default function DropdownMenuCheckboxesDemo() { const [telemetry, setTelemetry] = useState(true); const [grid, setGrid] = useState(false); const [scanlines, setScanlines] = useState(true); return ( <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant="outline-tech">View</Button> </DropdownMenuTrigger> <DropdownMenuContent align="start"> <DropdownMenuLabel>HUD // Layers</DropdownMenuLabel> <DropdownMenuSeparator /> <DropdownMenuCheckboxItem checked={telemetry} onCheckedChange={setTelemetry} > Telemetry </DropdownMenuCheckboxItem> <DropdownMenuCheckboxItem checked={grid} onCheckedChange={setGrid}> Grid </DropdownMenuCheckboxItem> <DropdownMenuCheckboxItem checked={scanlines} onCheckedChange={setScanlines} > Scanlines </DropdownMenuCheckboxItem> </DropdownMenuContent> </DropdownMenu> );}Radio group
DropdownMenuRadioGroup binds a set of DropdownMenuRadioItems to a single value for mutually-exclusive choices.
[ interactive — use the trigger to open ]
"use client";import { useState } from "react";import { Button } from "@/registry/okibi/ui/button";import { DropdownMenu, DropdownMenuContent, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger,} from "@/registry/okibi/ui/dropdown-menu";export default function DropdownMenuRadioGroupDemo() { const [channel, setChannel] = useState("CH-02"); return ( <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant="outline-tech">Channel</Button> </DropdownMenuTrigger> <DropdownMenuContent align="start"> <DropdownMenuLabel>Signal // Band</DropdownMenuLabel> <DropdownMenuSeparator /> <DropdownMenuRadioGroup value={channel} onValueChange={setChannel}> <DropdownMenuRadioItem value="CH-01">CH-01</DropdownMenuRadioItem> <DropdownMenuRadioItem value="CH-02">CH-02</DropdownMenuRadioItem> <DropdownMenuRadioItem value="CH-03">CH-03</DropdownMenuRadioItem> </DropdownMenuRadioGroup> </DropdownMenuContent> </DropdownMenu> );}Submenu
The full menu surface — a nested DropdownMenuSub, DropdownMenuShortcuts, an inset item, and a labeled group.
[ interactive — use the trigger to open ]
"use client";import { ArchiveIcon, RadarIcon, ShareIcon, ZapIcon } from "lucide-react";import { Button } from "@/registry/okibi/ui/button";import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger,} from "@/registry/okibi/ui/dropdown-menu";export default function DropdownMenuSubmenuDemo() { return ( <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant="outline-tech">Signal Relay</Button> </DropdownMenuTrigger> <DropdownMenuContent align="start"> <DropdownMenuLabel>RELAY // 0x7F1</DropdownMenuLabel> <DropdownMenuGroup> <DropdownMenuItem> <ZapIcon /> Pulse Burst <DropdownMenuShortcut>⌘P</DropdownMenuShortcut> </DropdownMenuItem> <DropdownMenuItem> <RadarIcon /> Sweep <DropdownMenuShortcut>⌘S</DropdownMenuShortcut> </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuSub> <DropdownMenuSubTrigger> <ShareIcon /> Broadcast To </DropdownMenuSubTrigger> <DropdownMenuSubContent> <DropdownMenuItem>Uplink Alpha</DropdownMenuItem> <DropdownMenuItem>Uplink Bravo</DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem> <ArchiveIcon /> Cold Vault </DropdownMenuItem> </DropdownMenuSubContent> </DropdownMenuSub> <DropdownMenuItem inset>Sever Link</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> );}API Reference
DropdownMenuContent
| Prop | Type | Default | Description |
|---|---|---|---|
sideOffset | number | 4 | Gap in pixels between the trigger and the menu. |
align | "start" | "center" | "end" | "start" | Alignment against the trigger (Radix default). |
DropdownMenuItem
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "destructive" | "default" | destructive washes scarlet on focus for irreversible actions. |
inset | boolean | false | Pad the left edge to align with items that carry an indicator. |
disabled | boolean | false | Skip the item in keyboard navigation and dim it. |
onSelect | (event: Event) => void | – | Fires when the item is chosen. |
DropdownMenuCheckboxItem
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | – | Controlled checked state; renders the indicator. |
onCheckedChange | (checked: boolean) => void | – | Fires when toggled. |
DropdownMenuRadioGroup
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | – | The selected DropdownMenuRadioItem value. |
onValueChange | (value: string) => void | – | Fires when a radio item is chosen. |
DropdownMenuLabel
| Prop | Type | Default | Description |
|---|---|---|---|
inset | boolean | false | Pad the left edge to align with indicator items. |
DropdownMenuSubTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
inset | boolean | false | Pad the left edge to align with indicator items. |
Accessibility
- Radix manages roving focus, type-ahead, arrow-key navigation, and
Escapeto close. DropdownMenuLabelis a non-interactive caption — it is not focusable and announces as a group label, not a command.DropdownMenuCheckboxItemandDropdownMenuRadioItemexpose their checked state to assistive tech via Radix item indicators.- Disabled items (
data-[disabled]) are skipped by keyboard navigation and dimmed toopacity-60. - Destructive items use
text-destructive-sparkso the scarlet reads at WCAG AA on the popover surface.
Guidelines
Do
- Group related commands and split sections with
DropdownMenuSeparator; caption a group withDropdownMenuLabel. - Mark the
variant='destructive'on irreversible items so they wash scarlet on focus. - Pair frequent items with a
DropdownMenuShortcutthat mirrors a real keybinding.
Don't
- Don't reach for a dropdown menu when you need form fields or rich content — use a
PopoverorDialog. - Don't bury primary navigation in a submenu; reserve
DropdownMenuSubfor secondary, lower-traffic actions. - Don't rely on
DropdownMenuLabelas a control — it can't be focused or activated.