Zum Inhalt springen

Dialog

Ein modales Fenster für Bestätigungen, Schnellformulare und Abfragen zu irreversiblen Aktionen – über der Seite eingeblendet, mit dicken Rahmen und einem kräftigen Schatten.

import { Button } from "@/components/ui/button"
import {
  Dialog,

Der Dialog öffnet ein modales Fenster über der Seite – der Fokus wandert hinein, der Inhalt dahinter wird inert, und beim Schließen landest du exakt dort, wo du warst. Basiert auf dem Primitive Base UI Dialog und ist nach dem neobrutalistischen Rezept gestaltet: dicke Rahmen, harte Schatten und kräftige Typografie.

Typische Anwendungsfälle:

  • Bestätigungen – Löschen, Abmelden, Plan-Downgrades; alles Destruktive, das einen harten Stopp verdient.
  • Schnelle Formulare – Profil bearbeiten, Projekt umbenennen, Teammitglied einladen, ohne die Seite zu verlassen.
  • Fokussierte Detailansichten – Bildvorschauen, Lizenztexte, Release Notes, die keine eigene Route brauchen.

Installation

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

Installiere die folgenden Abhängigkeiten:

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

Kopiere den folgenden Code und füge ihn in dein Projekt ein.

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,
}

Passe die Importpfade an deine Projektkonfiguration an.

Verwendung

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>

Komposition

Verwende die folgende Komposition, um ein Dialog zu erstellen:

Dialog
├── DialogTrigger
└── DialogContent
    ├── DialogHeader
    │   ├── DialogTitle
    │   └── DialogDescription
    └── DialogFooter

Beispiele

Benutzerdefinierter Schließen-Button

Ersetze das Standard-Schließ-Steuerelement durch deinen eigenen Button.

import { Button } from "@/components/ui/button"
import {
  Dialog,

Kein Schließen-Button

Verwende showCloseButton={false}, um den Schließen-Button auszublenden.

import { Button } from "@/components/ui/button"
import {
  Dialog,

Halte Aktionen sichtbar, während der Inhalt scrollt.

import { Button } from "@/components/ui/button"
import {
  Dialog,

Scrollbarer Inhalt

Langer Inhalt kann scrollen, während der Header sichtbar bleibt.

import { Button } from "@/components/ui/button"
import {
  Dialog,

RTL

Um RTL-Unterstützung in Neobrutalism zu aktivieren, siehe die RTL-Konfigurationsanleitung.

"use client"

import {

Barrierefreiheit

Der Dialog folgt dem WAI-ARIA-Pattern „Dialog (Modal)“: Der Inhalt wird mit role="dialog" und aria-modal gerendert, über DialogTitle beschriftet und über DialogDescription beschrieben; der Fokus bleibt gefangen, solange der Dialog offen ist, und kehrt beim Schließen zum Trigger zurück.

Tastaturinteraktionen:

TasteAktion
Space / EnterÖffnet den Dialog vom fokussierten Trigger aus
Tab / Shift + TabBewegt den Fokus zum nächsten / vorherigen Element im Dialog (der Fokus bleibt gefangen)
EscSchließt den Dialog und gibt den Fokus an den Trigger zurück

API-Referenz

Weitere Informationen findest du in der Base UI-Dokumentation.