npm i @accessible/drawer
An accessible and versatile drawer 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
styleobjects, anything! - Portal-friendly The drawer 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 Drawer from '@accessible/drawer' const Component = () => ( <Drawer.Drawer> <Drawer.Trigger> <button>Open me</button> </Drawer.Trigger> <Drawer.Target> <div className='my-drawer'> <Drawer.CloseButton> <button>Close me</button> </Drawer.CloseButton> <div>I've been revealed!</div> </div> </Drawer.Target> </Drawer.Drawer> )| Component | Description |
|---|---|
<Drawer> | This component creates the context for your drawer target and trigger and contains some configuration options. |
<Target> | This component wraps any React element and turns it into a drawer target. |
<Trigger> | This component wraps any React element and turns it into a drawer trigger. |
<CloseButton> | This is a convenience component that wraps any React element and adds an onClick handler to close the drawer. |
| Hook | Description |
|---|---|
useDrawer() | This hook provides the value of the drawer's DrawerContextValue object. |
useA11yTarget() | A React hook for creating a headless drawer target to WAI-ARIA authoring practices. |
useA11yTrigger() | A React hook for creating a headless drawer 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 drawer 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 drawer. By default the drawer is closed. |
| open | boolean | undefined | No | This creates a controlled drawer component where the open state of the drawer is controlled by this property. |
| onChange | (open: boolean) => void | undefined | No | This callback is invoked any time the open state of the drawer 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 drawer contents and any other children. |
A React hook for creating a headless drawer 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 { /** * Sets the placement of the drawer menu * @default "left" */ placement?: 'top' | 'right' | 'bottom' | 'left' /** * Adds this class name to props when the drawer is open */ openClass?: string /** * Adds this class name to props when the drawer is closed */ closedClass?: string /** * Adds this style to props when the drawer is open */ openStyle?: React.CSSProperties /** * Adds this style to props when the drawer 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 }type A11yProps = { readonly 'aria-hidden': boolean readonly id: string | undefined readonly className: string | undefined readonly style: { readonly visibility: 'hidden' | 'visible' } & React.CSSProperties } & { readonly style: | ({ readonly visibility: 'hidden' | 'visible' } & React.CSSProperties & { readonly position: 'fixed' readonly top: 0 readonly right: 0 readonly bottom: 'auto' readonly left: 0 readonly transform: 'translate3d(0, -100%, 0)' }) | ({ readonly visibility: 'hidden' | 'visible' } & React.CSSProperties & { readonly position: 'fixed' readonly top: 0 readonly right: 0 readonly bottom: 0 readonly left: 'auto' readonly transform: 'translate3d(100%, 0, 0)' }) | ({ readonly visibility: 'hidden' | 'visible' } & React.CSSProperties & { readonly position: 'fixed' readonly top: 'auto' readonly right: 0 readonly bottom: 0 readonly left: 0 readonly transform: 'translate3d(0, 100%, 0)' }) | ({ readonly visibility: 'hidden' | 'visible' } & React.CSSProperties & { readonly position: 'fixed' readonly top: 0 readonly right: 'auto' readonly bottom: 0 readonly left: 0 readonly transform: 'translate3d(-100%, 0, 0)' }) }import * as React from 'react' import {useA11yTarget} from '@accessible/drawer' const MyTarget = () => { const ref = React.useRef(null) const a11yProps = useA11yTarget(ref, {preventScroll: true}) return ( <div ref={ref} {...a11yProps}> I am the drawer content </div> ) }This component wraps any React element and turns it into a drawer target.
| Prop | Type | Default | Required? | Description |
|---|---|---|---|---|
| placement | "top" | "right" | "bottom" | "left" | false | No | When true this will render the drawer 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. |
| portal | boolean | string | PortalizeProps | false | No | When true this will render the drawer 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 drawer 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 drawer is closed. |
| openClass | string | undefined | No | This class name will be applied to the child element when the drawer is open. |
| closedStyle | React.CSSProperties | undefined | No | These styles will be applied to the child element when the drawer 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 drawer 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> // <div // class="alert" // aria-hidden="true" // id="π
°12" // style="visibility: hidden;" // > // Alert // </div>A React hook for creating a headless drawer 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 drawer is open */ openClass?: string /** * Adds this class name to props when the drawer is closed */ closedClass?: string /** * Adds this style to props when the drawer is open */ openStyle?: React.CSSProperties /** * Adds this style to props when the drawer is closed */ closedStyle?: React.CSSProperties /** * Adds an onClick handler in addition to the default one that * toggles the drawer's open state. */ onClick?: (e: MouseEvent) => any }interface A11yProps<E extends React.MouseEvent<any, MouseEvent>> { 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/drawer' const MyTrigger = () => { const ref = React.useRef(null) const a11yProps = useA11yTrigger(ref, { openClass: 'open', closedClass: 'closed', }) return ( <button ref={ref} {...a11yProps}> Clicking me toggles the drawer content </button> ) }This component wraps any React element and adds an onClick handler which toggles the open state of the drawer target.
| Prop | Type | Default | Required? | Description |
|---|---|---|---|---|
| closedClass | string | undefined | No | This class name will be applied to the child element when the drawer is closed. |
| openClass | string | undefined | No | This class name will be applied to the child element when the drawer is open. |
| closedStyle | React.CSSProperties | undefined | No | These styles will be applied to the child element when the drawer is closed. |
| openStyle | React.CSSProperties | undefined | No | These styles name will be applied to the child element when the drawer 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> // <button // class="my-button" // aria-controls="π
°12" // aria-expanded="false" // > // Open me! // </button>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 drawer. */ onClick?: (e: MouseEvent) => any }interface A11yProps<E extends React.MouseEvent<any, MouseEvent>> { 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/drawer' const MyTrigger = () => { const ref = React.useRef(null) const a11yProps = useA11yCloseButton(ref, { onClick: () => console.log('Closing!'), }) return ( <button ref={ref} {...a11yProps}> Clicking me closes the drawer content </button> ) }This is a convenience component that wraps any React element and adds an onClick handler which closes the drawer.
| 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> // <button // class="my-button" // aria-controls="drawer--12" // aria-expanded="false" // > // Close me // </button>This hook provides the value of the drawer's DrawerContextValue object
export interface DrawerContextValue { /** * The open state of the drawer */ isOpen: boolean /** * Opens the drawer */ open: () => void /** * Closes the drawer */ close: () => void /** * Toggles the open state of the drawer */ toggle: () => void /** * A unique ID for the drawer target */ id?: string }const Component = () => { const {open, close, toggle, isOpen} = useDrawer() return <button onClick={toggle}>Toggle the drawer</button> }MIT