Dialoog
Een modaal venster voor bevestigingen, snelle formulieren en waarschuwingen bij ingrijpende acties — dat over de pagina heen wordt weergegeven met dikke randen en een scherpe schaduw.
import { Button } from "@/components/ui/button"
import {
Dialog,De dialog opent een modaal venster boven op de pagina — de focus verplaatst zich naar binnen, de content erachter wordt inert en bij het sluiten kom je precies terug waar je was. Gebouwd op de Base UI Dialog-primitive en vormgegeven volgens het neobrutalistische recept: dikke randen, scherpe schaduwen en vette typografie.
Ideaal voor:
- Bevestigingen — verwijderen, uitloggen, downgraden van een abonnement; alles wat destructief is en een harde stop verdient.
- Snelle formulieren — een profiel bewerken, een project hernoemen, een teamgenoot uitnodigen zonder de pagina te verlaten.
- Gerichte detailweergaven — afbeeldingsvoorbeelden, licentietekst, releasenotes die geen eigen route nodig hebben.
Installatie#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/dialog.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/dialog.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/dialog.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/dialog.json
Installeer de volgende afhankelijkheden:
pnpm add @base-ui/reactnpm install @base-ui/reactyarn add @base-ui/reactbun add @base-ui/react
Kopieer en plak de volgende code in je project.
"use client"
import * as React from "react"
import { XIcon } from "lucide-react"
import { Dialog as DialogPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"fixed inset-0 isolate z-50 bg-foreground/20 duration-100 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 DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
}) {
return (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded border-2 bg-popover p-4 text-sm text-popover-foreground shadow-md duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close data-slot="dialog-close" asChild>
<Button
variant="ghost"
className="absolute top-2 right-2"
size="icon-sm"
>
<XIcon />
<span className="sr-only">Close</span>
</Button>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
}
function DialogFooter({
className,
showCloseButton = false,
children,
...props
}: React.ComponentProps<"div"> & {
showCloseButton?: boolean
}) {
return (
<div
data-slot="dialog-footer"
className={cn(
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t-2 bg-muted/50 p-4 sm:flex-row sm:justify-end",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close asChild>
<Button variant="outline">Close</Button>
</DialogPrimitive.Close>
)}
</div>
)
}
function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn(
"font-head text-base leading-none font-medium",
className
)}
{...props}
/>
)
}
function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn(
"text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className
)}
{...props}
/>
)
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}
Werk de importpaden bij zodat ze overeenkomen met je projectconfiguratie.
Gebruik#
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"<Dialog>
<DialogTrigger>Open</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete your account
and remove your data from our servers.
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>Compositie#
Gebruik de volgende compositie om een Dialog te bouwen:
Dialog
├── DialogTrigger
└── DialogContent
├── DialogHeader
│ ├── DialogTitle
│ └── DialogDescription
└── DialogFooterVoorbeelden#
Aangepaste sluitknop#
Vervang de standaard sluitknop door je eigen knop.
import { Button } from "@/components/ui/button"
import {
Dialog,Geen sluitknop#
Gebruik showCloseButton={false} om de sluitknop te verbergen.
import { Button } from "@/components/ui/button"
import {
Dialog,Vastgezette voettekst#
Houd acties zichtbaar terwijl de inhoud scrolt.
import { Button } from "@/components/ui/button"
import {
Dialog,Scrollbare inhoud#
Lange inhoud kan scrollen terwijl de koptekst zichtbaar blijft.
import { Button } from "@/components/ui/button"
import {
Dialog,RTL#
Om RTL-ondersteuning in Neobrutalism in te schakelen, zie de RTL-configuratiegids.
"use client"
import {Toegankelijkheid#
De dialog volgt het WAI-ARIA Dialog (Modal)-patroon: de content wordt gerenderd met role="dialog" en aria-modal, krijgt een label via DialogTitle en een beschrijving via DialogDescription, de focus wordt binnen het venster vastgehouden zolang het open is, en bij het sluiten keert de focus terug naar de trigger.
Toetsenbordinteracties:
| Toets | Actie |
|---|---|
Space / Enter | Opent de dialog vanaf de trigger met focus |
Tab / Shift + Tab | Verplaatst de focus naar het volgende / vorige element in de dialog (de focus blijft binnen) |
Esc | Sluit de dialog en geeft de focus terug aan de trigger |
API-referentie#
Zie de Base UI-documentatie voor meer informatie.