Dialog
A window overlaid on either the primary window or another dialog window.
import Dialog from 'corvu/dialog'
import type { VoidComponent } from 'solid-js'
const DialogExample: VoidComponent = () => {
return (
<Dialog.Root>
<Dialog.Trigger class="my-auto rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium text-corvu-dark transition-all duration-100 hover:bg-corvu-200 active:translate-y-0.5">
Open Dialog
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay class="fixed inset-0 z-50 bg-corvu-1000/60" />
<Dialog.Content class="fixed left-1/2 top-1/2 z-50 min-w-[320px] -translate-x-1/2 -translate-y-1/2 rounded-lg border-2 border-corvu-400 bg-corvu-1000 px-6 py-5 duration-200 corvu-open:animate-in corvu-open:fade-in-0 corvu-open:zoom-in-95 corvu-open:slide-in-from-left-1/2 corvu-open:slide-in-from-top-[60%] corvu-closed:animate-out corvu-closed:fade-out-0 corvu-closed:zoom-out-95 corvu-closed:slide-out-to-left-1/2 corvu-closed:slide-out-to-top-[60%]">
<Dialog.Label class="text-lg font-bold">
Survey about SolidJS
</Dialog.Label>
<Dialog.Description class="mt-2">
Tell us what you like about Solid the most!
</Dialog.Description>
<textarea class="mt-3 w-full rounded border-2 border-corvu-500 bg-corvu-1000 focus:outline-none" />
<div class="mt-3 flex justify-between">
<Dialog.Close class="rounded-md bg-corvu-200 px-3 py-2 text-corvu-dark">
Cancel
</Dialog.Close>
<Dialog.Close class="rounded-md bg-corvu-400 px-3 py-2 font-bold text-corvu-dark">
Submit
</Dialog.Close>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
)
}
export default DialogExample
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
50: '#f2f0fe',
100: '#e6e2fd',
200: '#d4cbfb',
light: '#D4C0FF',
300: '#bcacf6',
400: '#a888f1',
500: '#9a6de9',
600: '#8f50dc',
700: '#7e41c3',
accent: '#7250AE',
800: '#63359c',
900: '#52317d',
dark: '#180f23',
1000: '#0C0812',
},
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('tailwindcss-animate'),
require('@corvu/tailwind'),
],
}
import Dialog from 'corvu/dialog'
import type { VoidComponent } from 'solid-js'
import './index.css'
const DialogExample: VoidComponent = () => {
return (
<Dialog.Root>
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay />
<Dialog.Content>
<Dialog.Label>Survey about SolidJS</Dialog.Label>
<Dialog.Description>
Tell us what you like about Solid the most!
</Dialog.Description>
<textarea />
<div class="button_wrapper">
<Dialog.Close class="cancel_button">Cancel</Dialog.Close>
<Dialog.Close class="submit_button">Submit</Dialog.Close>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
)
}
export default DialogExample
[data-corvu-dialog-trigger] {
border-radius: 0.5rem;
background-color: rgb(230 226 253);
padding-left: 1rem;
padding-right: 1rem;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
margin-top: auto;
margin-bottom: auto;
font-size: 1.125rem;
line-height: 1.75rem;
font-weight: 500;
color: rgb(24 15 35);
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
transition-duration: 100ms;
animation-duration: 100ms;
}
[data-corvu-dialog-trigger]:hover {
background-color: rgb(212 203 251);
}
[data-corvu-dialog-trigger]:active {
transform: translate(0px, 0.125rem);
}
[data-corvu-dialog-overlay] {
position: fixed;
inset: 0px;
z-index: 50;
background-color: rgb(12 8 18 / 0.6);
}
[data-corvu-dialog-content] {
position: fixed;
left: 50%;
top: 50%;
z-index: 50;
min-width: 320px;
transform: translate(-50%, -50%);
border-radius: 0.5rem;
border-width: 2px;
border-color: rgb(168 136 241);
background-color: rgb(12 8 18);
padding-left: 1.5rem;
padding-right: 1.5rem;
padding-top: 1.25rem;
padding-bottom: 1.25rem;
animation-duration: 200ms;
}
[data-corvu-dialog-content][data-open] {
animation-name: enter;
animation-duration: 150ms;
--tw-enter-opacity: initial;
--tw-enter-scale: initial;
--tw-enter-rotate: initial;
--tw-enter-translate-x: initial;
;
--tw-enter-translate-y: initial --tw-enter-opacity: 0;
--tw-enter-scale: .95;
--tw-enter-translate-x: -50%;
--tw-enter-translate-y: -60%;
}
[data-corvu-dialog-content][data-closed] {
animation-name: exit;
animation-duration: 150ms;
--tw-exit-opacity: initial;
--tw-exit-scale: initial;
--tw-exit-rotate: initial;
--tw-exit-translate-x: initial;
--tw-exit-translate-y: initial;
--tw-exit-opacity: 0;
--tw-exit-scale: .95;
--tw-exit-translate-x: -50%;
--tw-exit-translate-y: -60%;
}
@keyframes enter {
from {
opacity: var(--tw-enter-opacity, 1);
transform: translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))
}
}
@keyframes exit {
to {
opacity: var(--tw-exit-opacity, 1);
transform: translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))
}
}
[data-corvu-dialog-label] {
font-size: 1.125rem;
line-height: 1.75rem;
font-weight: 700;
}
[data-corvu-dialog-description] {
margin-top: 0.5rem;
}
textarea {
margin-top: 0.75rem;
width: 100%;
border-radius: 0.25rem;
border-width: 2px;
border-color: rgb(154 109 233);
background-color: rgb(24 15 35);
}
textarea:focus {
outline: 2px solid transparent;
outline-offset: 2px;
}
.button_wrapper {
margin-top: 0.75rem;
display: flex;
justify-content: space-between;
}
[data-corvu-dialog-close] {
border-radius: 0.375rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: rgb(24 15 35)
}
.cancel_button {
background-color: rgb(212 203 251);
}
.submit_button {
background-color: rgb(168 136 241);
}
Features
- Supports modal and non-modal modes
- Customizable focus management
- Waits for pending animations before removing the dialog from the DOM
- Pressing the escape key and interacting outside closes the dialog
- Supports nested dialogs
Usage
import Dialog from 'corvu/dialog'
// Or
// import { Root, Trigger, ... } from 'corvu/dialog'
Anatomy
<Dialog.Root>
<Dialog.Trigger />
<Dialog.Portal>
<Dialog.Overlay />
<Dialog.Content>
<Dialog.Close />
<Dialog.Label />
<Dialog.Description />
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
Scrolling
By default, corvu prevents scrolling of elements outside of the dialog content while the dialog open. It will also add additional padding to the body to prevent the page from jumping when the scrollbar disappears. There is also a CSS variable called --scrollbar-width
which is present when the scrollbar is hidden. You can use this to prevent any fixed element from jumping:
<header
class="fixed top-0 inset-x-0 z-50"
style={{
'padding-right': 'var(--scrollbar-width, 0)',
}}
>
Header
</header>
Nested dialogs
Feel free to nest dialogs however you want. Corvu is aware of nested dialogs and only dismisses the top-most dialog when you press the escape key or click outside the dialog.
import Dialog from 'corvu/dialog'
import type { VoidComponent } from 'solid-js'
const DialogExample: VoidComponent = () => {
return (
<Dialog.Root>
<Dialog.Trigger class="my-auto rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium text-corvu-dark transition-all duration-100 hover:bg-corvu-200 active:translate-y-0.5">
Open Dialog
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay class="fixed inset-0 z-50 bg-corvu-1000/60" />
<Dialog.Content class="fixed left-1/2 top-1/2 z-50 min-w-[320px] -translate-x-1/2 -translate-y-1/2 rounded-lg border-2 border-corvu-400 bg-corvu-1000 px-6 py-5 duration-200 corvu-open:animate-in corvu-open:fade-in-0 corvu-open:zoom-in-95 corvu-open:slide-in-from-left-1/2 corvu-open:slide-in-from-top-[60%] corvu-closed:animate-out corvu-closed:fade-out-0 corvu-closed:zoom-out-95 corvu-closed:slide-out-to-left-1/2 corvu-closed:slide-out-to-top-[60%]">
<Dialog.Label class="text-lg font-bold">
Nested dialog example
</Dialog.Label>
<div class="mt-3 flex justify-between">
<Dialog.Close class="rounded-md bg-corvu-200 px-3 py-2 text-corvu-dark">
Close
</Dialog.Close>
<Dialog.Root>
<Dialog.Trigger class="rounded-md bg-corvu-400 px-3 py-2 font-bold text-corvu-dark">
Open another dialog!
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Content class="fixed left-1/2 top-1/2 z-50 flex h-[150px] w-[320px] -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center space-y-4 rounded-lg border-2 border-corvu-400 bg-corvu-1000 px-6 py-5 duration-200 corvu-open:animate-in corvu-open:fade-in-0 corvu-open:zoom-in-95 corvu-open:slide-in-from-left-1/2 corvu-open:slide-in-from-top-[60%] corvu-closed:animate-out corvu-closed:fade-out-0 corvu-closed:zoom-out-95 corvu-closed:slide-out-to-left-1/2 corvu-closed:slide-out-to-top-[60%]">
<Dialog.Label class="text-lg font-bold">
Hey! I'm a nested dialog 🐦⬛
</Dialog.Label>
<Dialog.Close class="rounded-md bg-corvu-200 px-3 py-2 text-corvu-dark">
Close me
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
)
}
export default DialogExample
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
50: '#f2f0fe',
100: '#e6e2fd',
200: '#d4cbfb',
light: '#D4C0FF',
300: '#bcacf6',
400: '#a888f1',
500: '#9a6de9',
600: '#8f50dc',
700: '#7e41c3',
accent: '#7250AE',
800: '#63359c',
900: '#52317d',
dark: '#180f23',
1000: '#0C0812',
},
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('tailwindcss-animate'),
require('@corvu/tailwind'),
],
}
Accessibility
Adheres to the Dialog WAI-ARIA design pattern and Alert Dialog WAI-ARIA design pattern.
API reference
Dialog.Root
Component
Context wrapper for the dialog. Is required for every dialog you create.
Props
role
'dialog' | 'alertdialog'
The role
attribute of the dialog element. *Default = 'dialog'
*
open
boolean
Whether the dialog is open or not.
onOpenChange
(open: boolean) => void
Callback fired when the open state changes.
initialOpen
boolean
Whether the dialog is open initially or not. *Default = false
*
modal
boolean
Whether the dialog should be rendered as a modal or not. *Default = true
*
closeOnEscapeKeyDown
boolean
Whether the dialog should close when the user presses the Escape
key. *Default = true
*
onEscapeKeyDown
(event: KeyboardEvent) => void
Callback fired when the user presses the Escape
key. Can be prevented by calling event.preventDefault
.
closeOnOutsidePointerDown
boolean
Whether the dialog should be closed if the user interacts outside the bounds of <Dialog.Content />
. *Default = true
if modal
is true
, false
otherwise*
onOutsidePointerDown
(event: MouseEvent) => void
Callback fired when the user interacts outside the bounds of <Dialog.Content />
. Can be prevented by calling event.preventDefault
.
noOutsidePointerEvents
boolean
Whether pointer events outside of <Dialog.Content />
should be disabled. *Default = true
if modal
is true
, false
otherwise*
preventScroll
boolean
Whether scroll outside of the dialog should be prevented. *Default = true
if modal
is true
, false
otherwise*
hideScrollbar
boolean
Whether the scrollbar of the <body>
element should be hidden. *Default = true
if modal
is true
, false
otherwise*
preventScrollbarShift
boolean
Whether padding should be added to the <body>
element to avoid layout shift. *Default = true
*
preventScrollbarShiftMode
'padding' | 'margin'
Whether padding or margin should be used to avoid layout shift. *Default = 'padding'
*
trapFocus
boolean
Whether the dialog should trap focus or not. *Default = true
*
restoreFocus
boolean
Whether the dialog should restore focus to the previous active element when it closes. *Default = true
*
initialFocusEl
HTMLElement
The element to receive focus when the dialog opens.
onInitialFocus
(event: Event) => void
Callback fired when focus moves into the dialog. Can be prevented by calling event.preventDefault
.
finalFocusEl
HTMLElement
The element to receive focus when the dialog closes.
onFinalFocus
(event: Event) => void
Callback fired when focus moves out of the dialog. Can be prevented by calling event.preventDefault
.
dialogId
string
The id
attribute of the dialog element. *Default = A unique id.*
labelId
string
The id
attribute of the dialog label element. *Default = A unique id.*
descriptionId
string
The id
attribute of the dialog description element. *Default = A unique id.*
contextId
string
The id
of the dialog context. Useful if you have nested dialogs and want to create components that belong to a dialog higher up in the tree.
children
JSX.Element | (props: DialogRootChildrenProps) => JSX.Element
Dialog.Trigger
Component
Button that changes the open state of the dialog when clicked.
Props
as
ValidComponent
Default: button
Component to render the polymorphic component as. *Default = div
*
asChild
boolean
Whether to render the polymorphic component as the first <As />
component found in its children. *Default = false
*
contextId
string
The id
of the dialog context to use.
Data
Data attributes present on Dialog.Trigger components.
data-corvu-dialog-trigger
Present on every dialog trigger element.
data-open
Present when the dialog is open.
data-closed
Present when the dialog is closed.
CSS props
CSS properties attributes present on Dialog.Trigger components.
Dialog.Portal
Component
Portals its children at the end of the body element to ensure that the dialog always rendered on top.
Props
forceMount
boolean
Whether the dialog portal should be forced to render. Useful when using third-party animation libraries.
contextId
string
The id
of the dialog context to use.
Dialog.Overlay
Component
Component which can be used to create a faded background. Can be animated.
Props
as
ValidComponent
Default: div
Component to render the polymorphic component as. *Default = div
*
asChild
boolean
Whether to render the polymorphic component as the first <As />
component found in its children. *Default = false
*
forceMount
boolean
Whether the dialog overlay should be forced to render. Useful when using third-party animation libraries.
contextId
string
The id
of the dialog context to use.
Data
Data attributes present on Dialog.Overlay components.
data-corvu-dialog-overlay
Present on every dialog overlay element.
data-open
Present when the dialog is open.
data-closed
Present when the dialog is closed.
CSS props
CSS properties attributes present on Dialog.Overlay components.
Dialog.Content
Component
Content of the dialog. Can be animated.
Props
as
ValidComponent
Default: div
Component to render the polymorphic component as. *Default = div
*
asChild
boolean
Whether to render the polymorphic component as the first <As />
component found in its children. *Default = false
*
forceMount
boolean
Whether the dialog content should be forced to render. Useful when using third-party animation libraries.
contextId
string
The id
of the dialog context to use.
Data
Data attributes present on Dialog.Content components.
data-corvu-dialog-content
Present on every dialog content element.
data-open
Present when the dialog is open.
data-closed
Present when the dialog is closed.
CSS props
CSS properties attributes present on Dialog.Content components.
Dialog.Close
Component
Close button that changes the open state to false when clicked.
Props
as
ValidComponent
Default: button
Component to render the polymorphic component as. *Default = div
*
asChild
boolean
Whether to render the polymorphic component as the first <As />
component found in its children. *Default = false
*
contextId
string
The id
of the dialog context to use.
Data
Data attributes present on Dialog.Close components.
data-corvu-dialog-close
Present on every dialog close element.
CSS props
CSS properties attributes present on Dialog.Close components.
Dialog.Label
Component
Label element to announce the dialog to accessibility tools.
Props
as
ValidComponent
Default: h2
Component to render the polymorphic component as. *Default = div
*
asChild
boolean
Whether to render the polymorphic component as the first <As />
component found in its children. *Default = false
*
contextId
string
The id
of the dialog context to use.
Data
Data attributes present on Dialog.Label components.
data-corvu-dialog-label
Present on every dialog label element.
CSS props
CSS properties attributes present on Dialog.Label components.
Dialog.Description
Component
Description element to announce the dialog to accessibility tools.
Props
as
ValidComponent
Default: p
Component to render the polymorphic component as. *Default = div
*
asChild
boolean
Whether to render the polymorphic component as the first <As />
component found in its children. *Default = false
*
contextId
string
The id
of the dialog context to use.
Data
Data attributes present on Dialog.Description components.
data-corvu-dialog-description
Present on every dialog description element.
CSS props
CSS properties attributes present on Dialog.Description components.
Dialog.useContext
Context
Context which exposes various properties to interact with the dialog. Optionally provide a contextId to access a keyed context.
Returns
role
Accessor<'dialog' | 'alertdialog'>
The role
attribute of the dialog element.
open
Accessor<boolean>
Whether the dialog is open or not.
setOpen
Setter<boolean>
Change the open state of the dialog.
modal
Accessor<boolean>
Whether the dialog should be rendered as a modal or not.
closeOnEscapeKeyDown
Accessor<boolean>
Whether the dialog should close when the user presses the Escape
key.
closeOnOutsidePointerDown
Accessor<boolean>
Whether the dialog should be closed if the user interacts outside the bounds of <Dialog.Content />
.
noOutsidePointerEvents
Accessor<boolean>
Whether pointer events outside of <Dialog.Content />
should be disabled.
preventScroll
Accessor<boolean>
Whether scroll outside of the dialog should be prevented.
hideScrollbar
Accessor<boolean>
Whether the scrollbar of the <body>
element should be hidden.
preventScrollbarShift
Accessor<boolean>
Whether padding should be added to the <body>
element to avoid layout shift.
preventScrollbarShiftMode
Accessor<'padding' | 'margin'>
Whether padding or margin should be used to avoid layout shift.
trapFocus
Accessor<boolean>
Whether the dialog should trap focus or not.
restoreFocus
Accessor<boolean>
Whether the dialog should restore focus to the previous active element when it closes.
initialFocusEl
Accessor<HTMLElement | undefined>
The element to receive focus when the dialog opens.
finalFocusEl
Accessor<HTMLElement | undefined>
The element to receive focus when the dialog closes.
contentPresent
Accessor<boolean>
Whether the dialog content is present. This differes from open
as it tracks pending animations.
contentRef
Accessor<HTMLElement | null>
The ref of the dialog content.
overlayPresent
Accessor<boolean>
Whether the dialog overlay is present. This differes from open
as it tracks pending animations.
overlayRef
Accessor<HTMLElement | null>
The ref of the dialog overlay.
dialogId
Accessor<string>
The id
attribute of the dialog element.
labelId
Accessor<string | undefined>
The id
attribute of the dialog label element. Is undefined if no Dialog.Label
is present.
descriptionId
Accessor<string | undefined>
The id
attribute of the dialog description element. Is undefined if no Dialog.Description
is present.
Dialog.RootChildrenProps
Type
Props which are passed to the Root component children function.
Props
role
'dialog' | 'alertdialog'
The role
attribute of the dialog element.
open
boolean
Whether the dialog is open or not.
setOpen
Setter<boolean>
Change the open state of the dialog.
modal
boolean
Whether the dialog should be rendered as a modal or not.
closeOnEscapeKeyDown
boolean
Whether the dialog should close when the user presses the Escape
key.
closeOnOutsidePointerDown
boolean
Whether the dialog should be closed if the user interacts outside the bounds of the dialog content.
noOutsidePointerEvents
boolean
Whether pointer events outside of <Dialog.Content />
should be disabled.
preventScroll
boolean
Whether scroll outside of the dialog should be prevented.
hideScrollbar
boolean
Whether the scrollbar of the <body>
element should be hidden.
preventScrollbarShift
boolean
Whether padding should be added to the <body>
element to avoid layout shift.
preventScrollbarShiftMode
'padding' | 'margin'
Whether padding or margin should be used to avoid layout shift.
trapFocus
boolean
Whether the dialog should trap focus or not.
restoreFocus
boolean
Whether the dialog should restore focus to the previous active element when it closes.
initialFocusEl
HTMLElement
The element to receive focus when the dialog opens.
finalFocusEl
HTMLElement
The element to receive focus when the dialog closes.
contentPresent
boolean
Whether the dialog content is present. This differes from open
as it tracks pending animations.
contentRef
HTMLElement | null
The ref of the dialog content.
overlayPresent
boolean
Whether the dialog overlay is present. This differes from open
as it tracks pending animations.
overlayRef
HTMLElement | null
The ref of the dialog overlay.
dialogId
string
The id
attribute of the dialog description element.
labelId
string | undefined
The id
attribute of the dialog label element. Is undefined if no Dialog.Label
is present.
descriptionId
string | undefined
The id
attribute of the dialog description element. Is undefined if no Dialog.Description
is present.
corvu@0.2.3
Developed and designed by Jasmin