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 Disclosures 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
Disclosureextends the accessibility features of Button.Disclosurehas a value specified foraria-controlsthat refers toDisclosureContent.- When
DisclosureContentis visible,Disclosurehasaria-expandedset totrue. WhenDisclosureContentis hidden, it is set tofalse.
Learn more in Accessibility.
#Composition
Disclosureuses Button, and is used by DialogDisclosure.DisclosureContentuses Role, and is used by Dialog, DialogBackdrop, TabPanel, Tooltip and all their derivatives.
Learn more in Composition.
#Props
#useDisclosureState
-
baseIdstringID that will serve as a base for all the items IDs.
-
visiblebooleanWhether it's visible or not.
-
animatednumber | booleanIf
true,animatingwill be set totruewhenvisibleis updated. It'll wait forstopAnimationto be called or a CSS transition ends. Ifanimatedis set to anumber,stopAnimationwill be called only after the same number of milliseconds have passed.
#Disclosure
-
disabledboolean | undefinedSame as the HTML attribute.
-
focusableboolean | undefinedWhen an element is
disabled, it may still befocusable. It works similarly toreadOnlyon form elements. In this case, onlyaria-disabledwill 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.
-
visiblebooleanWhether it's visible or not.
-
baseIdstringID that will serve as a base for all the items IDs.
-
toggle() => voidToggles the
visiblestate
#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.
-
baseIdstringID that will serve as a base for all the items IDs.
-
visiblebooleanWhether it's visible or not.
-
animatednumber | booleanIf
true,animatingwill be set totruewhenvisibleis updated. It'll wait forstopAnimationto be called or a CSS transition ends. Ifanimatedis set to anumber,stopAnimationwill be called only after the same number of milliseconds have passed. -
animatingbooleanWhether it's animating or not.
-
stopAnimation() => voidStops animation. It's called automatically if there's a CSS transition.