Dialog
A neo-brutalist modal dialog built on top of reka-ui's Dialog primitive. Supports overlay, close button, and composable sub-components.
Demo
Installation
pnpm dlx brutx-vue@latest add dialogUsage
<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).
| Size | Max Width | Use Case |
|---|---|---|
sm | max-w-sm | Confirmation dialogs, simple prompts |
default | max-w-lg | Standard forms, general content |
lg | max-w-2xl | Multi-column forms, detailed settings |
xl | max-w-4xl | Complex content, data display |
full | max-w-[calc(100vw-2rem)] | Large content, full-screen interactions |
<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
| Component | Description |
|---|---|
Dialog | Root component (import directly from reka-ui: import { DialogRoot as Dialog } from 'reka-ui') |
DialogTrigger | Button that opens the dialog (import directly from reka-ui: import { DialogTrigger } from 'reka-ui') |
DialogContent | Dialog content panel with overlay |
DialogHeader | Header container |
DialogFooter | Footer container with flex layout |
DialogTitle | Dialog title |
DialogDescription | Dialog description text |
DialogClose | Close button (import directly from reka-ui: import { DialogClose } from 'reka-ui') |
DialogOverlay | Background overlay |
DialogPortal | Portal container (import directly from reka-ui: import { DialogPortal } from 'reka-ui') |
DialogEnhanced | Enhanced dialog with draggable and resizable support |
DialogEnhanced Usage
An enhanced dialog that supports dragging and resizing:
<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
| Name | Type | Default | Description |
|---|---|---|---|
showCloseButton | boolean | undefined | true | Controls whether the built-in close button is rendered in the top-right corner of the content panel. |
DetailsDeclared typeboolean | undefined Literal values
Default value details
Sourcepackages/ui/src/components/dialog/DialogContent.vue | |||
size | "default" | "full" | "lg" | "sm" | "xl" | undefined | "default" | Sets the width variant of the dialog content panel. |
DetailsDeclared typeNonNullable<"default" | "lg" | "sm" | "xl" | "full" | null | undefined> | undefined Literal values
Default value details
Sourcepackages/ui/src/components/dialog/DialogContent.vue | |||
entrance | "fade-zoom" | "shutter" | undefined | "fade-zoom" | Selects the entrance animation variant for the content panel. |
DetailsDeclared typeNonNullable<"fade-zoom" | "shutter" | null | undefined> | undefined Literal values
Default value details
Sourcepackages/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. |
DetailsDeclared typeboolean | undefined Literal values
Default value details
Sourcepackages/ui/src/components/dialog/DialogContent.vue | |||
class | string | undefined | undefined | Custom CSS classes added to the dialog content panel. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/dialog/DialogContent.vue | |||
| Name | Signature or type | Description |
|---|---|---|
#default | {} | Renders the dialog body; the built-in close button is placed after the slot content. |
DetailsDeclared type{}Sourcepackages/ui/src/components/dialog/DialogContent.vue | ||
DialogDescription
| Name | Type | Default | Description |
|---|---|---|---|
class | string | undefined | — | Custom CSS classes added to the description element; other attributes such as id fall through to the Reka UI primitive. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/dialog/DialogDescription.vue | |||
| Name | Signature or type | Description |
|---|---|---|
#default | {} | The supporting description content for the dialog. |
DetailsDeclared type{}Sourcepackages/ui/src/components/dialog/DialogDescription.vue | ||
DialogEnhanced
| Name | Type | Default | Description |
|---|---|---|---|
draggable | boolean | undefined | false | Enables pointer dragging of the content panel; dragging styles are not applied in fullscreen mode. |
DetailsDeclared typeboolean | undefined Literal values
Default value details
Sourcepackages/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. |
DetailsDeclared typestring | HTMLElement | undefined Default value details
Sourcepackages/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. |
DetailsDeclared type"viewport" | "parent" | { top: number; left: number; right: number; bottom: number; } | undefinedLiteral values
Default value details
Sourcepackages/ui/src/components/dialog/DialogEnhanced.vue | |||
initialPosition | { x: number; y: number; } | undefined | undefined | Provides the initial x and y coordinates to the geometry controller. |
DetailsDeclared type{ x: number; y: number; } | undefinedDefault value details
Sourcepackages/ui/src/components/dialog/DialogEnhanced.vue | |||
resizable | boolean | undefined | false | Enables pointer resizing from the dialog edges. |
DetailsDeclared typeboolean | undefined Literal values
Default value details
Sourcepackages/ui/src/components/dialog/DialogEnhanced.vue | |||
minWidth | number | undefined | 200 | Sets the minimum width allowed during resizing, in pixels. |
DetailsDeclared typenumber | undefined Default value details
Sourcepackages/ui/src/components/dialog/DialogEnhanced.vue | |||
minHeight | number | undefined | 150 | Sets the minimum height allowed during resizing, in pixels. |
DetailsDeclared typenumber | undefined Default value details
Sourcepackages/ui/src/components/dialog/DialogEnhanced.vue | |||
maxWidth | number | undefined | undefined | Sets an optional maximum width during resizing, in pixels. |
DetailsDeclared typenumber | undefined Default value details
Sourcepackages/ui/src/components/dialog/DialogEnhanced.vue | |||
maxHeight | number | undefined | undefined | Sets an optional maximum height during resizing, in pixels. |
DetailsDeclared typenumber | undefined Default value details
Sourcepackages/ui/src/components/dialog/DialogEnhanced.vue | |||
aspectRatio | number | undefined | undefined | Provides a locked width-to-height ratio for resizing through the geometry controller. |
DetailsDeclared typenumber | undefined Default value details
Sourcepackages/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. |
DetailsDeclared typeboolean | undefined Literal values
Default value details
Sourcepackages/ui/src/components/dialog/DialogEnhanced.vue | |||
forceMount | boolean | undefined | undefined | Keeps the overlay and content mounted while closed; this takes precedence over destroyOnClose. |
DetailsDeclared typeboolean | undefined Literal values
Default value details
Sourcepackages/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. |
DetailsDeclared typeboolean | undefined Literal values
Default value details
Sourcepackages/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. |
DetailsDeclared type(() => boolean | Promise<boolean>) | undefined Default value details
Sourcepackages/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. |
DetailsDeclared typeboolean | undefined Literal values
Default value details
Sourcepackages/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. |
DetailsDeclared typenumber | undefined Default value details
Sourcepackages/ui/src/components/dialog/DialogEnhanced.vue | |||
zIndex | number | undefined | undefined | Explicitly sets the CSS z-index of the dialog content panel. |
DetailsDeclared typenumber | undefined Default value details
Sourcepackages/ui/src/components/dialog/DialogEnhanced.vue | |||
class | string | undefined | undefined | Custom CSS classes added to the dialog content panel. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/dialog/DialogEnhanced.vue | |||
| Name | Signature or type | Description |
|---|---|---|
@open | [] | Emitted when the component mounts, indicating that the DialogEnhanced instance mounted rather than tracking each controlled open-state change. |
DetailsDeclared type[] Sourcepackages/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. |
DetailsDeclared type[] Sourcepackages/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. |
DetailsDeclared type[value: boolean] Literal values
Sourcepackages/ui/src/components/dialog/DialogEnhanced.vue | ||
| Name | Signature or type | Description |
|---|---|---|
#default | {} | The dialog content; it may be unloaded after the close delay when destroyOnClose is enabled. |
DetailsDeclared type{}Sourcepackages/ui/src/components/dialog/DialogEnhanced.vue | ||
DialogHeader
| Name | Type | Default | Description |
|---|---|---|---|
class | string | undefined | — | Custom CSS classes added to the title-area container. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/dialog/DialogHeader.vue | |||
| Name | Signature or type | Description |
|---|---|---|
#default | {} | The title, description, or other header content. |
DetailsDeclared type{}Sourcepackages/ui/src/components/dialog/DialogHeader.vue | ||
DialogOverlay
| Name | Type | Default | Description |
|---|---|---|---|
class | string | undefined | undefined | Custom CSS classes added to the backdrop primitive. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/dialog/DialogOverlay.vue | |||
forceMount | boolean | undefined | undefined | Passes the force-mount setting to the Reka UI overlay primitive. |
DetailsDeclared typeboolean | undefined Literal values
Default value details
Sourcepackages/ui/src/components/dialog/DialogOverlay.vue | |||
asChild | boolean | undefined | false | Delegates the overlay primitive element rendering to the child in the default slot. |
DetailsDeclared typeboolean | undefined Literal values
Default value details
Sourcepackages/ui/src/components/dialog/DialogOverlay.vue | |||
pattern | boolean | undefined | false | Adds a dotted halftone background pattern to the overlay. |
DetailsDeclared typeboolean | undefined Literal values
Default value details
Sourcepackages/ui/src/components/dialog/DialogOverlay.vue | |||
| Name | Signature or type | Description |
|---|---|---|
#default | {} | Provides the overlay child element when using asChild. |
DetailsDeclared type{}Sourcepackages/ui/src/components/dialog/DialogOverlay.vue | ||
DialogTitle
| Name | Type | Default | Description |
|---|---|---|---|
class | string | undefined | — | Custom CSS classes added to the dialog title primitive. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/dialog/DialogTitle.vue | |||
| Name | Signature or type | Description |
|---|---|---|
#default | {} | The dialog title text or content. |
DetailsDeclared type{}Sourcepackages/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
Escapeto 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:
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:
| Parameter | Type | Default | Description |
|---|---|---|---|
title | string | — | Dialog title |
description | string | — | Dialog description |
content | string | Component | VNode | (() => ...) | — | Custom content slot |
footer | string | Component | VNode | (() => ...) | — | Custom footer slot |
size | 'sm' | 'default' | 'lg' | 'xl' | 'full' | 'default' | Dialog size |
draggable | boolean | false | Enable drag functionality |
resizable | boolean | false | Enable resize functionality |
fullscreen | boolean | false | Fullscreen display |
zIndex | number | — | 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:
<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:
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:
positionandsizeare readonly Refs; programmatically update them viasetPositionandsetSize.
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.