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>

Props

Dialog (Root Component)

Re-exported from reka-ui's DialogRoot. Manages the open/close state of the dialog.

PropTypeDefaultDescription
openbooleanControlled open state
defaultOpenbooleanfalseDefault open state in uncontrolled mode
modalbooleantrueWhether the dialog is modal

DialogTrigger

Trigger button re-exported from reka-ui.

PropTypeDefaultDescription
asChildbooleanWhether to delegate rendering to child element
asstring'button'HTML element to render

DialogContent

PropTypeDefaultDescription
showCloseButtonbooleantrueWhether to show the close button
size'sm' | 'default' | 'lg' | 'xl' | 'full''default'Dialog size
forceMountbooleanForce mount (for animation control)
classstringCustom CSS class

DialogClose

Close button re-exported from reka-ui.

PropTypeDefaultDescription
asChildbooleanWhether to delegate rendering to child element
asstring'button'HTML element to render

DialogEnhanced

PropTypeDefaultDescription
draggablebooleanfalseWhether the dialog is draggable
dragHandlestring | HTMLElementDrag handle (CSS selector or element)
bounds'parent' | 'viewport' | { top, left, right, bottom }'viewport'Drag boundaries
initialPosition{ x: number; y: number }Initial position
resizablebooleanfalseWhether the dialog is resizable
minWidthnumber200Minimum width
minHeightnumber150Minimum height
maxWidthnumberMaximum width
maxHeightnumberMaximum height
aspectRationumberLock aspect ratio
showCloseButtonbooleantrueWhether to show the close button
forceMountbooleanForce mount
fullscreenbooleanfalseFullscreen mode (occupies entire viewport)
beforeClose((done) => void) | (() => boolean | Promise<boolean>)Close hook (supports callback and Promise mode)
destroyOnClosebooleanfalseDestroy content after closing
zIndexnumberCustom z-index
classstringCustom CSS class

DialogHeader / DialogFooter / DialogTitle / DialogDescription / DialogOverlay

PropTypeDefaultDescription
classstringCustom CSS class

Events

DialogRoot

EventPayloadDescription
update:open(value: boolean)Emitted when the dialog open state changes

DialogEnhanced Events

EventPayloadDescription
update:open(value: boolean)Emitted when the dialog open state changes
openEmitted when the dialog starts opening
openedEmitted when the dialog open animation completes
closeEmitted when the dialog starts closing
closedEmitted when the dialog close animation completes

Slots

Dialog

SlotScopeDescription
default{ open: boolean, close: () => void }Default slot, provides current open state and close method

DialogContent / DialogHeader / DialogFooter / DialogTitle / DialogDescription / DialogOverlay / DialogEnhanced Slots

SlotScopeDescription
defaultDefault slot

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 the declarative component approach, Dialog also provides functional invocation methods for quickly creating dialogs in business logic.

showDialog

Call showDialog to programmatically create and open a dialog:

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

const instance = showDialog({
    title: 'Confirm Action',
    content: 'Are you sure you want to delete this record?',
    size: 'sm',
    onConfirm: () => {
        // Handle confirmation logic
    },
    onCancel: () => {
        // Handle cancellation logic
    },
})

Parameters:

ParameterTypeDefaultDescription
titlestringDialog title
contentstringDialog content
size'sm' | 'default' | 'lg' | 'xl' | 'full''default'Dialog size
onConfirm() => voidConfirmation callback
onCancel() => voidCancellation callback

Return value: Returns a dialog instance with a close() method for manual closing.

showMessageBox

Call showMessageBox to display a confirmation message box that returns a Promise:

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

async function handleDelete() {
    const confirmed = await showMessageBox({
        title: 'Warning',
        content: 'This action cannot be undone. Are you sure you want to continue?',
        confirmText: 'Confirm',
        cancelText: 'Cancel',
    })

    if (confirmed) {
        // User clicked confirm
    }
}

Parameters:

ParameterTypeDefaultDescription
titlestringMessage box title
contentstringMessage box content
confirmTextstring'Confirm'Confirm button text
cancelTextstring'Cancel'Cancel button text
size'sm' | 'default' | 'lg' | 'xl' | 'full''sm'Message box size

Return value: Promise<boolean> -- returns true if the user clicks confirm, false if the user clicks cancel.

useDialog (Composable)

Use the useDialog composable within a component to gain reactive dialog control:

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

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

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

<template>
    <button @click="showMyDialog">Open Dialog</button>
</template>

useMessageBox (Composable)

Use the useMessageBox composable within a component to gain reactive message box control:

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

const { confirm } = useMessageBox()

async function handleAction() {
    const result = await confirm({
        title: 'Confirm',
        content: 'Are you sure you want to perform this action?',
    })
    if (result) {
        // User confirmed
    }
}
</script>

<template>
    <button @click="handleAction">Perform Action</button>
</template>

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.