Naar inhoud

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.json
npx shadcn@latest add https://neobrutalism.com/r/base/dialog.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/dialog.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/dialog.json

Installeer de volgende afhankelijkheden:

pnpm add @base-ui/react
npm install @base-ui/react
yarn add @base-ui/react
bun add @base-ui/react

Kopieer en plak de volgende code in je project.

components/ui/dialog.tsx
"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
    └── DialogFooter

Voorbeelden

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:

ToetsActie
Space / EnterOpent de dialog vanaf de trigger met focus
Tab / Shift + TabVerplaatst de focus naar het volgende / vorige element in de dialog (de focus blijft binnen)
EscSluit de dialog en geeft de focus terug aan de trigger

API-referentie

Zie de Base UI-documentatie voor meer informatie.