npm i @accessible/modal
An accessible and versatile modal component for React
- Style-agnostic You can use this component with the styling library of your choice. It
works with CSS-in-JS, SASS, plain CSS, plain
style
objects, anything! - Portal-friendly The modal target will render into React portals of your choice when configured to do so.
- a11y/aria-compliant This component works with screen readers out of the box and manages focus for you.
Check out the example on CodeSandbox
import * as React from 'react'
import * as Modal from '@accessible/modal'
const Component = () => (
<Modal.Modal>
<Modal.Trigger>
<button>Open me</button>
</Modal.Trigger>
<Modal.Target>
<div className='my-modal'>
<Modal.CloseButton>
<button>Close me</button>
</Modal.CloseButton>
<div>You did a thing</div>
</div>
</Modal.Target>
</Modal.Modal>
)
Component | Description |
---|---|
<Modal> |
This component creates the context for your modal target and trigger and contains some configuration options. |
<Target> |
This component wraps any React element and turns it into a modal target. |
<Trigger> |
This component wraps any React element and turns it into a modal trigger. |
<CloseButton> |
This is a convenience component that wraps any React element and adds an onClick handler to close the modal. |
Hook | Description |
---|---|
useModal() |
This hook provides the value of the modal's ModalContextValue object. |
useA11yTarget() |
A React hook for creating a headless modal target to WAI-ARIA authoring practices. |
useA11yTrigger() |
A React hook for creating a headless modal trigger to WAI-ARIA authoring practices. |
useA11yCloseButton() |
A React hook for creating a headless close button to WAI-ARIA authoring practices. |
This component creates the context for your modal target and trigger and contains some configuration options.
Prop | Type | Default | Required? | Description |
---|---|---|---|---|
defaultOpen | boolean |
false |
No | This sets the default open state of the modal. By default the modal is closed. |
open | boolean |
undefined |
No | This creates a controlled modal component where the open state of the modal is controlled by this property. |
onChange | (open: boolean) => void |
undefined |
No | This callback is invoked any time the open state of the modal changes. |
id | string |
undefined |
No | By default this component creates a unique id for you, as it is required for certain aria attributes. Supplying an id here overrides the auto id feature. |
children | React.ReactNode |
undefined |
No | Your modal contents and any other children. |
A React hook for creating a headless modal target to WAI-ARIA authoring practices.
Argument | Type | Required? | Description |
---|---|---|---|
target | React.RefObject<T> | T | null |
Yes | A React ref or HTML element |
options | UseA11yTargetOptions |
No | Configuration options |
export interface UseA11yTargetOptions {
/**
* Adds this class name to props when the modal is open
*/
openClass?: string
/**
* Adds this class name to props when the modal is closed
*/
closedClass?: string
/**
* Adds this style to props when the modal is open
*/
openStyle?: React.CSSProperties
/**
* Adds this style to props when the modal is closed
*/
closedStyle?: React.CSSProperties
/**
* Prevents the `window` from scrolling when the target is
* focused after opening.
*/
preventScroll?: boolean
/**
* When `true`, this closes the target element when the `Escape`
* key is pressed.
* @default true
*/
closeOnEscape?: boolean
}
interface A11yProps {
readonly role: 'dialog'
readonly 'aria-hidden': boolean
readonly id: string | undefined
readonly className: string | undefined
readonly style: {
readonly visibility: 'hidden' | 'visible'
readonly position: 'fixed'
readonly margin: 'auto'
readonly left: '50%'
readonly top: '50%'
readonly transform: 'translate3d(-50%, -50%, 0)'
}
}
import * as React from 'react'
import {useA11yTarget} from '@accessible/modal'
const MyTarget = () => {
const ref = React.useRef(null)
const a11yProps = useA11yTarget(ref, {closeOnEscape: false})
return (
<div ref={ref} {...a11yProps}>
I am the modal dialog
</div>
)
}
This component wraps any React element and turns it into a modal target.
Prop | Type | Default | Required? | Description |
---|---|---|---|---|
portal | boolean | string | PortalizeProps |
false |
No | When true this will render the modal into a React portal with the id #portals . You can render it into any portal by providing its query selector here, e.g. #foobar , [data-portal=true] , or .foobar . |
closeOnEscape | boolean |
true |
No | By default the modal will close when the Escape key is pressed. You can turn this off by providing false here. |
closedClass | string |
undefined |
No | This class name will be applied to the child element when the modal is closed . |
openClass | string |
undefined |
No | This class name will be applied to the child element when the modal is open . |
closedStyle | React.CSSProperties |
undefined |
No | These styles will be applied to the child element when the modal is closed in addition to the default styles that set the target's visibility. |
openStyle | React.CSSProperties |
undefined |
No | These styles name will be applied to the child element when the modal is open in addition to the default styles that set the target's visibility. |
preventScroll | boolean |
false |
No | When true this will prevent your browser from scrolling the document to bring the newly-focused tab into view. |
children | React.ReactElement |
undefined |
Yes | The child is cloned by this component and has aria attributes injected into its props as well as the events defined above. |
<Target>
<div className='alert'>Alert</div>
</Target>
A React hook for creating a headless modal trigger to WAI-ARIA authoring practices. In addition to providing accessibility props to your component, this hook will add events for interoperability between actual elements and fake ones e.g. and
Argument | Type | Required? | Description |
---|---|---|---|
target | React.RefObject<T> | T | null |
Yes | A React ref or HTML element |
options | UseA11yTriggerOptions |
No | Configuration options |
export interface UseA11yTriggerOptions {
/**
* Adds this class name to props when the modal is open
*/
openClass?: string
/**
* Adds this class name to props when the modal is closed
*/
closedClass?: string
/**
* Adds this style to props when the modal is open
*/
openStyle?: React.CSSProperties
/**
* Adds this style to props when the modal is closed
*/
closedStyle?: React.CSSProperties
/**
* Adds an onClick handler in addition to the default one that
* toggles the modal's open state.
*/
onClick?: (e: MouseEvent) => any
}
interface A11yProps<E extends React.MouseEvent<any, MouseEvent>> {
readonly 'aria-haspopup': 'dialog'
readonly 'aria-controls': string | undefined
readonly 'aria-expanded': boolean
readonly role: 'button'
readonly tabIndex: 0
readonly className: string | undefined
readonly style: React.CSSProperties | undefined
}
import * as React from 'react'
import {useA11yTrigger} from '@accessible/modal'
const MyTrigger = () => {
const ref = React.useRef(null)
const a11yProps = useA11yTrigger(ref, {
openClass: 'open',
closedClass: 'closed',
})
return (
<button ref={ref} {...a11yProps}>
Clicking me toggles the modal dialog
</button>
)
}
This component wraps any React element and adds an onClick
handler which toggles the open state
of the modal target.
Prop | Type | Default | Required? | Description |
---|---|---|---|---|
closedClass | string |
undefined |
No | This class name will be applied to the child element when the modal is closed . |
openClass | string |
undefined |
No | This class name will be applied to the child element when the modal is open . |
closedStyle | React.CSSProperties |
undefined |
No | These styles will be applied to the child element when the modal is closed . |
openStyle | React.CSSProperties |
undefined |
No | These styles name will be applied to the child element when the modal is open . |
children | React.ReactElement |
undefined |
Yes | The child is cloned by this component and has aria attributes injected into its props as well as the events defined above. |
<Trigger on='click'>
<button className='my-button'>Open me!</button>
</Trigger>
A React hook for creating a headless close button to WAI-ARIA authoring practices. In addition to providing accessibility props to your component, this hook will add events for interoperability between actual
Argument | Type | Required? | Description |
---|---|---|---|
target | React.RefObject<T> | T | null |
Yes | A React ref or HTML element |
options | UseA11yCloseButtonOptions |
No | Configuration options |
export interface UseA11yCloseButtonOptions {
/**
* Adds an onClick handler in addition to the default one that
* closes the modal.
*/
onClick?: (e: MouseEvent) => any
}
interface A11yProps<E extends React.MouseEvent<any, MouseEvent>> {
readonly 'aria-haspopup': 'dialog'
readonly 'aria-controls': string | undefined
readonly 'aria-expanded': boolean
readonly 'aria-label': 'Close'
readonly role: 'button'
readonly tabIndex: 0
}
import * as React from 'react'
import {useA11yCloseButton} from '@accessible/modal'
const MyTrigger = () => {
const ref = React.useRef(null)
const a11yProps = useA11yCloseButton(ref, {
onClick: () => console.log('Closing!'),
})
return (
<button ref={ref} {...a11yProps}>
Clicking me closes the modal dialog
</button>
)
}
This is a convenience component that wraps any React element and adds an onClick handler which closes the modal.
Prop | Type | Default | Required? | Description |
---|---|---|---|---|
children | React.ReactElement |
undefined |
Yes | The child is cloned by this component and has aria attributes injected into its props as well as the events defined above. |
<CloseButton>
<button className='my-button'>Close me</button>
</CloseButton>
This hook provides the value of the modal's ModalContextValue object
export interface ModalContextValue {
/**
* The open state of the modal
*/
isOpen: boolean
/**
* Opens the modal
*/
open: () => void
/**
* Closes the modal
*/
close: () => void
/**
* Toggles the open state of the modal
*/
toggle: () => void
/**
* A unique ID for the modal target
*/
id?: string
}
const Component = () => {
const {open, close, toggle, isOpen} = useModal()
return <button onClick={toggle}>Toggle the modal</button>
}
MIT