Dialog

Dialogs, sometimes called "modals", appear above the page and require the user's immediate attention.

<div class="flex justify-center">
    <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-intro" command="show-modal">
        Open
    </dui-button>
</div>
<dui-dialog id="--dialog-intro">
    <dui-dialog-header>
        <dui-dialog-title>Edit profile</dui-dialog-title>
        <dui-dialog-description>Make changes to your profile here. Click save when you're done.</dui-dialog-description>
    </dui-dialog-header>
    <dui-field-group>
        <dui-field>
            <dui-label for="--dialog-intro-name">Name</dui-label>
            <dui-input id="--dialog-intro-name" name="name" defaultValue="Ibn Battuta"/>
        </dui-field>
        <dui-field>
            <dui-label for="--dialog-intro-username">Username</dui-label>
            <dui-input id="--dialog-intro-username" name="username" defaultValue="@@ibnbattuta"/>
        </dui-field>
    </dui-field-group>
    <dui-dialog-footer>
        <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-intro" command="close">
            Cancel
        </dui-button>
        <dui-button commandfor="--dialog-intro" command="close">
            Save Changes
        </dui-button>
    </dui-dialog-footer>
</dui-dialog>

Usage

<dui-dialog> renders a standard HTML dialog element. Use the Invoker Commands API to open and close it by adding commandfor and command attributes to a <dui-button> element.

<dui-button commandfor="--custom-dialog" command="show-modal">
    Dialog Trigger
</dui-button>
<dui-dialog id="--custom-dialog">
    <dui-dialog-header>
        <dui-dialog-title>...</dui-dialog-title>
        <dui-dialog-description>...</dui-dialog-description>
    </dui-dialog-header>
    <!-- Dialog content goes here -->
    <dui-dialog-footer>
        <dui-button variant="ButtonVariant.Outline" commandfor="--custom-dialog" command="close">
            Close
        </dui-button>
    </dui-dialog-footer>
</dui-dialog>

For more information, you can review the following MDN documentation:

Examples

Dismissing the dialog

Control how the dialog can be dismissed using the closedby attribute. The default behavior allows closing via the Esc key or a close button. Set closedby="any" to also allow clicking the backdrop (light dismiss), or closedby="none" to require explicit dismissal via a close button only.

<div class="flex justify-center">
    <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-dismiss-default" command="show-modal">
        Default Dismiss
    </dui-button>
    <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-dismiss-light" command="show-modal">
        Light Dismiss
    </dui-button>
    <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-dismiss-manual" command="show-modal">
        Manual Dismiss
    </dui-button>
</div>
<dui-dialog id="--dialog-dismiss-default">
    <dui-dialog-header>
        <dui-dialog-title>Default Dismiss</dui-dialog-title>
    </dui-dialog-header>
    <p>This dialog can be closed by pressing the
        <dui-kbd>Esc</dui-kbd>
        key or clicking the close button below.
    </p>
    <dui-dialog-footer>
        <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-dismiss-default" command="close">
            Close
        </dui-button>
    </dui-dialog-footer>
</dui-dialog>
<dui-dialog id="--dialog-dismiss-light" closedby="any">
    <dui-dialog-header>
        <dui-dialog-title>Light Dismiss</dui-dialog-title>
    </dui-dialog-header>
    <p>This dialog can be closed by pressing the
        <dui-kbd>Esc</dui-kbd>
        key, clicking on the backdrop, or clicking the close button below.
    </p>
    <dui-dialog-footer>
        <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-dismiss-light" command="close">
            Close
        </dui-button>
    </dui-dialog-footer>
</dui-dialog>
<dui-dialog id="--dialog-dismiss-manual" closedby="none">
    <dui-dialog-header>
        <dui-dialog-title>Manual Dismiss</dui-dialog-title>
    </dui-dialog-header>
    <p>This dialog can only be closed by clicking the close button below.
    </p>
    <dui-dialog-footer>
        <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-dismiss-manual" command="close">
            Close
        </dui-button>
    </dui-dialog-footer>
</dui-dialog>

Set return value

Add submit buttons to a form with method="dialog" and set a value on each button. When the buttons are clicked, the dialog will close and the returnValue will be set to the value of the button that was clicked.

If you want to return a result from the dialog, we strongly recommend using the dialog() helper which allows you to use dialogs in an async/await style

<div class="flex justify-center">
    <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-return-value" command="show-modal">
        Open Alert Dialog
    </dui-button>
</div>
<dui-dialog id="--dialog-return-value">
    <dui-dialog-header>
        <dui-dialog-title>Are you absolutely sure?</dui-dialog-title>
        <dui-dialog-description>
            This action cannot be undone. This will permanently delete your account from our servers.
        </dui-dialog-description>
    </dui-dialog-header>
    <form method="dialog">
        <dui-dialog-footer>
            <dui-button variant="ButtonVariant.Outline" type="submit" value="cancel">
                Cancel
            </dui-button>
            <dui-button type="submit" value="confirm" autofocus>
                Continue
            </dui-button>
        </dui-dialog-footer>
    </form>
</dui-dialog>
<script>
    (function() {
        const dialog = document.getElementById("--dialog-return-value");

        dialog.addEventListener("close", () => {
            const cancelled = dialog.returnValue === "" || dialog.returnValue === "cancel";
            if (cancelled) {
                alert("The action has been cancelled");
                return;
            }

            alert("The action has been confirmed");
        });
        dialog.addEventListener("toggle", (e) => {
            // Reset the return value every time the dialog opens to prevent a previous
            // returnValue from being returned when pressing the Esc key
            if (e.newState === "open") {
                dialog.returnValue = "";
            }
        });
    })();
</script>

Get form content depending on return value

Check returnValue to determine which button was clicked, then use FormData in the close event handler to read the submitted values.

If you want to return a result from the dialog, we strongly recommend using the dialog() helper which allows you to use dialogs in an async/await style

<dui-stack align="StackAlign.Start" class="min-w-md">
    <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-return-form-value-dialog" command="show-modal">
        Open Dialog with Form
    </dui-button>
    <label class="text-sm font-bold">Output:</label>
    <div class="font-mono w-full h-40 overflow-y-auto bg-gray-50" id="--dialog-return-form-value-output">-</div>
</dui-stack>
<dui-dialog id="--dialog-return-form-value-dialog">
    <form method="dialog"  class="grid gap-4">
        <dui-dialog-header>
            <dui-dialog-title>Edit profile</dui-dialog-title>
            <dui-dialog-description>Make changes to your profile here. Click save when you're done.
            </dui-dialog-description>
        </dui-dialog-header>
        <dui-field-group>
            <dui-field>
                <dui-label for="--dialog-return-form-value-name">Name</dui-label>
                <dui-input id="--dialog-return-form-value-name" name="name" defaultValue="Ibn Battuta" required/>
            </dui-field>
            <dui-field>
                <dui-label for="--dialog-return-form-value-username">Username</dui-label>
                <dui-input id="--dialog-return-form-value-username" name="username" defaultValue="@@ibnbattuta"
                           required/>
            </dui-field>
        </dui-field-group>
        <dui-dialog-footer>
            <dui-button variant="ButtonVariant.Outline" type="button" commandfor="--dialog-return-form-value-dialog"
                        command="close">
                Cancel
            </dui-button>
            <dui-button type="submit" value="confirm">
                Save Changes
            </dui-button>
        </dui-dialog-footer>
    </form>
</dui-dialog>
<script>
    (function() {
        const dialog = document.getElementById("--dialog-return-form-value-dialog");
        const output = document.getElementById("--dialog-return-form-value-output");

        dialog.addEventListener("close", () => {
            const cancelled = dialog.returnValue === "" || dialog.returnValue === "cancel";
            if (cancelled) {
                output.innerHTML = "Cancelled";
                return;
            }

            const form = dialog.querySelector("form");
            const data = Object.fromEntries(new FormData(form));
            output.innerHTML = JSON.stringify(data, null, 2);
        });
        dialog.addEventListener("toggle", (e) => {
            // Reset the return value every time the dialog opens to prevent a previous
            // returnValue from being returned when pressing the Esc key
            if (e.newState === "open") {
                dialog.returnValue = "";
            }
        });
    })();
</script>

Scrollable content

Wrap long content in a container with a fixed height and vertical scrolling enabled (using the Tailwind max-h- and overflow-y-auto utility classes) to make it scrollable within the dialog without resizing the dialog itself.

<div class="flex justify-center">
    <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-scrollable-content" command="show-modal">
        Open
    </dui-button>
</div>
<dui-dialog id="--dialog-scrollable-content">
    <dui-dialog-header>
        <dui-dialog-title>Scrollable Content</dui-dialog-title>
        <dui-dialog-description>
            This is a dialog with scrollable content.
        </dui-dialog-description>
    </dui-dialog-header>
    <div class="-mx-4 max-h-[300px] overflow-y-auto px-4">
        @for (int i = 0; i < 10; i++)
        {
            <p class="mb-4 leading-normal">
                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
                eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
                enim ad minim veniam, quis nostrud exercitation ullamco laboris
                nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
                reprehenderit in voluptate velit esse cillum dolore eu fugiat
                nulla pariatur. Excepteur sint occaecat cupidatat non proident,
                sunt in culpa qui officia deserunt mollit anim id est laborum.
            </p>
        }
    </div>
</dui-dialog>

Combine scrollable content with <dui-dialog-footer> to keep the footer always visible while the body scrolls.

<div class="flex justify-center">
    <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-sticky-footer" command="show-modal">
        Open
    </dui-button>
</div>
<dui-dialog id="--dialog-sticky-footer">
    <dui-dialog-header>
        <dui-dialog-title>Sticky Footer</dui-dialog-title>
        <dui-dialog-description>
            This dialog has a sticky footer that stays visible while the content
            scrolls.
        </dui-dialog-description>
    </dui-dialog-header>
    <div class="-mx-4 max-h-[300px] overflow-y-auto px-4">
        @for (int i = 0; i < 10; i++)
        {
            <p class="mb-4 leading-normal">
                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
                eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
                enim ad minim veniam, quis nostrud exercitation ullamco laboris
                nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
                reprehenderit in voluptate velit esse cillum dolore eu fugiat
                nulla pariatur. Excepteur sint occaecat cupidatat non proident,
                sunt in culpa qui officia deserunt mollit anim id est laborum.
            </p>
        }
    </div>
    <dui-dialog-footer>
        <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-sticky-footer" command="close">
            Close
        </dui-button>
    </dui-dialog-footer>
</dui-dialog>

JavaScript API

Since <dui-dialog> renders a native dialog element, you can control it directly via JavaScript using the showModal(), show(), and close().

<dui-group justify="GroupJustify.Center">
    <dui-button variant="ButtonVariant.Outline" id="--dialog-open-via-js-show-modal-button">
        Show Modal
    </dui-button>
    <dui-button variant="ButtonVariant.Outline" id="--dialog-open-via-js-show-button">
        Show
    </dui-button>
</dui-group>
<dui-dialog id="--dialog-open-via-js-dialog">
    This is the dialog content...
    <dui-dialog-footer>
        <dui-button variant="ButtonVariant.Outline" id="--dialog-open-via-js-close-button">
            Close via JS
        </dui-button>
    </dui-dialog-footer>
</dui-dialog>
<script>
    (function() {
        const dialog = document.getElementById("--dialog-open-via-js-dialog");
        const showModalButton = document.getElementById("--dialog-open-via-js-show-modal-button");
        const showButton = document.getElementById("--dialog-open-via-js-show-button");
        const closeButton = document.getElementById("--dialog-open-via-js-close-button");

        showModalButton.addEventListener("click", () => {
            dialog.showModal();
        });

        showButton.addEventListener("click", () => {
            dialog.show();
        });

        closeButton.addEventListener("click", () => {
            dialog.close();
        });
    })();
</script>

JavaScript events

The dialog fires beforetoggle and toggle events as it opens and closes, a cancel event when the user attempts to close via Esc or request-close, and a close event once closed. Call preventDefault() in beforetoggle to prevent opening, or in cancel to prevent closing.

<dui-stack gap="StackGap.Small" align="StackAlign.Start" class="min-w-md">
    <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-js-events-dialog" command="show-modal">
        Open Dialog
    </dui-button>
    <dui-input type="checkbox"
               id="--dialog-js-events-prevent-toggle-checkbox"
               label="PreventDefault() in beforetoggle event"
               description="Will prevent the dialog from opening"/>
    <label class="text-sm font-bold">Events:</label>
    <div class="font-mono w-full h-40 overflow-y-auto bg-gray-50" id="--dialog-js-events-output">
    </div>
</dui-stack>
<dui-dialog id="--dialog-js-events-dialog">
    <dui-dialog-header>
        <dui-dialog-title>Events</dui-dialog-title>
    </dui-dialog-header>
    <p>This dialog is used to demonstrates events.</p>
    <dui-input type="checkbox"
               id="--dialog-js-events-prevent-cancel-checkbox"
               label="PreventDefault() in cancel event"
               description="Will prevent the Request Close button from closing the dialog"/>
    <dui-dialog-footer>
        <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-js-events-dialog" command="close">
            Close
        </dui-button>
        <dui-button variant="ButtonVariant.Outline" commandfor="--dialog-js-events-dialog" command="request-close">
            Request Close
        </dui-button>
    </dui-dialog-footer>
</dui-dialog>
<script>
    (function () {
        let counter = 0;
        const dialog = document.getElementById('--dialog-js-events-dialog');
        const output = document.getElementById('--dialog-js-events-output');
        const preventToggleCheckbox = document.getElementById('--dialog-js-events-prevent-toggle-checkbox');
        const preventCancelCheckbox = document.getElementById('--dialog-js-events-prevent-cancel-checkbox');

        dialog.addEventListener("beforetoggle", (e) => {
            if (preventToggleCheckbox.checked === true) {
                output.innerText = `${++counter}: beforetoggle event prevented!\n` + output.innerText;
                e.preventDefault();
                return;
            }
            output.innerText = `${++counter}: beforetoggle event: ${e.oldState} -> ${e.newState}\n` + output.innerText;
        });
        dialog.addEventListener("toggle", (e) => {
            output.innerText = `${++counter}: toggle event: ${e.oldState} -> ${e.newState}\n` + output.innerText;
        });
        dialog.addEventListener("cancel", (e) => {
            if (preventCancelCheckbox.checked === true) {
                output.innerText = `${++counter}: cancel event prevented!\n` + output.innerText;
                e.preventDefault();
                return;
            }
            output.innerText = `${++counter}: cancel event\n` + output.innerText;
        });
        dialog.addEventListener("close", (e) => {
            output.innerText = `${++counter}: close event\n` + output.innerText;
        });
    })();
</script>