Aller au contenu

Dialogue

Une fenêtre modale destinée aux confirmations, aux formulaires rapides et aux invites relatives à des actions irréversibles — superposée à la page, avec des bordures épaisses et une ombre prononcée.

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

La boîte de dialogue ouvre une fenêtre modale au-dessus de la page — le focus se déplace à l’intérieur, le contenu en arrière-plan devient inerte, et la fermeture vous ramène exactement là où vous étiez. Construite sur la primitive Base UI Dialog et habillée selon la recette néobrutaliste : bordures épaisses, ombres franches et typographie affirmée.

À privilégier pour :

  • Confirmations — suppressions, déconnexions, passages à une offre inférieure ; tout ce qui est destructif et mérite un vrai coup d’arrêt.
  • Formulaires rapides — modifier un profil, renommer un projet, inviter un coéquipier sans quitter la page.
  • Vues de détail ciblées — aperçus d’images, textes de licence, notes de version qui n’ont pas besoin de leur propre route.

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

Installez les dépendances suivantes :

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

Copiez et collez le code suivant dans votre projet.

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

Adaptez les chemins d’import à la structure de votre projet.

Utilisation

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>

Composition

Utilisez la composition suivante pour construire un Dialog :

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

Exemples

Bouton de fermeture personnalisé

Remplacez le contrôle de fermeture par défaut par votre propre bouton.

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

Sans bouton de fermeture

Utilisez showCloseButton={false} pour masquer le bouton de fermeture.

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

Pied de page fixe

Gardez les actions visibles pendant que le contenu défile.

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

Contenu défilant

Un contenu long peut défiler tandis que l’en-tête reste visible.

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

RTL

Pour activer le RTL dans Neobrutalism, consultez le guide de configuration RTL.

"use client"

import {

Accessibilité

La boîte de dialogue suit le modèle WAI-ARIA Dialog (Modal) : le contenu est rendu avec role="dialog" et aria-modal, il est intitulé par DialogTitle et décrit par DialogDescription, le focus reste piégé à l’intérieur tant que la boîte est ouverte, et la fermeture renvoie le focus au déclencheur.

Interactions clavier :

ToucheAction
Space / EnterOuvre la boîte de dialogue depuis le déclencheur ayant le focus
Tab / Shift + TabDéplace le focus vers l’élément suivant / précédent dans la boîte de dialogue (le focus est piégé)
EscFerme la boîte de dialogue et renvoie le focus au déclencheur

Référence API

Consultez la documentation Base UI pour en savoir plus.