Skip to main content

Theme config

Theme mode

Color palette

Grays

Border radii/radiuses/radiopedes/you know
Border radius
Field border radius
Button border radius
All
Components
Guides
API
Recent

    Components

    Dialog

    Full supportSupported since v135.Partial supportMissing: overlay.Partial supportMissing: dialog-closedby, overlay.

    Usage

    Non-modal

    • Toast: informative but non-interruptive

    No JavaScript required

    In browsers that support Invoker Commands you can toggle a <dialog> without JavaScript by using the commandfor and command attributes.

    Are you sure?

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus.

    <script setup lang="ts">
    import { Button, Dialog } from "opui-css/vue"
    </script>
    <template>
    <Button commandfor="example-dialog" command="show-modal" variant="outlined">
    Open dialog
    </Button>
    <Dialog
    id="example-dialog"
    role="alertdialog"
    aria-labelledby="dialog-heading"
    aria-modal="true"
    >
    <template #header
    ><h2 id="dialog-heading" class="ui-h4">Are you sure?</h2></template
    >
    <template #content
    ><p>
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
    sodales, nulla sit amet porttitor rhoncus. Lorem ipsum dolor sit amet,
    consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor
    rhoncus.
    </p></template
    >
    <template #actions>
    <Button commandfor="example-dialog" command="close" type="button">
    Cancel
    </Button>
    <Button
    commandfor="example-dialog"
    command="close"
    type="button"
    variant="filled"
    >
    Save
    </Button>
    </template>
    </Dialog>
    </template>
    <!--[--><button
    class="ui-button ui-outlined"
    commandfor="example-dialog"
    command="show-modal"
    >
    <!--[-->
    Open dialog
    <!--]-->
    </button>
    <dialog
    class="ui-dialog ui-card ui-elevated"
    id="example-dialog"
    role="alertdialog"
    aria-labelledby="dialog-heading"
    aria-modal="true"
    >
    <hgroup>
    <!--[-->
    <h2 id="dialog-heading" class="ui-h4">Are you sure?</h2>
    <!--]-->
    </hgroup>
    <div class="ui-content">
    <!--[-->
    <p>
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales,
    nulla sit amet porttitor rhoncus. Lorem ipsum dolor sit amet, consectetur
    adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus.
    </p>
    <!--]-->
    </div>
    <!--[--><!--]-->
    <div class="ui-actions">
    <!--[--><button
    class="ui-button"
    commandfor="example-dialog"
    command="close"
    type="button"
    >
    <!--[-->
    Cancel
    <!--]--></button
    ><button
    class="ui-button ui-filled"
    commandfor="example-dialog"
    command="close"
    type="button"
    >
    <!--[-->
    Save
    <!--]--></button
    ><!--]-->
    </div>
    </dialog>
    <!--]-->

    How to close a dialog

    Use closedby prop to control the closing behavior.

    PropDescription
    closedby="any"Click anywhere outside of the dialog to close it.
    closedby="closerequest"Device-specific way to close, ex: Esc on desktop, back button on mobile, and whatever dismiss action assistive tools use.
    closedby="none"You have to handroll a closing solution yourself.

    How to close

    Choose a closing behavior:
    <script setup lang="ts">
    import {
    Button,
    Dialog,
    FieldGroup,
    FieldLegend,
    FieldSet,
    Radio,
    } from "opui-css/vue"
    import { onMounted } from "vue"
    onMounted(() => {
    const dialog = document.getElementById(
    "closing-behaviors-dialog",
    ) as HTMLDialogElement
    const radios = document.querySelectorAll('input[name="closedby-demo"]')
    radios.forEach((radio) => {
    radio.addEventListener("change", (e) => {
    const target = e.target
    if (dialog) {
    dialog.setAttribute("closedby", target.value)
    }
    })
    })
    })
    </script>
    <template>
    <Button
    commandfor="closing-behaviors-dialog"
    command="show-modal"
    variant="outlined"
    >
    Open dialog
    </Button>
    <Dialog id="closing-behaviors-dialog" closedby="any">
    <template #header><h2 class="ui-h4">How to close</h2></template>
    <template #content
    ><div>
    <FieldSet>
    <FieldLegend>Choose a closing behavior:</FieldLegend>
    <FieldGroup name="closedby-demo">
    <Radio value="any" checked>any</Radio>
    <Radio value="closerequest">closerequest</Radio>
    <Radio value="none">none</Radio>
    </FieldGroup>
    </FieldSet>
    </div></template
    >
    <template #actions>
    <Button commandfor="closing-behaviors-dialog" command="close">
    Close manually
    </Button>
    </template>
    </Dialog>
    </template>
    <!--[--><button
    class="ui-button ui-outlined"
    commandfor="closing-behaviors-dialog"
    command="show-modal"
    >
    <!--[-->
    Open dialog
    <!--]-->
    </button>
    <dialog
    class="ui-dialog ui-card ui-elevated"
    id="closing-behaviors-dialog"
    closedby="any"
    >
    <hgroup>
    <!--[-->
    <h2 class="ui-h4">How to close</h2>
    <!--]-->
    </hgroup>
    <div class="ui-content">
    <!--[-->
    <div>
    <fieldset class="ui-fieldset">
    <!--[-->
    <legend class=""><!--[-->Choose a closing behavior:<!--]--></legend>
    <div class="ui-field-group" role="group">
    <!--[--><label class="ui-radio"
    ><input type="radio" value="any" aria-describedby="s1-0" /><span
    class="ui-label"
    ><!--[-->any<!--]--></span
    ><!----></label
    ><label class="ui-radio"
    ><input
    type="radio"
    value="closerequest"
    aria-describedby="s1-1"
    /><span class="ui-label"><!--[-->closerequest<!--]--></span
    ><!----></label
    ><label class="ui-radio"
    ><input type="radio" value="none" aria-describedby="s1-2" /><span
    class="ui-label"
    ><!--[-->none<!--]--></span
    ><!----></label
    ><!--]-->
    </div>
    <!--]-->
    </fieldset>
    </div>
    <!--]-->
    </div>
    <!--[--><!--]-->
    <div class="ui-actions">
    <!--[--><button
    class="ui-button"
    commandfor="closing-behaviors-dialog"
    command="close"
    >
    <!--[-->
    Close manually
    <!--]--></button
    ><!--]-->
    </div>
    </dialog>
    <!--]-->

    Accessibility

    • The tabindex attribute must not be used on the<dialog> element.

    Role & attributes

    Role/attributeUsage
    role="dialog"Identifies the element that serves as the dialog container.
    role="alertdialog"If the dialog is a confirmation window communicating an important message that requires a confirmation or other user response.
    aria-labelledby="IDREF"Gives the dialog an accessible name by referring to the element that provides the dialog title.
    aria-describedby="IDREF"Gives the dialog an accessible description by referring to the dialog content that describes the primary message or purpose of the dialog.
    aria-modal="true"Tells assistive technologies that the windows underneath the current dialog are not available for interaction (inert).

    Keyboard support

    KeyFunction
    Tab
    • Moves focus to next focusable element inside the dialog.
    • When focus is on the last focusable element in the dialog, moves focus to the first focusable element in the dialog.
    Shift + Tab
    • Moves focus to previous focusable element inside the dialog.
    • When focus is on the first focusable element in the dialog, moves focus to the last focusable element in the dialog.
    EscCloses the dialog.

    Source: w3.org, MDN

    API

    Props

    PropTypeDefaultDescription
    actionsAlign"start", "end"-Alignment of the actions slot.
    closedby"any" | "closerequest" | "none"-Specifies how the dialog can be closed (e.g., clicking outside).

    Slots

    SlotDescription
    headerThe header content, wrapped in an hgroup.
    contentThe main content, wrapped in a div with a content class.
    actionsThe footer actions, wrapped in a div with an actions class.
    defaultAny content not assigned to a named slot.

    Browser support

    Full supportSupported since v135.Partial supportMissing: overlay.Partial supportMissing: dialog-closedby, overlay.

    See also the full browser support guide.

    Installation

    Dependencies

    @layer components.extended {
    :where(.ui-dialog) {
    --_motion: var(--motion, 1);
    inline-size: 100%;
    inset: 0;
    margin: auto;
    margin-block-start: 15%;
    max-inline-size: calc(100% - var(--size-4));
    padding-block: 0;
    pointer-events: none;
    position: fixed;
    @media (width > 600px) {
    max-inline-size: 60ch;
    }
    /* Animation */
    /* There's no close animation, intentionally */
    opacity: 0;
    &::backdrop {
    backdrop-filter: blur(1px);
    background-color: rgba(0, 0, 0, 0.5);
    }
    &:not([open]) {
    display: none;
    }
    &[open] {
    pointer-events: all;
    }
    .ui-actions {
    justify-content: end;
    padding-inline: var(--size-3) var(--size-1);
    }
    &[open] {
    opacity: 1;
    transition:
    display calc(0.2s * var(--_motion)) allow-discrete,
    overlay calc(0.2s * var(--_motion)) allow-discrete,
    opacity calc(0.2s * var(--_motion)) var(--ease-out-1);
    @starting-style {
    opacity: 0;
    }
    }
    &[open] {
    margin-block-start: 15%;
    @starting-style {
    opacity: 0;
    }
    }
    }
    :where(html:has(.ui-dialog[open])) {
    block-size: 100%;
    overflow: hidden;
    }
    }
    @layer components.root {
    :where(.ui-card) {
    --_bg-tonal: var(--surface-tonal);
    --_bg-elevated: var(--surface-elevated);
    --_bg-surface: var(--surface-default);
    --_border-color: var(--border-color);
    --_card-bg-color: var(--_bg-surface);
    --_card-border-color: transparent;
    --_card-border-width: 0;
    --_card-shadow: none;
    --_shadow-light: var(--shadow-3);
    --_shadow-dark: var(--shadow-4);
    --_shadow-elevated: var(--_shadow-light);
    @container style(--color-scheme: dark) {
    --_shadow-elevated: var(--_shadow-dark);
    }
    background-color: var(--_card-bg-color);
    border-color: var(--_card-border-color);
    border-radius: var(--border-radius);
    border-style: solid;
    border-width: var(--_card-border-width);
    box-shadow: var(--_card-shadow);
    display: flex;
    flex-direction: column;
    gap: var(--size-3);
    overflow: hidden;
    padding-inline: 0;
    position: relative;
    /* Variants */
    &.ui-text {
    --_card-bg-color: transparent;
    --_card-border-color: transparent;
    --_card-border-width: 0;
    --_card-shadow: none;
    }
    &.ui-tonal {
    --_card-bg-color: var(--_bg-tonal);
    --_card-border-width: 1px;
    }
    &.ui-elevated {
    --_card-bg-color: var(--_bg-elevated);
    --_card-shadow: var(--_shadow-elevated);
    }
    &.ui-outlined {
    --_card-bg-color: var(--_bg-surface);
    --_card-border-color: var(--_border-color);
    --_card-border-width: 1px;
    }
    &> :where(hgroup, .ui-content) {
    padding-inline: var(--size-3);
    &:last-child {
    padding-block-end: var(--size-3);
    }
    }
    /* Header */
    &>hgroup {
    padding-block: var(--size-3) 0;
    /* Top paragraph */
    &>p:first-of-type:first-child {
    font-size: var(--font-size-0);
    line-height: 1.3;
    }
    :where(h1, h2, h3, h4, h5, h6):last-child {
    margin-block-end: 0;
    }
    /* Bottom paragraph */
    &>p:last-of-type:last-child:not(:first-child) {
    font-size: var(--font-size-1);
    }
    }
    /* Content */
    &>.ui-content:where(:only-child, :first-child) {
    padding-block: var(--size-3) var(--size-4);
    }
    /* Actions */
    &>.ui-actions {
    display: flex;
    gap: var(--size-2);
    margin-block: var(--size-2) 0;
    padding-block-end: var(--size-2);
    padding-inline: var(--size-3);
    &:has(.ui-button:first-child[class="ui-button"]) {
    padding-inline: var(--size-1) var(--size-3);
    }
    &:has(.ui-button:not([class="ui-button"])) {
    padding-block-end: var(--size-2);
    }
    /* Alignment */
    &.ui-align-end {
    justify-content: end;
    &:has(.ui-button:first-child[class="ui-button"]) {
    padding-inline: var(--size-3) var(--size-1);
    }
    }
    }
    }
    }