MD3
Expressive
MATERIAL DESIGN 3 EXPRESSIVE

Dialogs

Dialogs provide important prompts in a user flow. They inform users about critical information, require decisions, or involve multiple tasks.

Dialogs focus user attention exclusively on one task or a piece of information via a modal overlay. Expressive dialogs use spring-based motion for entrance/exit animations and clear typography hierarchy.

Introduction

The MD3 Expressive Dialog is a versatile modal component that ranges from simple confirmation alerts to complex full-screen forms. It leverages Radix UI for robust accessibility and Framer Motion for premium, critically-damped spring animations that make the UI feel alive and responsive.

Anatomy

  • Scrim (Overlay): A semi-transparent background that dims the underlying UI.

  • Container: The panel holding the content. Uses rounded corners (28dp) to match the MD3 expressive language.

  • Icon (Optional): A visual indicator placed at the top for emphasis (e.g., Warning, Success).

  • Title: A clear, concise heading describing the purpose.

  • Description / Body: The main content area, which can be scrollable.

  • Footer / Actions: Buttons for the user to confirm or dismiss the prompt.

Variants

Basic Dialog

A standard modal for simple messages or confirmations.

Loading demo...

Icon Dialog

Adds an icon at the top for additional context and visual weight.

Loading demo...

Full-screen Dialog

Covers the entire viewport. Best for complex mobile-first tasks like creating a new record or extensive form filling.

Loading demo...

Features

Scrollable Body

Use DialogBody to create a scrollable region while keeping the title and footer sticky.

Loading demo...

Expressive Motion

Dialogs use a spring animation that feels "emphasized" and tactile, transitioning smoothly from a slightly scaled-down state to full size.

Usage

Basic Usage

import { Button } from "@bug-on/m3-expressive/buttons";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger } from "@bug-on/m3-expressive/overlays";

export function Example() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Open Dialog</Button>
      </DialogTrigger>
      <DialogPortal>
        <DialogOverlay />
        <DialogContent>
          <DialogTitle>Confirm Action</DialogTitle>
          <DialogDescription>
            Are you sure you want to proceed with this operation?
          </DialogDescription>
          <DialogFooter>
            <Button colorStyle="text">Cancel</Button>
            <Button>Confirm</Button>
          </DialogFooter>
        </DialogContent>
      </DialogPortal>
    </Dialog>
  );
}

Best Practices

Do

  • Use Dialogs for critical information that requires a decision.
  • Keep the title concise and the description focused.
  • Use Full-screen dialogs for complex tasks on mobile devices.
  • Ensure the primary action is clear (e.g., using a high-emphasis button).

Don't

  • Don't use a Dialog for information that is already visible on the main screen.
  • Don't use too many Dialogs in a single user flow; it can be disruptive.
  • Avoid long-running tasks inside a Dialog unless you provide a clear progress indicator.

Accessibility

  • Keyboard: Tab cycles focus inside the dialog. Esc closes the dialog.
  • Roles: Uses role="dialog" or role="alertdialog" depending on content.
  • Linking: Automatically links DialogTitle and DialogDescription via ARIA attributes.
  • Restoration: Focus is restored to the trigger element when the dialog closes.

API Reference

Dialog

Root component.

PropTypeDefaultDescription
openboolean—Controlled open state.
onOpenChange(open: boolean) => void—Open state change callback.

DialogContent

The main container.

PropTypeDefaultDescription
hideCloseButtonbooleanfalseHides the default top-right close button.
closeButtonPropsPartial<IconButtonProps>—Override any IconButton prop on the default close button. Useful for disabled, aria-label (i18n), colorStyle, or className.
closeButtonReact.ReactNode—Fully replace the close button with a custom node. Auto-wrapped in DialogClose. When provided, hideCloseButton and closeButtonProps are ignored.
classNamestring—Custom classes on the container.

Close button customization examples

// Disable during form submission (most common use case)
<DialogContent closeButtonProps={{ disabled: isSubmitting }}>

// i18n label
<DialogContent closeButtonProps={{ "aria-label": "Đóng" }}>

// Restore the original filled style
<DialogContent closeButtonProps={{ colorStyle: "filled" }}>

// Full slot override (custom element, placed inside a flex header)
<DialogContent
  closeButton={
    <IconButton
      disabled={isSubmitting}
      aria-label="Đóng"
      className="absolute right-4 top-4"
    >
      <Icon name="close" />
    </IconButton>
  }
>

DialogFullScreenContent

PropTypeDefaultDescription
titlestring—Header title in the Top App Bar.
actionLabelstring—Label for the primary action button.
onAction() => void—Primary action callback.
actionButtonPropsReact.ButtonHTMLAttributes<HTMLButtonElement>—Override any attribute on the action button. Common: disabled during form submission.
closeButtonPropsPartial<IconButtonProps>—Override any IconButton prop on the close button. Common: disabled, aria-label for i18n.
showDividerbooleanfalseRenders a divider below the Top App Bar.

Full-screen form submission example

<DialogFullScreenContent
  title="Tạo lớp học"
  actionLabel="Lưu"
  onAction={handleSubmit}
  actionButtonProps={{ disabled: isSubmitting }}
  closeButtonProps={{ disabled: isSubmitting, "aria-label": "Đóng" }}
>
  <form onSubmit={handleSubmit}>…</form>
</DialogFullScreenContent>