Dialog
Component

Dialog

A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.

Demo

Features

  • Controlled and uncontrolled modes
  • Nested dialog support with depth tracking
  • Automatic focus trapping and management
  • Body scroll locking with iOS Safari support
  • Keyboard navigation (Escape key support)
  • Portal rendering for z-index control
  • Close animation support
  • WCAG compliant with proper ARIA attributes

Installation

bash
dotnet add package SummitUI

Anatomy

Import the components and structure them as follows:

razor
<DialogRoot>
    <DialogTrigger>Open Dialog</DialogTrigger>
    <DialogPortal>
        <DialogOverlay />
        <DialogContent>
            <DialogTitle>Title</DialogTitle>
            <DialogDescription>Description</DialogDescription>
            <DialogClose>Close</DialogClose>
        </DialogContent>
    </DialogPortal>
</DialogRoot>

Sub-components

DialogRoot

Root component managing dialog state and providing context to children.

DialogContent

Main dialog panel with focus trapping and scroll locking.

DialogTitle

Accessible title component with auto-generated ID.

DialogDescription

Accessible description component with auto-generated ID.

DialogTrigger

Button element that opens the dialog when clicked.

DialogClose

Button element that closes the dialog when clicked.

DialogOverlay

Backdrop overlay that can close the dialog when clicked.

DialogPortal

Fixed-position container for rendering outside the DOM hierarchy.

API Reference

DialogRoot

Property Type Default Description
Open bool? null Controlled open state (null for uncontrolled mode)
DefaultOpen bool false Default open state for uncontrolled mode
OpenChanged EventCallback<bool> - Callback on open state change
OnOpen EventCallback - Callback when dialog opens
OnClose EventCallback - Callback when dialog closes
ChildContentrequired RenderFragment - Child components

DialogContent

Property Type Default Description
ChildContentrequired RenderFragment - Dialog content
As string "div" HTML element type
TrapFocus bool true Enable focus trapping
PreventScroll bool true Lock body scroll
EscapeKeyBehavior EscapeKeyBehavior Close Escape key behavior (Close or Ignore)
OutsideClickBehavior OutsideClickBehavior Close Click outside behavior (Close or Ignore)
OnInteractOutside EventCallback<MouseEventArgs> - Callback on click outside
OnEscapeKeyDown EventCallback<KeyboardEventArgs> - Callback on escape key
OnOpenAutoFocus EventCallback - Callback when dialog receives auto-focus
OnCloseAutoFocus EventCallback - Callback when dialog loses auto-focus
AdditionalAttributes IDictionary<string, object> - Additional HTML attributes

DialogTitle

Property Type Default Description
ChildContentrequired RenderFragment - Title text
As string "h2" HTML element type
AdditionalAttributes IDictionary<string, object> - Additional HTML attributes

DialogDescription

Property Type Default Description
ChildContentrequired RenderFragment - Description text
As string "p" HTML element type
AdditionalAttributes IDictionary<string, object> - Additional HTML attributes

DialogTrigger

Property Type Default Description
ChildContentrequired RenderFragment - Trigger button content
As string "button" HTML element type
AdditionalAttributes IDictionary<string, object> - Additional HTML attributes

DialogClose

Property Type Default Description
ChildContentrequired RenderFragment - Close button content
As string "button" HTML element type
AriaLabel string? "Close dialog" Accessible label
AdditionalAttributes IDictionary<string, object> - Additional HTML attributes

DialogOverlay

Property Type Default Description
ChildContent RenderFragment? - Optional overlay content
As string "div" HTML element type
OnClick EventCallback<MouseEventArgs> - Callback on click
CloseOnClick bool true Whether clicking closes dialog
AdditionalAttributes IDictionary<string, object> - Additional HTML attributes

DialogPortal

Property Type Default Description
ChildContentrequired RenderFragment - Portal content
ContainerId string? null Optional custom container ID

Examples

Basic Usage

razor
<DialogRoot>
    <DialogTrigger class="btn btn-primary">Open Dialog</DialogTrigger>
    <DialogPortal>
        <DialogOverlay class="dialog-overlay">
            <DialogContent class="dialog-content">
                <DialogTitle>Edit Profile</DialogTitle>
                <DialogDescription>
                    Make changes to your profile here. Click save when you're done.
                </DialogDescription>
                <div class="dialog-actions">
                    <DialogClose class="btn btn-secondary">Cancel</DialogClose>
                    <DialogClose class="btn btn-primary">Save Changes</DialogClose>
                </div>
            </DialogContent>
        </DialogOverlay>
    </DialogPortal>
</DialogRoot>

Controlled Mode

Control the dialog open state externally.

razor
@code {
    private bool isOpen = false;
}

<button @onclick="@(() => isOpen = true)">Open Dialog</button>

<DialogRoot Open="@isOpen" OpenChanged="@(v => isOpen = v)">
    <DialogPortal>
        <DialogOverlay class="dialog-overlay">
            <DialogContent class="dialog-content">
                <DialogTitle>Controlled Dialog</DialogTitle>
                <DialogDescription>
                    This dialog's state is managed externally.
                </DialogDescription>
                <DialogClose class="btn btn-primary">Close</DialogClose>
            </DialogContent>
        </DialogOverlay>
    </DialogPortal>
</DialogRoot>

Nested Dialogs

Dialogs can be nested within each other. Depth is tracked via CSS variables.

razor
<DialogRoot>
    <DialogTrigger class="btn btn-primary">Open Parent</DialogTrigger>
    <DialogPortal>
        <DialogOverlay class="dialog-overlay">
            <DialogContent class="dialog-content">
                <DialogTitle>Parent Dialog</DialogTitle>
                <DialogDescription>This is the parent dialog.</DialogDescription>

                <DialogRoot>
                    <DialogTrigger class="btn btn-secondary">Open Nested</DialogTrigger>
                    <DialogPortal>
                        <DialogOverlay class="dialog-overlay nested">
                            <DialogContent class="dialog-content nested">
                                <DialogTitle>Nested Dialog</DialogTitle>
                                <DialogDescription>This is nested inside another dialog.</DialogDescription>
                                <DialogClose class="btn btn-secondary">Close</DialogClose>
                            </DialogContent>
                        </DialogOverlay>
                    </DialogPortal>
                </DialogRoot>

                <DialogClose class="btn btn-secondary">Close Parent</DialogClose>
            </DialogContent>
        </DialogOverlay>
    </DialogPortal>
</DialogRoot>

Dialog with Form

A dialog containing a form with multiple focusable elements.

razor
<DialogRoot>
    <DialogTrigger class="btn btn-primary">Edit Profile</DialogTrigger>
    <DialogPortal>
        <DialogOverlay class="dialog-overlay">
            <DialogContent class="dialog-content">
                <DialogTitle>Edit Profile</DialogTitle>
                <DialogDescription>
                    Make changes to your profile here. Click save when you're done.
                </DialogDescription>

                <EditForm Model="@model" OnValidSubmit="HandleSubmit">
                    <div class="dialog-form">
                        <div class="form-group">
                            <label for="name">Name</label>
                            <InputText id="name" class="form-control" @bind-Value="model.Name" />
                        </div>
                        <div class="form-group">
                            <label for="email">Email</label>
                            <InputText id="email" class="form-control" @bind-Value="model.Email" />
                        </div>
                    </div>

                    <div class="dialog-actions">
                        <DialogClose type="submit" class="btn btn-primary">Save Changes</DialogClose>
                        <DialogClose class="btn btn-secondary">Cancel</DialogClose>
                    </div>
                </EditForm>
            </DialogContent>
        </DialogOverlay>
    </DialogPortal>
</DialogRoot>

Styling

Data Attributes

Attribute Values Description
data-state "open" | "closed" Dialog open state
data-nested Present on nested dialogs Indicates dialog is nested
data-nested-open Number of open nested dialogs Count of nested dialogs that are open

CSS Variables

Variable Description
--summit-dialog-depth Nesting depth (0-based)
--summit-dialog-nested-count Number of nested open dialogs

CSS Example

css
/* Overlay styles */
.dialog-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 1000;
}

.dialog-overlay[data-state="open"] {
    animation: fadeIn 200ms ease-out;
}

.dialog-overlay[data-state="closed"] {
    animation: fadeOut 200ms ease-in;
}

/* Content styles */
.dialog-content {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: white;
    border-radius: 8px;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
    padding: 1.5rem;
    min-width: 400px;
    max-width: 90vw;
    max-height: 85vh;
    overflow-y: auto;
    z-index: 1001;
}

.dialog-content[data-state="open"] {
    animation: scaleIn 200ms ease-out;
}

.dialog-content[data-state="closed"] {
    animation: scaleOut 200ms ease-in;
}

/* Nested dialog styling */
.dialog-overlay[data-nested] {
    background: rgba(0, 0, 0, 0.3);
}

.dialog-content[data-nested] {
    z-index: 1003;
}

/* Adjust z-index based on depth */
.dialog-content {
    z-index: calc(1001 + var(--summit-dialog-depth) * 2);
}

/* Animations */
@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

@keyframes fadeOut {
    from { opacity: 1; }
    to { opacity: 0; }
}

@keyframes scaleIn {
    from {
        opacity: 0;
        transform: translate(-50%, -50%) scale(0.95);
    }
    to {
        opacity: 1;
        transform: translate(-50%, -50%) scale(1);
    }
}

@keyframes scaleOut {
    from {
        opacity: 1;
        transform: translate(-50%, -50%) scale(1);
    }
    to {
        opacity: 0;
        transform: translate(-50%, -50%) scale(0.95);
    }
}

Accessibility

Keyboard Navigation

Key Action
Escape Closes the dialog
Tab Moves focus to next focusable element
Shift + Tab Moves focus to previous focusable element

ARIA Attributes

  • DialogContent: Has role="dialog", aria-modal, aria-labelledby, and aria-describedby
  • DialogTitle: Auto-generates ID and is referenced by content's aria-labelledby
  • DialogDescription: Auto-generates ID and is referenced by content's aria-describedby
  • DialogTrigger: Has aria-haspopup, aria-expanded, and aria-controls
  • DialogOverlay: Has aria-hidden="true"
An unhandled error has occurred. Reload X