Lade
Een gebarenbestuurd venster dat vanaf elke rand naar binnen schuift voor mobiele menu’s, filters en snelle formulieren — met neobrutalistische randen en schaduwen.
"use client"
import * as React from "react"De drawer is een paneel dat vanaf een rand van het scherm naar binnen schuift en dichtgesleept kan worden. Gebouwd op Vaul van Emil Kowalski en vormgegeven volgens het neobrutalistische recept: dikke randen, scherpe schaduwen en vette typografie.
Ideaal voor:
- Mobiele actiemenu’s — deelmenu’s, sorteeropties, snelle bevestigingen. Het sleep-om-te-sluiten-gebaar werkt op touch prettiger dan zoeken naar een piepklein sluitknopje.
- Filters en instellingenpanelen — e-commercefilters of voorkeuren die meer ruimte nodig hebben dan een popover, maar geen eigen pagina rechtvaardigen.
- Responsieve dialogen — combineer hem met
Dialogom op desktop een modaal venster te tonen en op mobiel een drawer (zie het voorbeeld hieronder).
Over#
Drawer is gebouwd bovenop Vaul van emilkowalski.
Installatie#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/drawer.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/drawer.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/drawer.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/drawer.json
Installeer de volgende afhankelijkheden:
pnpm add vaulnpm install vaulyarn add vaulbun add vaul
Kopieer en plak de volgende code in je project.
"use client"
import * as React from "react"
import { Drawer as DrawerPrimitive } from "vaul"
import { cn } from "@/lib/utils"
function Drawer({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
return <DrawerPrimitive.Root data-slot="drawer" {...props} />
}
function DrawerTrigger({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
}
function DrawerPortal({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
}
function DrawerClose({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
}
function DrawerOverlay({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
return (
<DrawerPrimitive.Overlay
data-slot="drawer-overlay"
className={cn(
"fixed inset-0 z-50 bg-foreground/20 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
className
)}
{...props}
/>
)
}
function DrawerContent({
className,
children,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
return (
<DrawerPortal data-slot="drawer-portal">
<DrawerOverlay />
<DrawerPrimitive.Content
data-slot="drawer-content"
className={cn(
"group/drawer-content fixed z-50 flex h-auto flex-col bg-popover text-sm text-popover-foreground shadow-md data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-xl data-[vaul-drawer-direction=bottom]:border-t-2 data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:rounded-r-xl data-[vaul-drawer-direction=left]:border-r-2 data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:rounded-l-xl data-[vaul-drawer-direction=right]:border-l-2 data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-xl data-[vaul-drawer-direction=top]:border-b-2 data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm",
className
)}
{...props}
>
<div className="mx-auto mt-4 hidden h-1 w-[100px] shrink-0 rounded-full bg-muted group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
)
}
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="drawer-header"
className={cn(
"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-0.5 md:text-left",
className
)}
{...props}
/>
)
}
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="drawer-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
)
}
function DrawerTitle({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
return (
<DrawerPrimitive.Title
data-slot="drawer-title"
className={cn(
"font-head text-base font-medium text-foreground",
className
)}
{...props}
/>
)
}
function DrawerDescription({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
return (
<DrawerPrimitive.Description
data-slot="drawer-description"
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
}
export {
Drawer,
DrawerPortal,
DrawerOverlay,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
}
Werk de importpaden bij zodat ze overeenkomen met je projectconfiguratie.
Gebruik#
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer"<Drawer>
<DrawerTrigger>Open</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Are you absolutely sure?</DrawerTitle>
<DrawerDescription>This action cannot be undone.</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose>
<Button variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>Compositie#
Gebruik de volgende compositie om een Drawer te bouwen:
Drawer
├── DrawerTrigger
└── DrawerContent
├── DrawerHeader
│ ├── DrawerTitle
│ └── DrawerDescription
└── DrawerFooterVoorbeelden#
Scrollbare inhoud#
Houd acties zichtbaar terwijl de inhoud scrolt.
import { Button } from "@/components/ui/button"
import {
Drawer,Zijden#
Gebruik de direction-prop om de zijde van de drawer in te stellen. Beschikbare opties zijn top, right, bottom en left.
import { Button } from "@/components/ui/button"
import {
Drawer,Responsief dialoogvenster#
Je kunt de componenten Dialog en Drawer combineren om een responsief dialoogvenster te maken. Dit rendert een Dialog-component op desktop en een Drawer op mobiel.
"use client"
import * as React from "react"RTL#
Om RTL-ondersteuning in Neobrutalism in te schakelen, zie de RTL-configuratiegids.
"use client"
import * as React from "react"Toegankelijkheid#
De drawer volgt het WAI-ARIA Dialog (Modal)-patroon: Vaul bouwt voort op de Radix Dialog-primitive, dus het paneel wordt gerenderd met role="dialog" en aria-modal, houdt de focus vast zolang het open is, en krijgt een label via DrawerTitle en een beschrijving via DrawerDescription. Slepen om te sluiten werkt alleen met een aanwijzer — toetsenbordgebruikers sluiten met Escape of een DrawerClose-knop, dus neem er altijd een op.
Toetsenbordinteracties:
| Toets | Actie |
|---|---|
Space / Enter | Opent de drawer vanaf de trigger met focus |
Tab / Shift + Tab | Laat de focus door de inhoud van de drawer rouleren (de focus blijft binnen) |
Escape | Sluit de drawer en geeft de focus terug aan de trigger |
API-referentie#
Zie de Vaul-documentatie voor de volledige API-referentie.