Skip to content
Esc
navigateopen⌘Jpreview
On this page

Dialog

A window overlaid on the page, rendering the content underneath inert.

import { Button } from '@/components/ui/button';
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog';

export default function DialogDemo() {
  return (
    <Dialog>
      <DialogTrigger render={<Button variant="outline" />}>
        Open dialog
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Delete project</DialogTitle>
          <DialogDescription>
            This action cannot be undone. The project and all of its data
            will be permanently removed.
          </DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <DialogClose render={<Button variant="ghost" />}>Cancel</DialogClose>
          <DialogClose render={<Button variant="destructive" />}>
            Delete
          </DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

Install

npx shadcn@latest add @madeui/dialog

Usage

import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog';

Composition

<Dialog>
  <DialogTrigger />
  <DialogContent>
    <DialogHeader>
      <DialogTitle />
      <DialogDescription />
    </DialogHeader>
    <DialogFooter>
      <DialogClose />
    </DialogFooter>
  </DialogContent>
</Dialog>

Custom width

Override the content width with the style prop.

import * as stylex from '@stylexjs/stylex';

import { container } from '@/lib/constants.stylex';

import { Button } from '@/components/ui/button';
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';

export default function DialogCustom() {
  return (
    <Dialog>
      <DialogTrigger render={<Button />}>Rename</DialogTrigger>
      <DialogContent style={styles.narrow}>
        <DialogHeader>
          <DialogTitle>Rename project</DialogTitle>
          <DialogDescription>Give your project a new name.</DialogDescription>
        </DialogHeader>
        <Input defaultValue="madeui" />
        <DialogFooter>
          <DialogClose render={<Button variant="ghost" />}>Cancel</DialogClose>
          <DialogClose render={<Button />}>Save</DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

const styles = stylex.create({
  narrow: {
    width: container.md,
  },
});

Scrollable content

Give DialogContent a maxHeight and wrap the long content in its own scrollable container — the header and footer stay put.

import * as stylex from '@stylexjs/stylex';

import { Button } from '@/components/ui/button';
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog';
import { space } from '@/lib/constants.stylex';

export default function DialogScrollable() {
  return (
    <Dialog>
      <DialogTrigger render={<Button variant="outline" />}>
        View terms
      </DialogTrigger>
      <DialogContent style={styles.content}>
        <DialogHeader>
          <DialogTitle>Terms of Service</DialogTitle>
          <DialogDescription>
            Last updated February 2026. Please read before continuing.
          </DialogDescription>
        </DialogHeader>
        <div {...stylex.props(styles.body)}>
          <p>
            1. Acceptance of terms. By accessing or using this service, you
            agree to be bound by these terms and all applicable laws and
            regulations.
          </p>
          <p>
            2. Use license. Permission is granted to temporarily use this
            service for personal, non-commercial purposes only. This is the
            grant of a license, not a transfer of title.
          </p>
          <p>
            3. Account responsibilities. You are responsible for maintaining
            the confidentiality of your account credentials and for all
            activity that occurs under your account.
          </p>
          <p>
            4. Service availability. We do not guarantee that the service
            will be uninterrupted, timely, secure, or error-free, and we
            reserve the right to modify or discontinue it at any time.
          </p>
          <p>
            5. Limitation of liability. In no event shall we be liable for
            any indirect, incidental, special, or consequential damages
            arising out of your use of the service.
          </p>
          <p>
            6. Changes to terms. We may revise these terms at any time. By
            continuing to use the service after changes take effect, you
            agree to the revised terms.
          </p>
          <p>
            7. Termination. We may suspend or terminate your access at any
            time, without notice, for conduct that violates these terms.
          </p>
        </div>
        <DialogFooter>
          <DialogClose render={<Button variant="ghost" />}>Decline</DialogClose>
          <DialogClose render={<Button />}>Accept</DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

const styles = stylex.create({
  content: {
    maxHeight: `calc(100dvh - ${space.s16})`,
  },
  body: {
    display: 'flex',
    flexDirection: 'column',
    flex: 1,
    gap: space.s3,
    minHeight: 0,
    overflowY: 'auto',
  },
});

No close button

Set showCloseButton={false} and close the dialog through your own footer buttons instead.

import { Button } from '@/components/ui/button';
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog';

export default function DialogNoCloseButton() {
  return (
    <Dialog>
      <DialogTrigger render={<Button variant="outline" />}>
        Open dialog
      </DialogTrigger>
      <DialogContent showCloseButton={false}>
        <DialogHeader>
          <DialogTitle>Confirm your email</DialogTitle>
          <DialogDescription>
            We sent a confirmation link to your inbox. This dialog only
            closes through the buttons below.
          </DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <DialogClose render={<Button variant="ghost" />}>
            Resend later
          </DialogClose>
          <DialogClose render={<Button />}>Got it</DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

API reference

Built on Base UI Dialog. The tables below cover the props this library adds or changes — every other prop is forwarded to the underlying Base UI part; see the Base UI Dialog API reference for the full list.

Dialog (Root)

Prop Type Default Description
open boolean Controlled open state.
defaultOpen boolean false
onOpenChange (open: boolean) => void

DialogContent

Prop Type Default Description
showCloseButton boolean true Renders an X close button in the top-right corner.
style StyleXStyles StyleX styles merged last — always win over the component’s own styles.

Styling

DialogContent, DialogOverlay, DialogHeader, DialogFooter, DialogTitle, DialogDescription accept style (StyleXStyles, merged last so caller overrides always win) plus all native props of the element they render.

DialogTrigger / DialogClose accept render: <DialogTrigger render={<Button />}>. Always include a DialogTitle for accessibility.

Theming note: dialogs portal to <body> — apply themes (e.g. darkTheme) to <html>, not a wrapper element.

Was this page helpful?