Dialog is an overlay shown above other content in an application.
createDialog
- Provides the behavior and accessibility implementation for a dialog component.
npm install @solid-aria/dialog
# or
yarn add @solid-aria/dialog
# or
pnpm add @solid-aria/dialog
Provides the behavior and accessibility implementation for a dialog component.
The HTML <dialog>
element can be used to build dialogs. However, it is not yet widely supported across browsers, and building fully accessible custom dialogs from scratch is very difficult and error prone. createDialog
, combined with createModal
, helps achieve accessible dialogs that can be styled as needed.
- Exposed to assistive technology as a dialog or alertdialog with ARIA
- Handles labelling the dialog by its title element
- Handles focusing the dialog on mount, unless a child element is already focused
- Contains focus inside the dialog when combined with
<FocusScope>
- Hides content behind the dialog from screen readers when combined with
createModal
- Prevents scrolling the page behind the dialog when combined with
createPreventScroll
- Handles closing the dialog when interacting outside and pressing the
Escape
key, when combined withcreateOverlay
This example shows how to build a typical modal dialog. It has a partially transparent backdrop above the rest of the page, prevents scrolling the body using createPreventScroll
, and hides content outside the dialog with createModal
.
The modal can be closed by clicking or interacting outside the dialog if the isDismissable
prop is set to true, or by pressing the Escape
key. This is handled by createOverlay
.
Focus is contained within the dialog while it is open using a <FocusScope>
. In addition, the first focusable element is automatically focused when the dialog opens, and focus is restored back to the button that opened it when it closes.
The application is contained in an OverlayProvider
, which is used to hide the content from screen readers with aria-hidden
while a modal is open. In addition, each modal must be contained in an OverlayContainer
, which uses a SolidJS Portal to render the modal at the end of the document body. If a nested modal is opened, then the first modal will also be set to aria-hidden
, so that only the top-most modal is accessible.
import { createButton } from "@solid-aria/button";
import { AriaDialogProps, createDialog } from "@solid-aria/dialog";
import { FocusScope } from "@solid-aria/focus";
import {
AriaModalProps,
AriaOverlayProps,
createModal,
createOverlay,
createOverlayTriggerState,
createPreventScroll,
OverlayContainer,
OverlayProvider
} from "@solid-aria/overlays";
import { JSX, Show } from "solid-js";
interface ModalDialogProps extends AriaDialogProps, AriaModalProps, AriaOverlayProps {
title?: JSX.Element;
children?: JSX.Element;
}
function ModalDialog(props: ModalDialogProps) {
// Handle interacting outside the dialog and pressing
// the Escape key to close the modal.
let ref: HTMLDivElement | undefined;
const { overlayProps, underlayProps } = createOverlay(props, () => ref);
// Prevent scrolling while the modal is open, and hide content
// outside the modal from screen readers.
createPreventScroll();
const { modalProps } = createModal();
// Get props for the dialog and its title
const { dialogProps, titleProps } = createDialog(props, () => ref);
return (
<div
style={{
position: "fixed",
"z-index": 100,
top: 0,
left: 0,
bottom: 0,
right: 0,
background: "rgba(0, 0, 0, 0.5)",
display: "flex",
"align-items": "center",
"justify-content": "center"
}}
{...underlayProps()}
>
<FocusScope contain restoreFocus autoFocus>
<div
{...overlayProps()}
{...dialogProps()}
{...modalProps()}
ref={ref}
style={{
background: "white",
color: "black",
padding: "30px"
}}
>
<h3 {...titleProps()} style={{ "margin-top": 0 }}>
{props.title}
</h3>
{props.children}
</div>
</FocusScope>
</div>
);
}
function Example() {
let openButtonRef: HTMLButtonElement | undefined;
let closeButtonRef: HTMLButtonElement | undefined;
const state = createOverlayTriggerState({});
// createButton ensures that focus management is handled correctly,
// across all browsers. Focus is restored to the button once the
// dialog closes.
const { buttonProps: openButtonProps } = createButton(
{
onPress: () => state.open()
},
() => openButtonRef
);
const { buttonProps: closeButtonProps } = createButton(
{
onPress: () => state.close()
},
() => closeButtonRef
);
return (
<>
<button {...openButtonProps()} ref={openButtonRef}>
Open Dialog
</button>
<Show when={state.isOpen()}>
<OverlayContainer>
<ModalDialog title="Enter your name" isOpen onClose={state.close} isDismissable>
<form style={{ display: "flex", "flex-direction": "column" }}>
<label for="first-name">First Name:</label>
<input id="first-name" />
<label for="last-name">Last Name:</label>
<input id="last-name" />
<button {...closeButtonProps()} ref={closeButtonRef} style={{ "margin-top": "10px" }}>
Submit
</button>
</form>
</ModalDialog>
</OverlayContainer>
</Show>
</>
);
}
function App() {
return (
// Application must be wrapped in an OverlayProvider so that it can be
// hidden from screen readers when a modal opens.
<OverlayProvider>
<Example />
</OverlayProvider>
);
}
All notable changes are described in the CHANGELOG.md file.