ComponentsOverlays
Drawer
A panel that slides in from any edge of the window, with a bottom sheet you can drag down to dismiss.
Filters
"use client";
import { SlidersHorizontal } from "lucide-react";
import { useState } from "react";
import { Badge } from "@/components/miyuki-ui/badge";
import { Button } from "@/components/miyuki-ui/button";
import { Checkbox, CheckboxGroup } from "@/components/miyuki-ui/checkbox";
import {
Drawer,
DrawerBody,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/miyuki-ui/drawer";
const groups = [
{ id: "grade", label: "Grade", options: ["Easy", "Moderate", "Hard", "Expert"] },
{ id: "season", label: "Best in", options: ["Winter", "Spring", "Summer", "Autumn"] },
{ id: "along", label: "Along the way", options: ["Mountain hut", "Hot spring", "Train station at the end"] },
];
type Filters = Record<string, string[]>;
const empty: Filters = { grade: [], season: [], along: [] };
export default function DrawerDemo() {
const [applied, setApplied] = useState<Filters>({ ...empty, grade: ["Moderate"], season: ["Winter"] });
const [draft, setDraft] = useState<Filters>(applied);
const count = Object.values(applied).flat().length;
return (
<DrawerTrigger onOpenChange={(open) => open && setDraft(applied)}>
<Button
variant="secondary"
iconStart={<SlidersHorizontal />}
iconEnd={
count > 0 ? (
<Badge size="sm" tone="brand">
{count}
</Badge>
) : undefined
}
>
Filters
</Button>
<Drawer>
{({ close }) => (
<>
<DrawerHeader>
<DrawerTitle>Filters</DrawerTitle>
<p className="text-[13px] text-muted-foreground">Routes in the Northern Alps</p>
</DrawerHeader>
<DrawerBody className="flex flex-col gap-7 py-4">
{groups.map((group) => (
<CheckboxGroup
key={group.id}
label={group.label}
value={draft[group.id]}
onChange={(value) => setDraft((d) => ({ ...d, [group.id]: value }))}
className="gap-3"
>
{group.options.map((option) => (
<Checkbox key={option} value={option}>
{option}
</Checkbox>
))}
</CheckboxGroup>
))}
</DrawerBody>
<DrawerFooter className="flex-nowrap">
<Button variant="secondary" className="flex-1" onPress={() => setDraft(empty)}>
Reset
</Button>
<Button
className="flex-1"
onPress={() => {
setApplied(draft);
close();
}}
>
Apply
</Button>
</DrawerFooter>
</>
)}
</Drawer>
</DrawerTrigger>
);
}Bottom sheet
"use client";
import { CalendarPlus, Download, Flag, MoreHorizontal, Share2 } from "lucide-react";
import { Button } from "@/components/miyuki-ui/button";
import { Drawer, DrawerBody, DrawerHeader, DrawerTitle, DrawerTrigger } from "@/components/miyuki-ui/drawer";
const actions = [
{ label: "Share route", icon: Share2 },
{ label: "Download GPX file", icon: Download },
{ label: "Add to a trip", icon: CalendarPlus },
{ label: "Report a problem", icon: Flag },
];
export default function DrawerBottomDemo() {
return (
<DrawerTrigger>
<Button variant="secondary" iconStart={<MoreHorizontal />}>
Route options
</Button>
<Drawer side="bottom">
<DrawerHeader>
<DrawerTitle>Hakuba traverse</DrawerTitle>
<p className="text-[13px] text-muted-foreground tabular-nums">18.4 km · 24 waypoints · Grade 3</p>
</DrawerHeader>
<DrawerBody className="flex flex-col gap-0.5 px-3 pt-1 pb-3">
{actions.map(({ label, icon: Icon }) => (
<Button
key={label}
variant="ghost"
slot="close"
iconStart={<Icon className="text-muted-foreground" />}
className="h-11 w-full justify-start gap-3 px-3 font-normal"
>
{label}
</Button>
))}
<Button variant="soft" slot="close" className="mt-2 h-11 w-full">
Cancel
</Button>
</DrawerBody>
</Drawer>
</DrawerTrigger>
);
}Navigation
This example uses sample data from this site: data.ts, which also needs art.ts. Copy those files from the repository, or use your own data in their place.
"use client";
import { Bookmark, CalendarDays, CircleHelp, CloudSnow, House, Menu, Route, Settings } from "lucide-react";
import { Avatar } from "@/components/miyuki-ui/avatar";
import { Button } from "@/components/miyuki-ui/button";
import {
Drawer,
DrawerBody,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/miyuki-ui/drawer";
import { cn } from "@/lib/utils";
import { people } from "@/registry/lib/data";
const main = [
{ label: "Routes", icon: Route },
{ label: "Huts and inns", icon: House },
{ label: "Snow report", icon: CloudSnow, current: true },
{ label: "Saved", icon: Bookmark, count: 12 },
{ label: "Bookings", icon: CalendarDays, count: 2 },
];
const more = [
{ label: "Settings", icon: Settings },
{ label: "Help", icon: CircleHelp },
];
type NavItem = { label: string; icon: typeof Route; current?: boolean; count?: number };
function NavLink({ item, onNavigate }: { item: NavItem; onNavigate: () => void }) {
const Icon = item.icon;
return (
<a
href={`#${item.label.toLowerCase().replaceAll(" ", "-")}`}
aria-current={item.current ? "page" : undefined}
onClick={(event) => {
event.preventDefault();
onNavigate();
}}
className={cn(
"flex h-9 items-center gap-3 rounded-lg px-2.5 text-sm text-foreground outline-none select-none",
"transition-colors duration-150 focus-visible:ring-[3px] focus-visible:ring-ring",
item.current ? "bg-brand-soft font-medium" : "hover:bg-accent",
)}
>
<Icon
aria-hidden="true"
className={cn("size-4 shrink-0", item.current ? "text-brand-strong" : "text-muted-foreground")}
/>
<span className="flex-1 truncate">{item.label}</span>
{item.count != null && <span className="text-xs text-muted-foreground tabular-nums">{item.count}</span>}
</a>
);
}
export default function DrawerLeftDemo() {
const me = people[0];
return (
<DrawerTrigger>
<Button variant="secondary" iconStart={<Menu />}>
Menu
</Button>
<Drawer side="left">
{({ close }) => (
<>
<DrawerHeader className="flex-row items-center gap-2.5">
<Avatar name="Kitakaze Outfitters" shape="rounded" size="sm" aria-hidden />
<DrawerTitle>Kitakaze Outfitters</DrawerTitle>
</DrawerHeader>
<DrawerBody className="px-3 py-3">
<nav aria-label="Main" className="flex flex-col gap-0.5">
{main.map((item) => (
<NavLink key={item.label} item={item} onNavigate={close} />
))}
</nav>
<nav aria-label="More" className="mt-6 flex flex-col gap-0.5">
{more.map((item) => (
<NavLink key={item.label} item={item} onNavigate={close} />
))}
</nav>
</DrawerBody>
<DrawerFooter className="justify-start gap-3 border-t border-border pt-4 pb-4">
<Avatar src={me.avatar} name={me.name} size="md" />
<div className="flex min-w-0 flex-col">
<span className="truncate text-sm font-medium">{me.name}</span>
<span className="truncate text-[13px] text-muted-foreground">{me.email}</span>
</div>
</DrawerFooter>
</>
)}
</Drawer>
</DrawerTrigger>
);
}From the top
"use client";
import { TrainFront } from "lucide-react";
import { Badge, type BadgeTone } from "@/components/miyuki-ui/badge";
import { Button } from "@/components/miyuki-ui/button";
import { Drawer, DrawerBody, DrawerHeader, DrawerTitle, DrawerTrigger } from "@/components/miyuki-ui/drawer";
const alerts: { line: string; status: string; tone: BadgeTone; text: string }[] = [
{
line: "Hakodate line",
status: "Delays",
tone: "warning",
text: "Trains every 30 minutes until 10:00 while snow is cleared near Zenibako.",
},
{
line: "Otaru",
status: "Notice",
tone: "brand",
text: "The 22:40 last train runs as normal tonight, from platform 3.",
},
{
line: "Chitose line",
status: "Resolved",
tone: "success",
text: "Signal fault at Kitahiroshima fixed at 06:55. Trains are back on time.",
},
];
export default function DrawerTopDemo() {
return (
<DrawerTrigger>
<Button variant="secondary" iconStart={<TrainFront />}>
Service alerts
</Button>
<Drawer side="top">
<DrawerHeader>
<DrawerTitle>Northline service alerts</DrawerTitle>
<p className="text-[13px] text-muted-foreground tabular-nums">Updated 07:42</p>
</DrawerHeader>
<DrawerBody className="pt-1 pb-5">
<ul className="flex flex-col divide-y divide-border">
{alerts.map(({ line, status, tone, text }) => (
<li key={line} className="flex flex-col gap-1 py-3 first:pt-1 last:pb-0">
<div className="flex items-center justify-between gap-3">
<span className="text-sm leading-5 font-medium">{line}</span>
<Badge size="sm" tone={tone} dot>
{status}
</Badge>
</div>
<p className="text-[13px] leading-5 text-pretty text-muted-foreground">{text}</p>
</li>
))}
</ul>
</DrawerBody>
</Drawer>
</DrawerTrigger>
);
}Install
npx shadcn@latest add @miyuki-ui/drawerThis needs the @miyuki-ui registry in your components.json. See Using the shadcn CLI.
Props
DrawerTrigger
Prop
Type
Drawer
Prop
Type
DrawerHeader
Prop
Type
DrawerTitle
Prop
Type
DrawerBody
Prop
Type