Dialog
A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
Examples
Basic
Prop | Default | Type | Description |
---|---|---|---|
title | - | string | The title of the dialog. |
description | - | string | The description of the dialog. |
showClose | true | boolean | Show the close button. |
defaultOpen | false | boolean | The open state of the dialog when it is initially rendered. Use when you do not need to control its open state. |
modal | true | boolean | The modality of the dialog When set to true, interaction with outside elements will be disabled and only dialog content will be visible to screen readers. |
open | - | boolean | The controlled open state of the dialog. Can be binded as v-model:open . |
Read more in Radix Dialog Root API
Preview
Code
Scrollable Content
Prop | Default | Type | Description |
---|---|---|---|
scrollable | false | boolean | If true, the dialog will have a scrollable body. |
Preview
Code
Prevent Closing
Prop | Default | Type | Description |
---|---|---|---|
preventClose | - | boolean | If true, the dialog will not close on overlay click or escape key press. |
Preview
Code
Slots
Name | Props | Description |
---|---|---|
default | - | The trigger slot. |
content | - | The content slot. |
trigger | open | The trigger button used to open the dialog. |
header | - | Contains the title and description slots. |
footer | - | The footer. |
title | - | The title displayed in the dialog. |
description | - | The description displayed below the title. |
Custom Close Button
Preview
Code
Scrollable Body
Preview
Code
Login Prompt
A login dialog with state which closes itself after a successful login.
Preview
Code
Blurred Background
A dialog whose overlay blurs the background content.
Preview
Code
Presets
shortcuts/dialog.ts
type KbdPrefix = 'dialog'
export const staticDialog: Record<`${KbdPrefix}-${string}` | KbdPrefix, string> = {
// base
'dialog': '',
// sub-components
'dialog-overlay': 'fixed inset-0 z-50 bg-black/80',
'dialog-content': 'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border border-base bg-base p-6 shadow-lg duration-200 sm:rounded-lg',
'dialog-scroll-overlay': 'fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/80',
'dialog-scroll-content': 'relative z-50 grid w-full max-w-lg my-8 gap-4 border border-base bg-base p-6 shadow-lg duration-200 sm:rounded-lg md:w-full',
'dialog-header': 'flex flex-col gap-y-1.5 text-center sm:text-left',
'dialog-title': 'text-lg font-semibold leading-none tracking-tight',
'dialog-description': 'text-sm text-muted',
'dialog-close': 'absolute right-4 top-4',
'dialog-footer': 'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2',
}
export const dynamicDialog: [RegExp, (params: RegExpExecArray) => string][] = [
// dynamic preset
]
export const dialog = [
...dynamicDialog,
staticDialog,
]
Props
types/dialog.ts
import type {
DialogCloseProps,
DialogContentProps,
DialogDescriptionProps,
DialogRootProps,
DialogTitleProps,
} from 'radix-vue'
import type { HTMLAttributes } from 'vue'
import type { NButtonProps } from './button'
export interface NDialogProps extends DialogRootProps {
title?: string
description?: string
scrollable?: boolean
showClose?: boolean
preventClose?: boolean
// sub-components
_dialogTitle?: NDialogTitleProps
_dialogDescription?: NDialogDescriptionProps
_dialogHeader?: NDialogHeaderProps
_dialogFooter?: NDialogFooterProps
_dialogOverlay?: NDialogOverlayProps
_dialogContent?: NDialogContentProps
_dialogClose?: NDialogCloseProps
una?: NDialogUnaProps
}
interface BaseExtensions {
class?: HTMLAttributes['class']
}
export interface NDialogTitleProps extends DialogTitleProps, BaseExtensions {
una?: NDialogUnaProps['dialogTitle']
}
export interface NDialogDescriptionProps extends DialogDescriptionProps, BaseExtensions {
una?: NDialogUnaProps['dialogDescription']
}
export interface NDialogContentProps extends DialogContentProps, BaseExtensions, Pick<NDialogProps, '_dialogOverlay' | '_dialogClose' | 'preventClose' | 'showClose'> {
onCloseAutoFocus?: (event: any) => void
onEscapeKeyDown?: (event: KeyboardEvent) => void
onInteractOutside?: (event: any) => void
onOpenAutoFocus?: (event: any) => void
onPointerDownOutside?: (event: any) => void
una?: NDialogUnaProps['dialogContent']
}
export interface NDialogOverlayProps extends BaseExtensions, Pick<NDialogProps, 'scrollable'> {
una?: NDialogUnaProps['dialogOverlay']
}
export interface NDialogHeaderProps extends BaseExtensions {
una?: NDialogUnaProps['dialogHeader']
}
export interface NDialogFooterProps extends BaseExtensions {
una?: NDialogUnaProps['dialogFooter']
}
export interface NDialogCloseProps extends DialogCloseProps, NButtonProps {
}
export interface NDialogUnaProps {
dialogTitle?: HTMLAttributes['class']
dialogDescription?: HTMLAttributes['class']
dialogHeader?: HTMLAttributes['class']
dialogFooter?: HTMLAttributes['class']
dialogOverlay?: HTMLAttributes['class']
dialogContent?: HTMLAttributes['class']
}
Components
Dialog.vue
DialogTitle.vue
DialogDescription.vue
DialogHeader.vue
DialogFooter.vue
DialogClose.vue
DialogOverlay.vue
DialogContent.vue
DialogScrollContent.vue
<script setup lang="ts">
import type { NDialogProps } from '../../../types'
import { reactivePick } from '@vueuse/core'
import { DialogRoot, type DialogRootEmits, DialogTrigger, useForwardPropsEmits } from 'radix-vue'
import DialogContent from './DialogContent.vue'
import DialogDescription from './DialogDescription.vue'
import DialogFooter from './DialogFooter.vue'
import DialogHeader from './DialogHeader.vue'
import DialogScrollContent from './DialogScrollContent.vue'
import DialogTitle from './DialogTitle.vue'
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(defineProps<NDialogProps>(), {
showClose: true,
})
const emits = defineEmits<DialogRootEmits>()
const rootProps = reactivePick(props, [
'open',
'defaultOpen',
'modal',
])
const rootPropsEmits = useForwardPropsEmits(rootProps, emits)
</script>
<template>
<DialogRoot v-slot="{ open }" v-bind="rootPropsEmits">
<DialogTrigger as-child>
<slot name="trigger" :open />
</DialogTrigger>
<component
:is="!scrollable ? DialogContent : DialogScrollContent"
v-bind="_dialogContent"
:_dialog-overlay
:_dialog-close
:una
:scrollable
:show-close
:prevent-close
:aria-describedby="props.description ? 'dialog-description' : undefined"
>
<slot name="content">
<DialogHeader
v-if="props.title || props.description || $slots.header"
v-bind="_dialogHeader"
:una
>
<slot name="header">
<DialogTitle
v-if="props.title"
v-bind="_dialogTitle"
:una
>
<slot name="title">
{{ title }}
</slot>
</DialogTitle>
<DialogDescription
v-if="props.description"
v-bind="_dialogDescription"
:una
>
<slot name="description">
{{ description }}
</slot>
</DialogDescription>
</slot>
</DialogHeader>
<!-- body -->
<slot />
<DialogFooter
v-if="$slots.footer"
v-bind="_dialogFooter"
:una
>
<slot name="footer" />
</DialogFooter>
</slot>
</component>
</DialogRoot>
</template>