# Dialog

Centered dialog overlay with portal, inert background, focus trap, click-outside, nested stacking, and Svelte transitions. Composes _internal/overlay for shared mechanics.

- Package: `@mielui/svelte`
- Component version: `1.4.0`
- Depends on Mielui components: _internal/utils, button, _internal/overlay, typography
- Shared utilities: components/_internal/button-attributes, components/_internal/inset-layout, components/_internal/overlay-close, components/_internal/surface, hugeicons-icon, utils.cn, utils.createContext, utils.inertOutside, utils.lockBodyScroll, utils.visualViewportBounds, transition

## Install

~~~~sh
pnpm dlx @mielui/svelte add dialog
~~~~

## API

This reference is generated at build time from the component manifest, public `index.ts`, and documentation examples below. Changes to those source files are reflected here in the published Markdown. Standard Svelte and HTML attributes accepted by the exported prop types are supported.


### Body

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### Close

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| disabled | boolean \| undefined | - | No | No | Prevents activation. A disabled link loses its destination and is skipped by Tab. |
| variant | ButtonVariant \| undefined | - | No | No | Visual style. Choose it for what the action means, not for decoration. |
| size | "sm" \| "md" \| "lg" \| "icon" \| undefined | - | No | No | Control height. `icon` is a square button for a single icon. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |
| element | HTMLButtonElement \| HTMLAnchorElement \| undefined | - | No | Yes | Bindable reference to the rendered DOM element. Type is the union of the two possible element types -- narrow at the use site: |
| unstyled | boolean \| undefined | - | No | No | Skip the variant/size base classes and render with `class` alone. |
| status | ButtonStatus \| undefined | - | No | No | Controlled visual state. Loading remains focusable and refuses activation. |
| loading | boolean \| undefined | - | No | No | Convenience alias for `status="loading"`. |
| labels | ButtonLabels \| undefined | - | No | No | Overrides the built-in text and accessible names. Every key is optional and English is the fallback. |
| onclick | ((event: MouseEvent) => void) \| undefined | - | No | No | Called when the button is activated. Loading and disabled buttons do not call it. |
| onkeydown | ((event: KeyboardEvent) => void) \| undefined | - | No | No | Called on key down, before the button handles the key itself. |
| href | string \| undefined | - | No | No | Renders a link to this address instead of a button. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Confirm

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| disabled | boolean \| undefined | - | No | No | Prevents activation. A disabled link loses its destination and is skipped by Tab. |
| variant | ButtonVariant \| undefined | - | No | No | Visual style. Choose it for what the action means, not for decoration. |
| size | "sm" \| "md" \| "lg" \| "icon" \| undefined | - | No | No | Control height. `icon` is a square button for a single icon. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |
| element | HTMLButtonElement \| HTMLAnchorElement \| undefined | - | No | Yes | Bindable reference to the rendered DOM element. Type is the union of the two possible element types -- narrow at the use site: |
| unstyled | boolean \| undefined | - | No | No | Skip the variant/size base classes and render with `class` alone. |
| status | ButtonStatus \| undefined | - | No | No | Controlled visual state. Loading remains focusable and refuses activation. |
| loading | boolean \| undefined | - | No | No | Convenience alias for `status="loading"`. |
| labels | ButtonLabels \| undefined | - | No | No | Overrides the built-in text and accessible names. Every key is optional and English is the fallback. |
| onclick | ((event: MouseEvent) => void) \| undefined | - | No | No | Called when the button is activated. Loading and disabled buttons do not call it. |
| onkeydown | ((event: KeyboardEvent) => void) \| undefined | - | No | No | Called on key down, before the button handles the key itself. |
| href | string \| undefined | - | No | No | Renders a link to this address instead of a button. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Content

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| surface | "solid" \| "glass" \| undefined | - | No | No | Surface treatment. Omit it to inherit `--mielui-surface` from the theme. |
| allowClickOutside | boolean \| undefined | true | No | No | Closes when the pointer is pressed outside it. Set false to keep it open until it is dismissed another way. |
| allowEscape | boolean \| undefined | true | No | No | Lets Escape dismiss the dialog. |
| overlayClass | string \| undefined | '' | No | No | Classes for the backdrop. |
| surfaceClass | string \| undefined | '' | No | No | Classes for the inner surface. |
| showClose | boolean \| undefined | true | No | No | Shows the close button in the corner. |
| size | DialogSize \| undefined | 'lg' | No | No | Maximum width of the panel. Defaults to `lg`. |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### Description

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### Footer

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### Header

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### Root

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| labels | DialogLabels \| undefined | - | No | No | Overrides the built-in text and accessible names. Every key is optional and English is the fallback. |
| open | boolean \| undefined | false | No | Yes | Whether it is open. Bind it to control the state from outside. |
| onOpenChange | ((open: boolean) => void) \| undefined | - | No | No | Called with the new state whenever it opens or closes. |
| error | boolean \| undefined | false | No | No | Marks the dialog as destructive. Confirm defaults to the destructive variant, and browsers that tint their chrome from the theme color turn it red while the dialog is open. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### Title

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### Trigger

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| disabled | boolean \| undefined | - | No | No | Prevents activation. A disabled link loses its destination and is skipped by Tab. |
| variant | ButtonVariant \| undefined | - | No | No | Visual style. Choose it for what the action means, not for decoration. |
| size | "sm" \| "md" \| "lg" \| "icon" \| undefined | - | No | No | Control height. `icon` is a square button for a single icon. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |
| element | HTMLButtonElement \| HTMLAnchorElement \| undefined | - | No | Yes | Bindable reference to the rendered DOM element. Type is the union of the two possible element types -- narrow at the use site: |
| unstyled | boolean \| undefined | - | No | No | Skip the variant/size base classes and render with `class` alone. |
| status | ButtonStatus \| undefined | - | No | No | Controlled visual state. Loading remains focusable and refuses activation. |
| loading | boolean \| undefined | - | No | No | Convenience alias for `status="loading"`. |
| labels | ButtonLabels \| undefined | - | No | No | Overrides the built-in text and accessible names. Every key is optional and English is the fallback. |
| onclick | ((event: MouseEvent) => void) \| undefined | - | No | No | Called when the button is activated. Loading and disabled buttons do not call it. |
| onkeydown | ((event: KeyboardEvent) => void) \| undefined | - | No | No | Called on key down, before the button handles the key itself. |
| href | string \| undefined | - | No | No | Renders a link to this address instead of a button. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Public types

~~~~ts
import type { DefaultProps } from '@mielui/svelte/utils';
import type { Snippet } from 'svelte';
import type { ButtonProps } from '../button';
import Root from './dialog.svelte';
import Body from './dialog-body.svelte';
import Close from './dialog-close.svelte';
import Confirm from './dialog-confirm.svelte';
import Content from './dialog-content.svelte';
import Description from './dialog-description.svelte';
import Footer from './dialog-footer.svelte';
import Header from './dialog-header.svelte';
import Title from './dialog-title.svelte';
import Trigger from './dialog-trigger.svelte';

export type DialogState = {
    open: boolean;
    error: boolean;
};

export type DialogSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';

export type DialogTriggerProps = ButtonProps;
export type DialogTitleProps = DefaultProps;
export type DialogHeaderProps = DefaultProps;
export type DialogFooterProps = DefaultProps;
export type DialogBodyProps = DefaultProps;
export type DialogConfirmProps = ButtonProps;
export type DialogCloseProps = ButtonProps;
export type DialogDescriptionProps = DefaultProps;

export type DialogContentProps = {
    /** Surface treatment. Omit it to inherit `--mielui-surface` from the theme. */
    surface?: 'solid' | 'glass';
    /**
     * Closes when the pointer is pressed outside it. Set false to keep it open until it is
     * dismissed another way.
     */
    allowClickOutside?: boolean;
    /** Lets Escape dismiss the dialog. */
    allowEscape?: boolean;
    /** Classes for the backdrop. */
    overlayClass?: string;
    /** Classes for the inner surface. */
    surfaceClass?: string;
    /** Shows the close button in the corner. */
    showClose?: boolean;
    /** Maximum width of the panel. Defaults to `lg`. */
    size?: DialogSize;
} & DefaultProps &
    Partial<Record<`aria-${string}`, string | boolean | null | undefined>>;

export type DialogLabels = {
    /** Accessible name of the close button in the corner of the panel. */
    close?: string;
};

export type DialogProps = {
    /**
     * Overrides the built-in text and accessible names. Every key is optional and English is the
     * fallback.
     */
    labels?: DialogLabels;
    /** Whether it is open. Bind it to control the state from outside. */
    open?: boolean;
    /** Called with the new state whenever it opens or closes. */
    onOpenChange?: (open: boolean) => void;
    /**
     * Marks the dialog as destructive. Confirm defaults to the destructive variant, and browsers
     * that tint their chrome from the theme color turn it red while the dialog is open.
     */
    error?: boolean;
    /** Content rendered inside. */
    children?: Snippet;
};

export { Body, Close, Confirm, Content, Description, Footer, Header, Root, Title, Trigger };
~~~~

## Examples

### Async Confirm

~~~~svelte
<script lang="ts">
    import * as Dialog from '@mielui/svelte/components/dialog';

    let open = $state(false);
    let saving = $state(false);

    function save(event: MouseEvent) {
        event.preventDefault();
        saving = true;
        setTimeout(() => {
            saving = false;
            open = false;
        }, 1200);
    }
</script>

<Dialog.Root bind:open>
    <Dialog.Trigger>Rename project</Dialog.Trigger>
    <Dialog.Content>
        <Dialog.Header>
            <Dialog.Title>Rename to Northwind API?</Dialog.Title>
            <Dialog.Description>The dialog stays open until the save finishes.</Dialog.Description>
        </Dialog.Header>
        <Dialog.Footer>
            <Dialog.Close disabled={saving}>Cancel</Dialog.Close>
            <Dialog.Confirm loading={saving} labels={{ loading: 'Saving' }} onclick={save}>
                Rename
            </Dialog.Confirm>
        </Dialog.Footer>
    </Dialog.Content>
</Dialog.Root>
~~~~

### Basic

~~~~svelte
<script lang="ts">
    import * as Dialog from '@mielui/svelte/components/dialog';
    import Kbd from '@mielui/svelte/components/kbd';

    let open = $state(false);
</script>

<Dialog.Root bind:open>
    <Dialog.Trigger>Open dialog</Dialog.Trigger>
    <Dialog.Content>
        <Dialog.Header>
            <Dialog.Title>Dialog title</Dialog.Title>
            <Dialog.Description>Describe what happens here.</Dialog.Description>
        </Dialog.Header>
        <Dialog.Footer>
            <Dialog.Close>
                Cancel
                <Kbd shortcut="esc" />
            </Dialog.Close>
            <Dialog.Confirm>
                Confirm
                <Kbd shortcut="enter" />
            </Dialog.Confirm>
        </Dialog.Footer>
    </Dialog.Content>
</Dialog.Root>
~~~~

### Destructive

~~~~svelte
<script lang="ts">
    import * as Dialog from '@mielui/svelte/components/dialog';
</script>

<Dialog.Root error>
    <Dialog.Trigger variant="outline">Remove member</Dialog.Trigger>
    <Dialog.Content>
        <Dialog.Header>
            <Dialog.Title>Remove Sam from the workspace?</Dialog.Title>
            <Dialog.Description>Sam loses access to every project right away.</Dialog.Description>
        </Dialog.Header>
        <Dialog.Footer>
            <Dialog.Close>Cancel</Dialog.Close>
            <Dialog.Confirm>Remove member</Dialog.Confirm>
        </Dialog.Footer>
    </Dialog.Content>
</Dialog.Root>
~~~~

### Glass

~~~~svelte
<script lang="ts">
    import { Globe02Icon as Globe } from '@hugeicons/core-free-icons';
    import * as Dialog from '@mielui/svelte/components/dialog';
    import { Input } from '@mielui/svelte/components/input';
    import Kbd from '@mielui/svelte/components/kbd';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';

    let open = $state(false);
    let domain = $state('');
    let addedDomain = $state('');

    function addDomain() {
        addedDomain = domain.trim();
        domain = '';
    }
</script>

<div class="flex flex-col items-center gap-3">
    <Dialog.Root bind:open>
        <Dialog.Trigger>Add domain</Dialog.Trigger>
        <Dialog.Content size="sm" surface="glass">
            <Dialog.Header>
                <div class="flex items-center gap-2.5">
                    <HugeiconsIcon icon={Globe} size={18} class="text-foreground-muted" />
                    <Dialog.Title>Add a domain</Dialog.Title>
                </div>
                <Dialog.Description>
                    Add a domain to this local preview. No DNS settings are changed.
                </Dialog.Description>
            </Dialog.Header>
            <Dialog.Body class="gap-4">
                <Input
                    bind:value={domain}
                    label="Domain"
                    placeholder="example.com"
                    description="Enter a domain name, such as example.com."
                />
            </Dialog.Body>
            <Dialog.Footer>
                <Dialog.Close>
                    Cancel
                    <Kbd shortcut="esc" />
                </Dialog.Close>
                <Dialog.Confirm disabled={!domain.trim()} onclick={addDomain}>
                    Add
                    <Kbd shortcut="enter" />
                </Dialog.Confirm>
            </Dialog.Footer>
        </Dialog.Content>
    </Dialog.Root>

    {#if addedDomain}
        <p role="status" class="text-sm text-foreground-muted">
            {`${addedDomain} added to the preview.`}
        </p>
    {/if}
</div>
~~~~

### Hero

~~~~svelte
<script lang="ts">
    import { Globe02Icon as Globe } from '@hugeicons/core-free-icons';
    import * as Dialog from '@mielui/svelte/components/dialog';
    import { Input } from '@mielui/svelte/components/input';
    import Kbd from '@mielui/svelte/components/kbd';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';

    let open = $state(false);
    let domain = $state('');
    let addedDomain = $state('');

    function addDomain() {
        addedDomain = domain.trim();
        domain = '';
    }
</script>

<div class="flex flex-col items-center gap-3">
    <Dialog.Root bind:open>
        <Dialog.Trigger>Add domain</Dialog.Trigger>
        <Dialog.Content size="sm">
            <Dialog.Header>
                <div class="flex items-center gap-2.5">
                    <HugeiconsIcon icon={Globe} size={18} class="text-foreground-muted" />
                    <Dialog.Title>Add a domain</Dialog.Title>
                </div>
                <Dialog.Description>
                    Add a domain to this local preview. No DNS settings are changed.
                </Dialog.Description>
            </Dialog.Header>
            <Dialog.Body class="gap-4">
                <Input
                    bind:value={domain}
                    label="Domain"
                    placeholder="example.com"
                    description="Enter a domain name, such as example.com."
                />
            </Dialog.Body>
            <Dialog.Footer>
                <Dialog.Close>
                    Cancel
                    <Kbd shortcut="esc" />
                </Dialog.Close>
                <Dialog.Confirm disabled={!domain.trim()} onclick={addDomain}>
                    Add
                    <Kbd shortcut="enter" />
                </Dialog.Confirm>
            </Dialog.Footer>
        </Dialog.Content>
    </Dialog.Root>

    {#if addedDomain}
        <p role="status" class="text-sm text-foreground-muted">
            {`${addedDomain} added to the preview.`}
        </p>
    {/if}
</div>
~~~~

### Nested

~~~~svelte
<script lang="ts">
    import * as Dialog from '@mielui/svelte/components/dialog';
    import Kbd from '@mielui/svelte/components/kbd';

    let open = $state(false);
    let nestedOpen = $state(false);
</script>

<Dialog.Root bind:open>
    <Dialog.Trigger>Edit project</Dialog.Trigger>
    <Dialog.Content size="sm">
        <Dialog.Header>
            <Dialog.Title>Edit project</Dialog.Title>
            <Dialog.Description>
                Change the name, or transfer this project to someone else.
            </Dialog.Description>
        </Dialog.Header>
        <Dialog.Body>
            <Dialog.Root bind:open={nestedOpen}>
                <Dialog.Trigger variant="outline">Transfer ownership</Dialog.Trigger>
                <Dialog.Content size="sm">
                    <Dialog.Header>
                        <Dialog.Title>Transfer ownership</Dialog.Title>
                        <Dialog.Description>
                            The new owner gets full control. You will remain a member.
                        </Dialog.Description>
                    </Dialog.Header>
                    <Dialog.Footer>
                        <Dialog.Close>
                            Cancel
                            <Kbd shortcut="esc" />
                        </Dialog.Close>
                        <Dialog.Confirm>
                            Transfer
                            <Kbd shortcut="enter" />
                        </Dialog.Confirm>
                    </Dialog.Footer>
                </Dialog.Content>
            </Dialog.Root>
        </Dialog.Body>
        <Dialog.Footer>
            <Dialog.Close>
                Cancel
                <Kbd shortcut="esc" />
            </Dialog.Close>
            <Dialog.Confirm>
                Save
                <Kbd shortcut="enter" />
            </Dialog.Confirm>
        </Dialog.Footer>
    </Dialog.Content>
</Dialog.Root>
~~~~

### Required Choice

~~~~svelte
<script lang="ts">
    import * as Dialog from '@mielui/svelte/components/dialog';

    let plan = $state('No plan chosen yet');
</script>

<div class="flex flex-col items-center gap-3">
    <Dialog.Root>
        <Dialog.Trigger>Choose a plan</Dialog.Trigger>
        <Dialog.Content showClose={false} allowEscape={false} allowClickOutside={false}>
            <Dialog.Header>
                <Dialog.Title>Your trial has ended</Dialog.Title>
                <Dialog.Description>
                    Pick a plan to keep working. Escape and the backdrop do nothing here.
                </Dialog.Description>
            </Dialog.Header>
            <Dialog.Footer>
                <Dialog.Close
                    onclick={() => {
                        plan = 'Free plan';
                    }}
                >
                    Stay on Free
                </Dialog.Close>
                <Dialog.Confirm
                    onclick={() => {
                        plan = 'Team plan';
                    }}
                >
                    Upgrade to Team
                </Dialog.Confirm>
            </Dialog.Footer>
        </Dialog.Content>
    </Dialog.Root>
    <p class="text-sm text-foreground-muted">{plan}</p>
</div>
~~~~

### Size Compact

~~~~svelte
<script lang="ts">
    import * as Dialog from '@mielui/svelte/components/dialog';
    import Kbd from '@mielui/svelte/components/kbd';

    let open = $state(false);
</script>

<Dialog.Root bind:open>
    <Dialog.Trigger variant="outline">Open compact dialog</Dialog.Trigger>
    <Dialog.Content size="sm">
        <Dialog.Header>
            <Dialog.Title>Quick action</Dialog.Title>
            <Dialog.Description>
                Keep short confirmations focused with the compact size.
            </Dialog.Description>
        </Dialog.Header>
        <Dialog.Footer>
            <Dialog.Close>
                Cancel
                <Kbd shortcut="esc" />
            </Dialog.Close>
            <Dialog.Confirm>
                Continue
                <Kbd shortcut="enter" />
            </Dialog.Confirm>
        </Dialog.Footer>
    </Dialog.Content>
</Dialog.Root>
~~~~

### Size Large

~~~~svelte
<script lang="ts">
    import * as Dialog from '@mielui/svelte/components/dialog';
    import Kbd from '@mielui/svelte/components/kbd';

    let open = $state(false);
</script>

<Dialog.Root bind:open>
    <Dialog.Trigger variant="outline">Open large dialog</Dialog.Trigger>
    <Dialog.Content size="lg">
        <Dialog.Header>
            <Dialog.Title>Review your workspace</Dialog.Title>
            <Dialog.Description>
                Use a larger surface when the dialog needs room for supporting details or multiple
                controls.
            </Dialog.Description>
        </Dialog.Header>
        <Dialog.Body>
            <p class="text-sm text-foreground-muted">
                Invite teammates, configure permissions, and review workspace settings in one place.
            </p>
        </Dialog.Body>
        <Dialog.Footer>
            <Dialog.Close>
                Cancel
                <Kbd shortcut="esc" />
            </Dialog.Close>
            <Dialog.Confirm>
                Save changes
                <Kbd shortcut="enter" />
            </Dialog.Confirm>
        </Dialog.Footer>
    </Dialog.Content>
</Dialog.Root>
~~~~

### Size Wide

~~~~svelte
<script lang="ts">
    import * as Dialog from '@mielui/svelte/components/dialog';
    import Kbd from '@mielui/svelte/components/kbd';

    let open = $state(false);
</script>

<Dialog.Root bind:open>
    <Dialog.Trigger variant="outline">Open wide dialog</Dialog.Trigger>
    <Dialog.Content size="xl">
        <Dialog.Header>
            <Dialog.Title>Import project data</Dialog.Title>
            <Dialog.Description>
                Wide modals work well for previews, tables, and other content that benefits from
                horizontal space.
            </Dialog.Description>
        </Dialog.Header>
        <Dialog.Body>
            <div class="grid gap-4 sm:grid-cols-2">
                <div>
                    <p class="font-medium text-foreground">Repositories</p>
                    <p class="mt-1 text-sm text-foreground-muted">
                        Choose which repositories to import.
                    </p>
                </div>
                <div>
                    <p class="font-medium text-foreground">Environment</p>
                    <p class="mt-1 text-sm text-foreground-muted">
                        Set the target deployment environment.
                    </p>
                </div>
            </div>
        </Dialog.Body>
        <Dialog.Footer>
            <Dialog.Close>
                Cancel
                <Kbd shortcut="esc" />
            </Dialog.Close>
            <Dialog.Confirm>
                Import
                <Kbd shortcut="enter" />
            </Dialog.Confirm>
        </Dialog.Footer>
    </Dialog.Content>
</Dialog.Root>
~~~~

### With Select

~~~~svelte
<script lang="ts">
    import * as Dialog from '@mielui/svelte/components/dialog';
    import Kbd from '@mielui/svelte/components/kbd';
    import Label from '@mielui/svelte/components/label';
    import * as Select from '@mielui/svelte/components/select';

    let open = $state(false);
    let role = $state('editor');

    const roles = [
        {
            value: 'viewer',
            label: 'Viewer'
        },
        {
            value: 'editor',
            label: 'Editor'
        },
        {
            value: 'admin',
            label: 'Admin'
        }
    ];
</script>

<Dialog.Root bind:open>
    <Dialog.Trigger>Invite member</Dialog.Trigger>
    <Dialog.Content>
        <Dialog.Header>
            <Dialog.Title>Invite member</Dialog.Title>
            <Dialog.Description>Choose a role for the new member.</Dialog.Description>
        </Dialog.Header>
        <Dialog.Body>
            <div class="flex flex-col gap-2">
                <Label for="invite-member-role">Role</Label>
                <Select.Root bind:value={role}>
                    <Select.Trigger id="invite-member-role" class="w-full">
                        <Select.Value placeholder="Select a role" />
                    </Select.Trigger>
                    <Select.Content>
                        {#each roles as option (option.value)}
                            <Select.Item value={option.value}>
                                {option.label}
                            </Select.Item>
                        {/each}
                    </Select.Content>
                </Select.Root>
            </div>
        </Dialog.Body>
        <Dialog.Footer>
            <Dialog.Close>
                Cancel
                <Kbd shortcut="esc" />
            </Dialog.Close>
            <Dialog.Confirm>
                Invite
                <Kbd shortcut="enter" />
            </Dialog.Confirm>
        </Dialog.Footer>
    </Dialog.Content>
</Dialog.Root>
~~~~

For the rendered reference, visit [/docs/components/dialog](/docs/components/dialog).
