Drill down menu
FreeA menu that goes deeper one level at a time: the next level slides in, Back slides it out, and the height eases to fit.
pnpm dlx shadcn@latest add https://caliperui.site/r/drill-down-menu.jsonNothing chosen yet
When to use #
Use it for
- Settings and nested menus on phones and in popovers.
- Hierarchies three or four levels deep.
Skip it when
- Flat menus; use a dropdown.
Installation #
terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/drill-down-menu.json
Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
example.tsx
import { Bell, Globe, Lock, Palette, Shield } from "lucide-react" import { useState } from "react" import { DrillDownMenu } from "@/components/ui/drill-down-menu" export function DrillDownMenuDemo() { const [last, setLast] = useState("Nothing chosen yet"); const pick = (what: string) => () => setLast(what); return ( <div className="grid w-full max-w-xs gap-3"> <DrillDownMenu label="Settings" items={[ { id: "appearance", label: "Appearance", icon: <Palette />, hint: "System", items: [ { id: "light", label: "Light", onSelect: pick("Appearance: Light") }, { id: "dark", label: "Dark", onSelect: pick("Appearance: Dark") }, { id: "system", label: "Match the system", onSelect: pick("Appearance: System") }, ] }, { id: "notifications", label: "Notifications", icon: <Bell />, hint: "On", items: [ { id: "email", label: "Email", hint: "Daily", items: [ { id: "instant", label: "As it happens", onSelect: pick("Email: as it happens") }, { id: "daily", label: "Daily digest", onSelect: pick("Email: daily digest") }, { id: "off", label: "Off", onSelect: pick("Email: off") }, ] }, { id: "push", label: "Push", hint: "Mentions", onSelect: pick("Push: mentions") }, ] }, { id: "privacy", label: "Privacy", icon: <Shield />, items: [ { id: "profile", label: "Who sees your profile", hint: "Team", onSelect: pick("Profile: team") }, { id: "activity", label: "Show activity status", hint: "On", onSelect: pick("Activity status: on") }, ] }, { id: "language", label: "Language", icon: <Globe />, hint: "English", onSelect: pick("Language: English") }, { id: "security", label: "Sign out everywhere", icon: <Lock />, onSelect: pick("Signed out of other devices") }, ]} /> <p className="text-xs text-muted-foreground" role="status">{last}</p> </div> ); }
API reference #
DrillDownMenu
component
PropTypeDefault
items *readonly DrillItem[]—
label *string—
classNamestring—
Accessibility #
It is built on native elements and keeps aria-label and aria-hidden in step with state.
KeyBehaviour
↓Moves to the next item or value.
↑Moves to the previous item or value.
←Moves to the previous item or value.
EscapeCloses or cancels, then returns focus to where it came from.
→Moves to the next item or value.
Motion #
It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.