Skip to content

Dialog ​

A neo-brutalist modal dialog built on top of reka-ui's Dialog primitive. Supports overlay, close button, and composable sub-components.

Demo ​

Preview

Installation ​

pnpm dlx brutx-vue@latest add dialog

Usage ​

vue
<script setup>
import { DialogRoot as Dialog, DialogTrigger, DialogClose } from 'reka-ui'
import { DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription } from 'brutx-ui-vue'
import { Button } from 'brutx-ui-vue'
</script>

<template>
    <Dialog>
        <DialogTrigger as-child>
            <Button variant="primary">Open Dialog</Button>
        </DialogTrigger>
        <DialogContent>
            <DialogHeader>
                <DialogTitle>Edit Profile</DialogTitle>
                <DialogDescription>
                    Make changes to your profile here. Click save when you're done.
                </DialogDescription>
            </DialogHeader>
            <DialogFooter>
                <DialogClose as-child>
                    <Button variant="outline">Cancel</Button>
                </DialogClose>
                <Button variant="primary">Save</Button>
            </DialogFooter>
        </DialogContent>
    </Dialog>
</template>

Sizes ​

Control the max-width of the dialog via the size prop on DialogContent. Defaults to default (max-w-lg).

SizeMax WidthUse Case
smmax-w-smConfirmation dialogs, simple prompts
defaultmax-w-lgStandard forms, general content
lgmax-w-2xlMulti-column forms, detailed settings
xlmax-w-4xlComplex content, data display
fullmax-w-[calc(100vw-2rem)]Large content, full-screen interactions
vue
<script setup>
import { DialogRoot as Dialog, DialogTrigger } from 'reka-ui'
import { DialogContent, DialogHeader, DialogTitle, DialogDescription } from 'brutx-ui-vue'
import { Button } from 'brutx-ui-vue'
</script>

<template>
    <Dialog>
        <DialogTrigger as-child>
            <Button variant="primary">Open Small Dialog</Button>
        </DialogTrigger>
        <DialogContent size="sm">
            <DialogHeader>
                <DialogTitle>Confirm Deletion</DialogTitle>
                <DialogDescription>This action cannot be undone. Are you sure you want to continue?</DialogDescription>
            </DialogHeader>
        </DialogContent>
    </Dialog>
</template>

Sub-components ​

ComponentDescription
DialogRoot component (import directly from reka-ui: import { DialogRoot as Dialog } from 'reka-ui')
DialogTriggerButton that opens the dialog (import directly from reka-ui: import { DialogTrigger } from 'reka-ui')
DialogContentDialog content panel with overlay
DialogHeaderHeader container
DialogFooterFooter container with flex layout
DialogTitleDialog title
DialogDescriptionDialog description text
DialogCloseClose button (import directly from reka-ui: import { DialogClose } from 'reka-ui')
DialogOverlayBackground overlay
DialogPortalPortal container (import directly from reka-ui: import { DialogPortal } from 'reka-ui')
DialogEnhancedEnhanced dialog with draggable and resizable support

DialogEnhanced Usage ​

An enhanced dialog that supports dragging and resizing:

vue
<script setup>
import { DialogRoot as Dialog, DialogTrigger } from 'reka-ui'
import { DialogEnhanced, DialogHeader, DialogTitle } from 'brutx-ui-vue'
</script>

<template>
    <Dialog>
        <DialogTrigger>Open Draggable Dialog</DialogTrigger>
        <DialogEnhanced
            draggable
            resizable
            :min-width="300"
            :min-height="200"
            drag-handle=".dialog-header"
        >
            <DialogHeader class="dialog-header">
                <DialogTitle>Draggable Dialog</DialogTitle>
            </DialogHeader>
            <p>Drag the title bar to move, drag the edges to resize</p>
        </DialogEnhanced>
    </Dialog>
</template>

API Reference ​

DialogContent

DialogContent — Props
NameTypeDefaultDescription
showCloseButton
boolean | undefined
true
Controls whether the built-in close button is rendered in the top-right corner of the content panel.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
true

Source

DialogContent.vue:22

packages/ui/src/components/dialog/DialogContent.vue

size
"default" | "full" | "lg" | "sm" | "xl" | undefined
"default"
Sets the width variant of the dialog content panel.
Details

Declared type

NonNullable<"default" | "lg" | "sm" | "xl" | "full" | null | undefined> | undefined

Literal values

  • "default"
  • "full"
  • "lg"
  • "sm"
  • "xl"

Default value details

Resolved value
"default"

Source

DialogContent.vue:23

packages/ui/src/components/dialog/DialogContent.vue

entrance
"fade-zoom" | "shutter" | undefined
"fade-zoom"
Selects the entrance animation variant for the content panel.
Details

Declared type

NonNullable<"fade-zoom" | "shutter" | null | undefined> | undefined

Literal values

  • "fade-zoom"
  • "shutter"

Default value details

Resolved value
"fade-zoom"

Source

DialogContent.vue:25

packages/ui/src/components/dialog/DialogContent.vue

forceMount
boolean | undefined
undefined
Passes the mount-preservation setting to the overlay and content primitives so they can remain mounted while closed.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
undefined

Source

DialogContent.vue:26

packages/ui/src/components/dialog/DialogContent.vue

class
string | undefined
undefined
Custom CSS classes added to the dialog content panel.
Details

Declared type

string | undefined

Default value details

Resolved value
undefined

Source

DialogContent.vue:27

packages/ui/src/components/dialog/DialogContent.vue

DialogContent — Slots
NameSignature or typeDescription
#default
{}
Renders the dialog body; the built-in close button is placed after the slot content.
Details

Declared type

{}

Source

DialogContent.vue

packages/ui/src/components/dialog/DialogContent.vue

DialogDescription

DialogDescription — Props
NameTypeDefaultDescription
class
string | undefined
—
Custom CSS classes added to the description element; other attributes such as id fall through to the Reka UI primitive.
Details

Declared type

string | undefined

Default value details

Not declared
—

Source

DialogDescription.vue:7

packages/ui/src/components/dialog/DialogDescription.vue

DialogDescription — Slots
NameSignature or typeDescription
#default
{}
The supporting description content for the dialog.
Details

Declared type

{}

Source

DialogDescription.vue

packages/ui/src/components/dialog/DialogDescription.vue

DialogEnhanced

DialogEnhanced — Props
NameTypeDefaultDescription
draggable
boolean | undefined
false
Enables pointer dragging of the content panel; dragging styles are not applied in fullscreen mode.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
false

Source

DialogEnhanced.vue:19

packages/ui/src/components/dialog/DialogEnhanced.vue

dragHandle
string | HTMLElement | undefined
undefined
Restricts drag start to a descendant matched by the CSS selector or to the specified HTMLElement.
Details

Declared type

string | HTMLElement | undefined

Default value details

Resolved value
undefined

Source

DialogEnhanced.vue:20

packages/ui/src/components/dialog/DialogEnhanced.vue

bounds
"viewport" | "parent" | { top: number; left: number; right: number; bottom: number; } | undefined
"viewport"
Constrains dragging to the parent, viewport, or the supplied top, left, right, and bottom boundaries.
Details

Declared type

"viewport" | "parent" | { top: number; left: number; right: number; bottom: number; } | undefined

Literal values

  • "parent"
  • "viewport"

Default value details

Resolved value
"viewport"

Source

DialogEnhanced.vue:21

packages/ui/src/components/dialog/DialogEnhanced.vue

initialPosition
{ x: number; y: number; } | undefined
undefined
Provides the initial x and y coordinates to the geometry controller.
Details

Declared type

{ x: number; y: number; } | undefined

Default value details

Resolved value
undefined

Source

DialogEnhanced.vue:22

packages/ui/src/components/dialog/DialogEnhanced.vue

resizable
boolean | undefined
false
Enables pointer resizing from the dialog edges.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
false

Source

DialogEnhanced.vue:23

packages/ui/src/components/dialog/DialogEnhanced.vue

minWidth
number | undefined
200
Sets the minimum width allowed during resizing, in pixels.
Details

Declared type

number | undefined

Default value details

Declaration
DIALOG_MIN_WIDTH_PX

Source: packages/ui/src/components/dialog/DialogEnhanced.vue

Resolved value
200

Source

DialogEnhanced.vue:24

packages/ui/src/components/dialog/DialogEnhanced.vue

minHeight
number | undefined
150
Sets the minimum height allowed during resizing, in pixels.
Details

Declared type

number | undefined

Default value details

Declaration
DIALOG_MIN_HEIGHT_PX

Source: packages/ui/src/components/dialog/DialogEnhanced.vue

Resolved value
150

Source

DialogEnhanced.vue:25

packages/ui/src/components/dialog/DialogEnhanced.vue

maxWidth
number | undefined
undefined
Sets an optional maximum width during resizing, in pixels.
Details

Declared type

number | undefined

Default value details

Resolved value
undefined

Source

DialogEnhanced.vue:26

packages/ui/src/components/dialog/DialogEnhanced.vue

maxHeight
number | undefined
undefined
Sets an optional maximum height during resizing, in pixels.
Details

Declared type

number | undefined

Default value details

Resolved value
undefined

Source

DialogEnhanced.vue:27

packages/ui/src/components/dialog/DialogEnhanced.vue

aspectRatio
number | undefined
undefined
Provides a locked width-to-height ratio for resizing through the geometry controller.
Details

Declared type

number | undefined

Default value details

Resolved value
undefined

Source

DialogEnhanced.vue:28

packages/ui/src/components/dialog/DialogEnhanced.vue

showCloseButton
boolean | undefined
true
Controls whether the built-in close button is rendered in the enhanced dialog content panel.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
true

Source

DialogEnhanced.vue:29

packages/ui/src/components/dialog/DialogEnhanced.vue

forceMount
boolean | undefined
undefined
Keeps the overlay and content mounted while closed; this takes precedence over destroyOnClose.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
undefined

Source

DialogEnhanced.vue:30

packages/ui/src/components/dialog/DialogEnhanced.vue

fullscreen
boolean | undefined
false
Places the content panel in a viewport-filling fullscreen layout and bypasses drag geometry styling.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
false

Source

DialogEnhanced.vue:32

packages/ui/src/components/dialog/DialogEnhanced.vue

beforeClose
(() => boolean | Promise<boolean>) | undefined
undefined
A synchronous or asynchronous check run before closing; returning false or throwing/rejecting prevents close events.
Details

Declared type

(() => boolean | Promise<boolean>) | undefined

Default value details

Resolved value
undefined

Source

DialogEnhanced.vue:34

packages/ui/src/components/dialog/DialogEnhanced.vue

destroyOnClose
boolean | undefined
false
Unloads default-slot content after closing and the configured delay; content is retained when forceMount is true.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
false

Source

DialogEnhanced.vue:36

packages/ui/src/components/dialog/DialogEnhanced.vue

destroyDelay
number | undefined
300
The number of milliseconds to wait after closing before unloading slot content when destroyOnClose is enabled.
Details

Declared type

number | undefined

Default value details

Declaration
DEFAULT_DIALOG_TRANSITION_MS

Source: packages/ui/src/components/dialog/DialogEnhanced.vue

Resolved value
300

Source

DialogEnhanced.vue:38

packages/ui/src/components/dialog/DialogEnhanced.vue

zIndex
number | undefined
undefined
Explicitly sets the CSS z-index of the dialog content panel.
Details

Declared type

number | undefined

Default value details

Resolved value
undefined

Source

DialogEnhanced.vue:40

packages/ui/src/components/dialog/DialogEnhanced.vue

class
string | undefined
undefined
Custom CSS classes added to the dialog content panel.
Details

Declared type

string | undefined

Default value details

Resolved value
undefined

Source

DialogEnhanced.vue:41

packages/ui/src/components/dialog/DialogEnhanced.vue

DialogEnhanced — Events
NameSignature or typeDescription
@open
[]
Emitted when the component mounts, indicating that the DialogEnhanced instance mounted rather than tracking each controlled open-state change.
Details

Declared type

[]

Source

DialogEnhanced.vue

packages/ui/src/components/dialog/DialogEnhanced.vue

@close
[]
Emitted when a close request is not rejected by beforeClose; without a hook, the request is accepted directly.
Details

Declared type

[]

Source

DialogEnhanced.vue

packages/ui/src/components/dialog/DialogEnhanced.vue

@update:open
[value: boolean]
Emits false for an accepted close request so the controlled DialogRoot open state can be set to closed.
Details

Declared type

[value: boolean]

Literal values

  • false
  • true

Source

DialogEnhanced.vue

packages/ui/src/components/dialog/DialogEnhanced.vue

DialogEnhanced — Slots
NameSignature or typeDescription
#default
{}
The dialog content; it may be unloaded after the close delay when destroyOnClose is enabled.
Details

Declared type

{}

Source

DialogEnhanced.vue

packages/ui/src/components/dialog/DialogEnhanced.vue

DialogFooter

DialogFooter — Props
NameTypeDefaultDescription
class
string | undefined
—
Custom CSS classes added to the footer action area.
Details

Declared type

string | undefined

Default value details

Not declared
—

Source

DialogFooter.vue:7

packages/ui/src/components/dialog/DialogFooter.vue

DialogFooter — Slots
NameSignature or typeDescription
#default
{}
Footer actions; the footer container is not rendered when the default slot is empty.
Details

Declared type

{}

Source

DialogFooter.vue

packages/ui/src/components/dialog/DialogFooter.vue

DialogHeader

DialogHeader — Props
NameTypeDefaultDescription
class
string | undefined
—
Custom CSS classes added to the title-area container.
Details

Declared type

string | undefined

Default value details

Not declared
—

Source

DialogHeader.vue:7

packages/ui/src/components/dialog/DialogHeader.vue

DialogHeader — Slots
NameSignature or typeDescription
#default
{}
The title, description, or other header content.
Details

Declared type

{}

Source

DialogHeader.vue

packages/ui/src/components/dialog/DialogHeader.vue

DialogOverlay

DialogOverlay — Props
NameTypeDefaultDescription
class
string | undefined
undefined
Custom CSS classes added to the backdrop primitive.
Details

Declared type

string | undefined

Default value details

Resolved value
undefined

Source

DialogOverlay.vue:9

packages/ui/src/components/dialog/DialogOverlay.vue

forceMount
boolean | undefined
undefined
Passes the force-mount setting to the Reka UI overlay primitive.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
undefined

Source

DialogOverlay.vue:11

packages/ui/src/components/dialog/DialogOverlay.vue

asChild
boolean | undefined
false
Delegates the overlay primitive element rendering to the child in the default slot.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
false

Source

DialogOverlay.vue:12

packages/ui/src/components/dialog/DialogOverlay.vue

pattern
boolean | undefined
false
Adds a dotted halftone background pattern to the overlay.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
false

Source

DialogOverlay.vue:14

packages/ui/src/components/dialog/DialogOverlay.vue

DialogOverlay — Slots
NameSignature or typeDescription
#default
{}
Provides the overlay child element when using asChild.
Details

Declared type

{}

Source

DialogOverlay.vue

packages/ui/src/components/dialog/DialogOverlay.vue

DialogTitle

DialogTitle — Props
NameTypeDefaultDescription
class
string | undefined
—
Custom CSS classes added to the dialog title primitive.
Details

Declared type

string | undefined

Default value details

Not declared
—

Source

DialogTitle.vue:7

packages/ui/src/components/dialog/DialogTitle.vue

DialogTitle — Slots
NameSignature or typeDescription
#default
{}
The dialog title text or content.
Details

Declared type

{}

Source

DialogTitle.vue

packages/ui/src/components/dialog/DialogTitle.vue

Reka UI Primitives ​

DialogRoot, DialogTrigger, DialogClose, and DialogPortal are primitives imported directly from reka-ui in the examples. DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, DialogOverlay, and DialogEnhanced are BrutxUI components. DialogEnhanced must be used within the context provided by DialogRoot.

Accessibility ​

  • Keyboard: Press Escape to close the dialog
  • Focus Management: When the dialog opens, focus is trapped inside; when closed, focus is restored
  • ARIA Attributes: Close button includes screen reader text
  • Interactive Elements: Interactive elements (Input, Button, etc.) are automatically excluded during drag

Functional API ​

In addition to declarative components, Dialog also provides imperative calling and Composable methods for quickly creating custom modal containers in business logic. For structured confirmations, status icons, or input prompt dialogs, please refer to the standalone MessageBox component.

showDialog ​

Call showDialog to imperatively mount a generic Dialog container:

ts
import { showDialog } from 'brutx-ui-vue'

const instance = showDialog({
    title: 'System Settings',
    content: 'Custom modal body content...',
    size: 'default',
    draggable: true,
    showCloseButton: true,
    onCancel: () => {
        console.log('Dialog closed')
    },
})

// Close manually
// instance.close()

Common Parameters:

ParameterTypeDefaultDescription
titlestring—Dialog title
descriptionstring—Dialog description
contentstring | Component | VNode | (() => ...)—Custom content slot
footerstring | Component | VNode | (() => ...)—Custom footer slot
size'sm' | 'default' | 'lg' | 'xl' | 'full''default'Dialog size
draggablebooleanfalseEnable drag functionality
resizablebooleanfalseEnable resize functionality
fullscreenbooleanfalseFullscreen display
zIndexnumber—Custom overlay layer z-index
onCancel() => void—Close callback hook

Return value: OverlayInstanceHandle<void> — contains close(), destroy() methods and promise handle.

useDialog (Composable) ​

Use the useDialog composable within a component to gain reactive status and singleton instance management:

vue
<script setup>
import { useDialog } from 'brutx-ui-vue'

const { show, close, isOpen } = useDialog()

function showMyDialog() {
    show({
        title: 'Notice',
        content: 'This custom dialog was opened via a Composable',
    })
}
</script>

<template>
    <div class="flex items-center gap-3">
        <button @click="showMyDialog">Open Dialog</button>
        <span v-if="isOpen" class="text-sm font-bold text-brutal-accent">Dialog is open</span>
    </div>
</template>

TIP

The isOpen property returned by useDialog is a Readonly<Ref<boolean>> view, strictly managed by the composable internally.

useDialogGeometry (Spatial Geometry Controller) ​

When you need dragging, resizing, and boundary clamping for custom dialogs or overlays, use the low-level useDialogGeometry pure spatial geometry controller:

ts
import { useDialogGeometry } from 'brutx-ui-vue'

const {
    contentRef,
    position,
    size,
    contentStyle,
    isDragging,
    isResizing,
    onDragStart,
    onResizeStart,
} = useDialogGeometry({
    draggable: true,
    resizable: true,
    bounds: 'viewport',
    minWidth: 320,
    minHeight: 240,
})
  • Pure Spatial Geometry Responsibility: Exclusively manages position, size, dragging, resizing, aspect ratio, and boundary constraints, fully decoupled from business close control flows.
  • Readonly State Views: position and size are readonly Refs; programmatically update them via setPosition and setSize.

FAQ ​

Q: The page is still scrollable after opening the dialog. What should I do?

A: By default, the Dialog component has modal set to true, which locks background scrolling when open. If the background is still scrollable, check whether the overflow CSS property has been manually overridden, or if other global styles are interfering with the dialog overlay behavior.

Q: How to programmatically open and close the dialog?

A: Use controlled mode: bind the open prop of Dialog to a ref variable, then modify that variable to control the dialog state. You can also listen to the update:open event to respond to the dialog closing (e.g., pressing Escape or clicking the overlay).

Q: The drag area of DialogEnhanced is incorrect. How do I set the drag handle?

A: Use the drag-handle prop to specify the drag handle. It can be a CSS selector string (e.g., ".dialog-header") or a DOM element reference. Make sure the selector correctly matches an element inside the dialog; only pressing and holding that area will trigger dragging. If not set, the entire dialog content area is draggable by default.

Brute force builds.