> For AI agents: the complete documentation index is available at https://wallarm.github.io/design-system/llms.txt, the full documentation bundle is available at https://wallarm.github.io/design-system/llms-full.txt.

# Dialog

A panel the reader has to resolve before carrying on — reach for `Drawer` when they should be able to keep it open and keep working, and `Popover` when the content is a detail rather than a task. It is `Drawer` with `kind='dialog'`, which shares the panel but keeps the two families' nesting separate: a dialog only steps back for another dialog.

[Source](https://github.com/wallarm/design-system/tree/main/packages/design-system/src/components/Dialog) · [Storybook](https://wallarm.github.io/design-system/storybook/?path=/docs/overlay-dialog--overview)

```tsx
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';

export const Basic = () => {
  return (
    <Dialog data-testid='dialog'>
      <DialogTrigger asChild>
        <Button variant='outline' color='neutral'>
          Open Dialog
        </Button>
      </DialogTrigger>

      <DialogContent>
        <DialogHeader>
          <DialogTitle>Dialog Title</DialogTitle>
        </DialogHeader>

        <DialogBody>
          <ContentPlaceholder />
        </DialogBody>
      </DialogContent>
    </Dialog>
  );
};
```

## Usage

```tsx
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';
```

## Examples

### With footer

`DialogFooter` holds the decision. Put the confirming action last, since that is where the
eye finishes.

```tsx
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogContent, DialogFooter, DialogFooterControls, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';

export const WithFooter = () => {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant='outline' color='neutral'>
          Open Dialog
        </Button>
      </DialogTrigger>

      <DialogContent>
        <DialogHeader>
          <DialogTitle>Dialog's title</DialogTitle>
        </DialogHeader>

        <DialogBody>
          <ContentPlaceholder />
        </DialogBody>

        <DialogFooter>
          <DialogFooterControls>
            <Button variant='ghost' color='neutral' size='large'>
              Button
            </Button>
            <Button variant='primary' color='brand' size='large'>
              Button
            </Button>
          </DialogFooterControls>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
};
```

### With description

`DialogDescription` states the consequence, which is what turns a confirmation from a speed
bump into a decision. `DialogHeader` finds the title and description among its children and
stacks them, so the close button stays right-aligned however many it gets.

```tsx
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogFooterControls, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';

export const WithDescription = () => {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant='outline' color='neutral'>
          Open Dialog
        </Button>
      </DialogTrigger>

      <DialogContent>
        <DialogHeader>
          <DialogTitle>Dialog's title</DialogTitle>
          <DialogDescription>Description</DialogDescription>
        </DialogHeader>

        <DialogBody>
          <ContentPlaceholder />
        </DialogBody>

        <DialogFooter>
          <DialogFooterControls>
            <Button variant='ghost' color='neutral' size='large'>
              Button
            </Button>
            <Button variant='primary' color='brand' size='large'>
              Button
            </Button>
          </DialogFooterControls>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
};
```

### With footer left actions

A left-aligned action for the escape route — cancel, learn more — kept away from the
confirming pair on the right.

```tsx
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogClose, DialogContent, DialogFooter, DialogFooterControls, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';
import { Switch, SwitchControl, SwitchLabel } from '@wallarm-org/design-system/Switch';

export const WithFooterLeftActions = () => (
  <Dialog>
    <DialogTrigger asChild>
      <Button variant='outline' color='neutral'>
        Open with Footer Actions
      </Button>
    </DialogTrigger>
    <DialogContent>
      <DialogHeader>
        <DialogTitle>Footer with Left Actions</DialogTitle>
      </DialogHeader>

      <DialogBody>
        <ContentPlaceholder />
      </DialogBody>

      <DialogFooter>
        <DialogFooterControls placement='left'>
          <Switch>
            <SwitchControl />
            <SwitchLabel>Remember choice</SwitchLabel>
          </Switch>
        </DialogFooterControls>

        <DialogClose asChild>
          <Button variant='ghost' color='neutral' size='large'>
            Cancel
          </Button>
        </DialogClose>
        <Button variant='primary' color='brand' size='large'>
          Apply
        </Button>
      </DialogFooter>
    </DialogContent>
  </Dialog>
);
```

### Sizes

The standard widths. Pick by the content, not the importance: a wide dialog for a short
question reads as a mistake.

```tsx
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';
import { HStack } from '@wallarm-org/design-system/Stack';
import { DIALOG_SIZES } from './constants';

export const Sizes = () => (
  <HStack gap={8} justify='center'>
    <Dialog width={DIALOG_SIZES.small}>
      <DialogTrigger asChild>
        <Button variant='outline' color='neutral'>
          Open Small ({DIALOG_SIZES.small}px)
        </Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Small Dialog ({DIALOG_SIZES.small}px)</DialogTitle>
        </DialogHeader>

        <DialogBody>
          <ContentPlaceholder />
        </DialogBody>
      </DialogContent>
    </Dialog>

    <Dialog width={DIALOG_SIZES.medium}>
      <DialogTrigger asChild>
        <Button variant='outline' color='neutral'>
          Open Medium ({DIALOG_SIZES.medium}px)
        </Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Medium Dialog ({DIALOG_SIZES.medium}px)</DialogTitle>
        </DialogHeader>

        <DialogBody>
          <ContentPlaceholder />
        </DialogBody>
      </DialogContent>
    </Dialog>

    <Dialog width={DIALOG_SIZES.large}>
      <DialogTrigger asChild>
        <Button variant='outline' color='neutral'>
          Open Large ({DIALOG_SIZES.large}px)
        </Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Large Dialog ({DIALOG_SIZES.large}px)</DialogTitle>
        </DialogHeader>

        <DialogBody>
          <ContentPlaceholder />
        </DialogBody>
      </DialogContent>
    </Dialog>
  </HStack>
);
```

### Custom sizes

Explicit `width`, `minWidth` and `maxWidth` for content the standard steps do not fit.

```tsx
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';
import { HStack } from '@wallarm-org/design-system/Stack';

export const CustomSizes = () => {
  return (
    <HStack gap={8} justify='center'>
      <Dialog width='50%'>
        <DialogTrigger asChild>
          <Button variant='outline' color='neutral'>
            50% Width
          </Button>
        </DialogTrigger>

        <DialogContent>
          <DialogHeader>
            <DialogTitle>50% Width Dialog</DialogTitle>
          </DialogHeader>

          <DialogBody>
            <div className='py-12'>
              <p>This dialog takes 50% of the viewport width.</p>
            </div>
          </DialogBody>
        </DialogContent>
      </Dialog>

      <Dialog width={1000}>
        <DialogTrigger asChild>
          <Button variant='outline' color='neutral'>
            1000px Width
          </Button>
        </DialogTrigger>

        <DialogContent>
          <DialogHeader>
            <DialogTitle>1000px Width Dialog</DialogTitle>
          </DialogHeader>

          <DialogBody>
            <div className='py-12'>
              <p>This dialog has a fixed width of 1000px.</p>
            </div>
          </DialogBody>
        </DialogContent>
      </Dialog>
    </HStack>
  );
};
```

### Scrollable

The body scrolls while header and footer stay put, so the decision never scrolls out of
reach.

```tsx
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';

export const Scrollable = () => {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant='outline' color='neutral'>
          Open Dialog with Scroll
        </Button>
      </DialogTrigger>

      <DialogContent>
        <DialogHeader>
          <DialogTitle>Scrollable Content</DialogTitle>
        </DialogHeader>

        <DialogBody>
          <div className='flex flex-col gap-16'>
            {Array.from({ length: 20 }).map((_, i) => (
              <ContentPlaceholder key={i} height={100} />
            ))}
          </div>
        </DialogBody>

        <DialogFooter>
          <DialogClose asChild>
            <Button variant='ghost' color='neutral' size='large'>
              Close
            </Button>
          </DialogClose>
          <Button variant='primary' color='brand' size='large'>
            Confirm
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
};
```

### With input at edge

A field against the panel edge, which is where focus rings and shadows get clipped if the
padding is wrong.

```tsx
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';
import { Input } from '@wallarm-org/design-system/Input';

export const WithInputAtEdge = () => {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant='outline' color='neutral'>
          Open Dialog with Edge Input
        </Button>
      </DialogTrigger>

      <DialogContent>
        <DialogHeader>
          <DialogTitle>Delete account</DialogTitle>
        </DialogHeader>

        <DialogBody>
          <Input placeholder='Type your email to confirm' data-testid='edge-input' />
        </DialogBody>

        <DialogFooter>
          <DialogClose asChild>
            <Button variant='ghost' color='neutral' size='large'>
              Cancel
            </Button>
          </DialogClose>
          <Button variant='primary' color='destructive' size='large'>
            Delete
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
};
```

### With input at edge scrollable

The same, with scrolling — the case where a focus ring can be cut by the scroll container
rather than the panel.

```tsx
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';
import { Input } from '@wallarm-org/design-system/Input';

export const WithInputAtEdgeScrollable = () => {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant='outline' color='neutral'>
          Open Scrollable Dialog with Edge Input
        </Button>
      </DialogTrigger>

      <DialogContent>
        <DialogHeader>
          <DialogTitle>Delete account</DialogTitle>
        </DialogHeader>

        <DialogBody>
          <div className='flex flex-col gap-16'>
            {Array.from({ length: 10 }).map((_, i) => (
              <ContentPlaceholder key={i} height={100} />
            ))}

            <Input placeholder='Type your email to confirm' data-testid='edge-input' />
          </div>
        </DialogBody>

        <DialogFooter>
          <DialogClose asChild>
            <Button variant='ghost' color='neutral' size='large'>
              Cancel
            </Button>
          </DialogClose>
          <Button variant='primary' color='destructive' size='large'>
            Delete
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
};
```

### With nested drawer

A drawer opened from a dialog. Different families, so neither pushes the other back; they
stack.

```tsx
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';
import { Drawer, DrawerBody, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from '@wallarm-org/design-system/Drawer';
import { PanelRight } from '@wallarm-org/design-system/icons';
import { VStack } from '@wallarm-org/design-system/Stack';
import { Text } from '@wallarm-org/design-system/Text';

export const WithNestedDrawer = () => {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant='outline' color='neutral'>
          Open dialog with drawer inside
        </Button>
      </DialogTrigger>

      <DialogContent>
        <DialogHeader>
          <DialogTitle>Dialog stays in place</DialogTitle>
        </DialogHeader>

        <DialogBody>
          <VStack gap={12} align='start'>
            <Text>Opening the drawer below must not scale or shift this dialog.</Text>

            <Drawer>
              <DrawerTrigger asChild>
                <Button variant='outline' color='neutral'>
                  <PanelRight />
                  Open drawer
                </Button>
              </DrawerTrigger>

              <DrawerContent>
                <DrawerHeader>
                  <DrawerTitle>Drawer over a dialog</DrawerTitle>
                </DrawerHeader>

                <DrawerBody>
                  <ContentPlaceholder height={200} />
                </DrawerBody>
              </DrawerContent>
            </Drawer>
          </VStack>
        </DialogBody>
      </DialogContent>
    </Dialog>
  );
};
```

### With nested select

A `Select` inside a dialog, checking that its menu escapes the panel rather than being
clipped by it.

```tsx
import { createListCollection } from '@ark-ui/react/collection';
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@wallarm-org/design-system/DropdownMenu';
import { PanelRight } from '@wallarm-org/design-system/icons';
import { Select, SelectButton, SelectContent, SelectOption, SelectOptionIndicator, SelectOptionText, SelectPositioner } from '@wallarm-org/design-system/Select';
import { VStack } from '@wallarm-org/design-system/Stack';

export const WithNestedSelect = () => {
  const collection = createListCollection({
    items: [
      { label: 'React', value: 'react' },
      { label: 'Vue', value: 'vue' },
      { label: 'Angular', value: 'angular' },
    ],
  });

  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant='outline' color='neutral'>
          Open dialog with nested select
        </Button>
      </DialogTrigger>

      <DialogContent>
        <DialogHeader>
          <DialogTitle>[Level 1] Main Dialog</DialogTitle>

          <Dialog>
            <DialogTrigger asChild>
              <Button variant='ghost' color='neutral' size='small'>
                <PanelRight />
                Open nested dialog
              </Button>
            </DialogTrigger>

            <DialogContent>
              <DialogHeader>
                <DialogTitle>[Level 2] With Select</DialogTitle>
              </DialogHeader>

              <DialogBody>
                <VStack gap={12} align='start'>
                  <Select collection={collection} data-testid='nested-select'>
                    <SelectButton />

                    <SelectPositioner>
                      <SelectContent>
                        {collection.items.map(item => (
                          <SelectOption key={item.value} item={item}>
                            <SelectOptionText>{item.label}</SelectOptionText>
                            <SelectOptionIndicator />
                          </SelectOption>
                        ))}
                      </SelectContent>
                    </SelectPositioner>
                  </Select>

                  <DropdownMenu data-testid='nested-dropdown'>
                    <DropdownMenuTrigger asChild>
                      <Button variant='outline' color='neutral'>
                        Open menu
                      </Button>
                    </DropdownMenuTrigger>
                    <DropdownMenuContent>
                      <DropdownMenuItem>First action</DropdownMenuItem>
                      <DropdownMenuItem>Second action</DropdownMenuItem>
                    </DropdownMenuContent>
                  </DropdownMenu>
                </VStack>
              </DialogBody>
            </DialogContent>
          </Dialog>
        </DialogHeader>

        <DialogBody>
          <ContentPlaceholder height={200} />
        </DialogBody>
      </DialogContent>
    </Dialog>
  );
};
```

### With nested calendar

A `Calendar` inside a dialog, the same check for a popover that is taller than the panel.

```tsx
import { Button } from '@wallarm-org/design-system/Button';
import { Calendar, CalendarBody, CalendarContent, CalendarGrids, CalendarTrigger } from '@wallarm-org/design-system/Calendar';
import { Dialog, DialogBody, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';
import { PanelRight } from '@wallarm-org/design-system/icons';

export const WithNestedCalendar = () => {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant='outline' color='neutral'>
          Open dialog with nested calendar
        </Button>
      </DialogTrigger>

      <DialogContent>
        <DialogHeader>
          <DialogTitle>[Level 1] Main Dialog</DialogTitle>

          <Dialog>
            <DialogTrigger asChild>
              <Button variant='ghost' color='neutral' size='small'>
                <PanelRight />
                Open nested dialog
              </Button>
            </DialogTrigger>

            <DialogContent>
              <DialogHeader>
                <DialogTitle>[Level 2] With Calendar</DialogTitle>
              </DialogHeader>

              <DialogBody>
                <Calendar type='single'>
                  <CalendarTrigger asChild>
                    <Button variant='outline' color='neutral'>
                      Open calendar
                    </Button>
                  </CalendarTrigger>
                  <CalendarContent>
                    <CalendarBody>
                      <CalendarGrids />
                    </CalendarBody>
                  </CalendarContent>
                </Calendar>
              </DialogBody>
            </DialogContent>
          </Dialog>
        </DialogHeader>

        <DialogBody>
          <ContentPlaceholder height={200} />
        </DialogBody>
      </DialogContent>
    </Dialog>
  );
};
```

### Controlled

Owning `open`, which is what you need when something outside the dialog decides it should
close.

```tsx
import { useState } from 'react';
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@wallarm-org/design-system/Dialog';

export const Controlled = () => {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Button variant='outline' color='neutral' onClick={() => setOpen(true)}>
        Open Controlled Dialog
      </Button>

      <Dialog open={open} onOpenChange={setOpen}>
        <DialogContent>
          <DialogHeader>
            <DialogTitle>Controlled Dialog</DialogTitle>
          </DialogHeader>

          <DialogBody>
            <div className='py-12'>
              <p className='mb-16'>This dialog is controlled externally.</p>
              <ContentPlaceholder />
            </div>
          </DialogBody>

          <DialogFooter>
            <DialogClose asChild>
              <Button variant='ghost' color='neutral' size='large'>
                Cancel
              </Button>
            </DialogClose>
            <Button variant='primary' color='brand' size='large'>
              Save Changes
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </>
  );
};
```

### No closable on esc

Escape disabled, for a decision that must not be dismissed by reflex. Use it sparingly — it
takes away the exit readers expect.

```tsx
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';

export const NoClosableOnEsc = () => {
  return (
    <Dialog closeOnEscape={false}>
      <DialogTrigger asChild>
        <Button variant='outline' color='neutral'>
          Open Dialog
        </Button>
      </DialogTrigger>

      <DialogContent>
        <DialogHeader>
          <DialogTitle>Dialog Title</DialogTitle>
        </DialogHeader>

        <DialogBody>
          <ContentPlaceholder />
        </DialogBody>
      </DialogContent>
    </Dialog>
  );
};
```

### No overlay

Without the backdrop. The panel stops reading as blocking, which is usually the wrong signal
for a dialog.

```tsx
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';
import { VStack } from '@wallarm-org/design-system/Stack';
import { Text } from '@wallarm-org/design-system/Text';

export const NoOverlay = () => {
  return (
    <VStack gap={12}>
      <Text>
        Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has
        been the industry's standard dummy text ever since the 1500s, when an unknown printer took a
        galley of type and scrambled it to make a type specimen book. It has survived not only five
        centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
        It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum
        passages, and more recently with desktop publishing software like Aldus PageMaker including
        versions of Lorem Ipsum. Why do we use it? It is a long established fact that a reader will
        be distracted by the readable content of a page when looking at its layout. The point of
        using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed
        to using 'Content here, content here', making it look like readable English. Many desktop
        publishing packages and web page editors now use Lorem Ipsum as their default model text,
        and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various
        versions have evolved over the years, sometimes by accident, sometimes on purpose (injected
        humour and the like).
      </Text>

      <Dialog overlay={false}>
        <DialogTrigger asChild>
          <Button variant='outline' color='neutral'>
            Open without Overlay
          </Button>
        </DialogTrigger>

        <DialogContent>
          <DialogHeader>
            <DialogTitle>No Overlay</DialogTitle>
          </DialogHeader>

          <DialogBody>
            <div className='py-12'>
              <p>This dialog has no overlay backdrop.</p>
            </div>
          </DialogBody>
        </DialogContent>
      </Dialog>

      <Text>
        Where does it come from? Contrary to popular belief, Lorem Ipsum is not simply random text.
        It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years
        old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up
        one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going
        through the cites of the word in classical literature, discovered the undoubtable source.
        Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The
        Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the
        theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem
        ipsum dolor sit amet..", comes from a line in section 1.10.32. The standard chunk of Lorem
        Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and
        1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact
        original form, accompanied by English versions from the 1914 translation by H. Rackham.
      </Text>
    </VStack>
  );
};
```

### With nested

A dialog inside a dialog: the outer one steps back so the depth is visible. Two is the most a
reader can follow.

```tsx
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';
import { PanelRight } from '@wallarm-org/design-system/icons';
import { VStack } from '@wallarm-org/design-system/Stack';
import { Text } from '@wallarm-org/design-system/Text';

export const WithNested = () => {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant='outline' color='neutral'>
          1st level dialog
        </Button>
      </DialogTrigger>

      <DialogContent>
        <DialogHeader>
          <DialogTitle>[Level 1] Main Dialog</DialogTitle>

          {/* Level 2 dialog */}
          <Dialog>
            <DialogTrigger asChild>
              <Button variant='ghost' color='neutral' size='small'>
                <PanelRight />
                2nd level dialog
              </Button>
            </DialogTrigger>

            <DialogContent>
              <DialogHeader>
                <DialogTitle>[Level 2] Detail View</DialogTitle>
                {/* Level 3 dialog */}
                <Dialog>
                  <DialogTrigger asChild>
                    <Button variant='ghost' color='neutral' size='small'>
                      <PanelRight />
                      3nd level dialog
                    </Button>
                  </DialogTrigger>

                  <DialogContent>
                    <DialogHeader>
                      <DialogTitle>[Level 3] Deep Nested</DialogTitle>
                    </DialogHeader>

                    <DialogBody>
                      <VStack gap={12} align='start'>
                        <Text>Level 3! Unlimited nesting works.</Text>
                        <ContentPlaceholder height={150} />
                      </VStack>
                    </DialogBody>

                    <DialogFooter>
                      <DialogClose asChild>
                        <Button size='large' variant='ghost' color='neutral'>
                          Back
                        </Button>
                      </DialogClose>
                    </DialogFooter>
                  </DialogContent>
                </Dialog>
              </DialogHeader>

              <DialogBody>
                <VStack gap={12} align='start'>
                  <Text>This is Level 2 dialog. Main dialog is pushed back.</Text>

                  <ContentPlaceholder height={150} />
                </VStack>
              </DialogBody>

              <DialogFooter>
                <DialogClose asChild>
                  <Button variant='ghost' color='neutral' size='large'>
                    Back
                  </Button>
                </DialogClose>
                <Button variant='primary' color='brand' size='large'>
                  Apply
                </Button>
              </DialogFooter>
            </DialogContent>
          </Dialog>
        </DialogHeader>

        <DialogBody>
          <VStack gap={12} align='start'>
            <Text>This is Level 1 dialog. Click above to open nested dialogs:</Text>

            <ContentPlaceholder height={150} />
          </VStack>
        </DialogBody>

        <DialogFooter>
          <DialogClose asChild>
            <Button variant='ghost' color='neutral' size='large'>
              Cancel
            </Button>
          </DialogClose>
          <Button variant='primary' color='brand' size='large'>
            Save
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
};
```

### With tabs

Tabs inside a dialog, for a task with a couple of facets — not as a way to fit a whole form
into one panel.

```tsx
import { useState } from 'react';
import { Button } from '@wallarm-org/design-system/Button';
import { Dialog, DialogBody, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@wallarm-org/design-system/Dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@wallarm-org/design-system/Tabs';

export const WithTabs = () => {
  const [activeTab, setActiveTab] = useState('tab1');

  return (
    <Dialog width={960}>
      <DialogTrigger asChild>
        <Button variant='outline' color='neutral'>
          Open Dialog with Tabs
        </Button>
      </DialogTrigger>

      <DialogContent>
        <Tabs value={activeTab} onChange={setActiveTab} asChild>
          <DialogHeader>
            <DialogTitle>Tabbed Content</DialogTitle>
          </DialogHeader>

          <TabsList>
            <TabsTrigger value='tab1'>General</TabsTrigger>
            <TabsTrigger value='tab2'>Settings</TabsTrigger>
            <TabsTrigger value='tab3'>Advanced</TabsTrigger>
          </TabsList>

          <DialogBody>
            <div className='pt-12'>
              <TabsContent value='tab1'>
                <h3 className='text-lg font-medium mb-8'>General Settings</h3>
                <ContentPlaceholder height={300} />
              </TabsContent>
              <TabsContent value='tab2'>
                <h3 className='text-lg font-medium mb-8'>Configuration</h3>
                <ContentPlaceholder height={300} />
              </TabsContent>
              <TabsContent value='tab3'>
                <h3 className='text-lg font-medium mb-8'>Advanced Options</h3>
                <ContentPlaceholder height={300} />
              </TabsContent>
            </div>
          </DialogBody>
        </Tabs>

        <DialogFooter>
          <DialogClose asChild>
            <Button variant='ghost' color='neutral' size='large'>
              Cancel
            </Button>
          </DialogClose>
          <Button variant='primary' color='brand' size='large'>
            Apply Settings
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
};
```

## Props

| Prop                | Type                                            | Default                        | Description                                                                                                                                                                                                                                                                            |
| ------------------- | ----------------------------------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| kind                | `DrawerKind \| undefined`                       |                                | Which overlay family this root belongs to. Drives the same-kind nesting counter behind the pushed-back animation: a Drawer only pushes back for a nested Drawer, a Dialog only for a nested Dialog. Set to 'dialog' by the Dialog component — consumers should not need to touch this. |
| open                | `boolean \| undefined`                          |                                | Controlled open state                                                                                                                                                                                                                                                                  |
| onOpenChange        | `((open: boolean) => void) \| undefined`        |                                | Controlled open change handler                                                                                                                                                                                                                                                         |
| closeOnEscape       | `boolean \| undefined`                          |                                | Whether ESC key closes the drawer (default: true)                                                                                                                                                                                                                                      |
| closeOnOutsideClick | `boolean \| undefined`                          |                                | Whether clicking outside closes the drawer (default: true)                                                                                                                                                                                                                             |
| overlay             | `boolean \| undefined`                          |                                | Overlay visibility (default: true)                                                                                                                                                                                                                                                     |
| modal               | `boolean \| undefined`                          |                                | Whether the drawer is modal — blocks interaction with the rest of the page (default: true)                                                                                                                                                                                             |
| width               | `string \| number \| undefined`                 | `DIALOG_SIZES.medium`          | Width - number for pixels, string for percentage (e.g., "50%")                                                                                                                                                                                                                         |
| minWidth            | `number \| undefined`                           | `DIALOG_WIDTH_CONSTRAINTS.min` | Minimum width in pixels                                                                                                                                                                                                                                                                |
| maxWidth            | `number \| undefined`                           | `DIALOG_WIDTH_CONSTRAINTS.max` | Maximum width in pixels                                                                                                                                                                                                                                                                |
| onInteractOutside   | `complex`                                       |                                | Fired when the user interacts outside the drawer (e.g. clicks the backdrop).                                                                                                                                                                                                           |
| onEscapeKeyDown     | `((event: KeyboardEvent) => void) \| undefined` |                                | Fired when the user presses the Escape key while the drawer is open.                                                                                                                                                                                                                   |
| data-testid         | `string \| undefined`                           |                                |                                                                                                                                                                                                                                                                                        |

## Explorer

