Disclosure
Accessible Disclosure
component that controls visibility of a section of content. It follows the WAI-ARIA Disclosure Pattern.
#Installation
npm install reakit
Learn more in Get started.
#Usage
import { useDisclosureState, Disclosure, DisclosureContent, } from "reakit/Disclosure"; function Example() { const disclosure = useDisclosureState({ visible: true }); return ( <> <Disclosure {...disclosure}>Toggle</Disclosure> <DisclosureContent {...disclosure}>Content</DisclosureContent> </> ); }
#Conditionally rendering
You shouldn't conditionally render the DisclosureContent
component as this will make some Reakit features not work. Instead, you can use render props and conditionally render the underneath element:
import { useDisclosureState, Disclosure, DisclosureContent, } from "reakit/Disclosure"; function Example() { const disclosure = useDisclosureState(); return ( <> <Disclosure {...disclosure}>Toggle</Disclosure> {/* instead of {disclosure.visible && <DisclosureContent {...disclosure}>Content</DisclosureContent>} */} <DisclosureContent {...disclosure}> {(props) => disclosure.visible && <div {...props}>Content</div>} </DisclosureContent> </> ); }
#Multiple components
Each DisclosureContent
component should have its own useDisclosureState
. This is also true for derivative modules like Dialog, Popover, Menu, Tooltip etc.
If you want to have only one Disclosure
element controling multiple DisclosureContent
components, you can use render props to apply the same state to different Disclosure
s that will result in a single element.
import { useDisclosureState, Disclosure, DisclosureContent, } from "reakit/Disclosure"; function Example() { const disclosure1 = useDisclosureState(); const disclosure2 = useDisclosureState(); return ( <> <Disclosure {...disclosure1}> {(props) => ( <Disclosure {...props} {...disclosure2}> Toggle All </Disclosure> )} </Disclosure> <DisclosureContent {...disclosure1}>Content 1</DisclosureContent> <DisclosureContent {...disclosure2}>Content 2</DisclosureContent> </> ); }
#Styling
Reakit components are unstyled by default. You're free to use whatever approach you want. Each component returns a single HTML element that accepts all HTML props, including className
and style
.
The example below uses Emotion. But these styles can be reproduced using static CSS and other CSS-in-JS libraries, such as styled-components.
import { css } from "emotion"; import { useDisclosureState, Disclosure, DisclosureContent, } from "reakit/Disclosure"; const styles = css` .button { line-height: 1.5; border: transparent; cursor: pointer; font-size: 16px; border-radius: 0.25rem; } .button:before { display: inline-block; content: "►"; margin: 4px; } .button[aria-expanded="true"]:before { transform: rotateZ(90deg); } .content { border: 1px solid #ddd; border-radius: 4px; padding: 8px; } `; function Example() { const disclosure = useDisclosureState(); return ( <div className={styles}> <Disclosure {...disclosure} className="button"> Toggle </Disclosure> <DisclosureContent {...disclosure} className="content"> Content </DisclosureContent> </div> ); }
Learn more on Styling.
#Animating
To perform animations, you must set animated
to true
on useDisclosureState
. It'll enable additional data-*
props on DisclosureContent
which you can use as selectors in CSS. It will also ensure that the element will only get hidden when the transition has ended.
Use the [data-enter]
and [data-leave]
selectors to style both enter and leave animations. data-enter
is added shortly after the element is shown so there's an interval for the browser to process the initial styles and understand this as a transition.
import { css } from "emotion"; import { useDisclosureState, Disclosure, DisclosureContent, } from "reakit/Disclosure"; const styles = css` .content { transition: opacity 250ms ease-in-out, transform 250ms ease-in-out; opacity: 0; transform: translate3d(0, -100%, 0); } .content[data-enter] { opacity: 1; transform: translate3d(0, 0, 0); } .content[data-leave] { // Uncomment below to have a different leave animation // transform: translate3d(0, 100%, 0); } `; function Example() { // Set animated to true const disclosure = useDisclosureState({ animated: true }); return ( <div className={styles}> <Disclosure {...disclosure}>Toggle</Disclosure> <DisclosureContent {...disclosure} className="content"> Content </DisclosureContent> </div> ); }
Alternatively, you can set animated
to a number
value, so it'll stop the animation and hide the element after that number in milliseconds.
import { useDisclosureState } from "reakit/Disclosure"; // Hides DisclosureContent after 500 milliseconds after calling disclosure.hide() const disclosure = useDisclosureState({ animated: 500 });
#Manually stopping animations
For transitions that don't use CSS transitions/animations, you must stop the animation manually by calling disclosure.stopAnimation()
. In the example below, you can see a physics-based animation using react-spring:
import { useSpring, animated } from "react-spring"; import { useDisclosureState, Disclosure, DisclosureContent, } from "reakit/Disclosure"; function Example() { const disclosure = useDisclosureState({ animated: true }); const { opacity, scale } = useSpring({ opacity: disclosure.visible ? 1 : 0, scale: disclosure.visible ? 1 : 0, onRest: disclosure.stopAnimation, }); return ( <div> <Disclosure {...disclosure}>Toggle</Disclosure> <DisclosureContent {...disclosure} as={animated.div} style={{ opacity, transformOrigin: "top center", transform: scale.interpolate((s) => `scaleY(${s})`), }} > Content </DisclosureContent> </div> ); }
#Accessibility
Disclosure
extends the accessibility features of Button.Disclosure
has a value specified foraria-controls
that refers toDisclosureContent
.- When
DisclosureContent
is visible,Disclosure
hasaria-expanded
set totrue
. WhenDisclosureContent
is hidden, it is set tofalse
.
Learn more in Accessibility.
#Composition
Disclosure
uses Button, and is used by DialogDisclosure.DisclosureContent
uses Role, and is used by Dialog, DialogBackdrop, TabPanel, Tooltip and all their derivatives.
Learn more in Composition.
#Props
#useDisclosureState
-
baseId
string
ID that will serve as a base for all the items IDs.
-
visible
boolean
Whether it's visible or not.
-
animated
number | boolean
If
true
,animating
will be set totrue
whenvisible
is updated. It'll wait forstopAnimation
to be called or a CSS transition ends. Ifanimated
is set to anumber
,stopAnimation
will be called only after the same number of milliseconds have passed.
#Disclosure
-
disabled
boolean | undefined
Same as the HTML attribute.
-
focusable
boolean | undefined
When an element is
disabled
, it may still befocusable
. It works similarly toreadOnly
on form elements. In this case, onlyaria-disabled
will be set.
3 state props
These props are returned by the state hook. You can spread them into this component (
{...state}
) or pass them separately. You can also provide these props from your own state logic.
-
visible
boolean
Whether it's visible or not.
-
baseId
string
ID that will serve as a base for all the items IDs.
-
toggle
() => void
Toggles the
visible
state
#DisclosureContent
5 state props
These props are returned by the state hook. You can spread them into this component (
{...state}
) or pass them separately. You can also provide these props from your own state logic.
-
baseId
string
ID that will serve as a base for all the items IDs.
-
visible
boolean
Whether it's visible or not.
-
animated
number | boolean
If
true
,animating
will be set totrue
whenvisible
is updated. It'll wait forstopAnimation
to be called or a CSS transition ends. Ifanimated
is set to anumber
,stopAnimation
will be called only after the same number of milliseconds have passed. -
animating
boolean
Whether it's animating or not.
-
stopAnimation
() => void
Stops animation. It's called automatically if there's a CSS transition.