reakit
Version:
Toolkit for building accessible rich web apps with React
356 lines (274 loc) • 9.99 kB
Markdown
---
path: /docs/disclosure/
redirect_from:
- /components/hidden/
- /components/hidden/hiddencontainer/
- /components/hidden/hiddenhide/
- /components/hidden/hiddenshow/
- /components/hidden/hiddentoggle/
---
# Disclosure
Accessible `Disclosure` component that controls visibility of a section of content. It follows the [WAI-ARIA Disclosure Pattern](https://www.w3.org/TR/wai-aria-practices/#disclosure).
<carbon-ad></carbon-ad>
## Installation
```sh
npm install reakit
```
Learn more in [Get started](/docs/get-started/).
## Usage
```jsx
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](/docs/composition/#render-props) and conditionally render the underneath element:
```jsx
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](/docs/dialog/), [Popover](/docs/popover/), [Menu](/docs/menu/), [Tooltip](/docs/tooltip/) etc.
If you want to have only one `Disclosure` element controling multiple `DisclosureContent` components, you can use [render props](/docs/composition/#render-props) to apply the same state to different `Disclosure`s that will result in a single element.
```jsx
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](https://emotion.sh/docs/introduction). But these styles can be reproduced using static CSS and other CSS-in-JS libraries, such as [styled-components](https://styled-components.com/).
```jsx unstyled
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](/docs/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.
```jsx
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.
```js static
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](https://www.react-spring.io/):
```jsx
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](/docs/button/#accessibility).
- `Disclosure` has a value specified for `aria-controls` that refers to `DisclosureContent`.
- When `DisclosureContent` is visible, `Disclosure` has `aria-expanded` set to `true`. When `DisclosureContent` is hidden, it is set to `false`.
Learn more in [Accessibility](/docs/accessibility/).
## Composition
- `Disclosure` uses [Button](/docs/button/), and is used by [DialogDisclosure](/docs/dialog/).
- `DisclosureContent` uses [Role](/docs/role/), and is used by [Dialog](/docs/dialog/), [DialogBackdrop](/docs/dialog/), [TabPanel](/docs/tab/), [Tooltip](/docs/tooltip/) and all their derivatives.
Learn more in [Composition](/docs/composition/#props-hooks).
## Props
<!-- Automatically generated -->
### `useDisclosureState`
- **`baseId`**
<code>string</code>
ID that will serve as a base for all the items IDs.
- **`visible`**
<code>boolean</code>
Whether it's visible or not.
- **`animated`**
<code>number | boolean</code>
If `true`, `animating` will be set to `true` when `visible` is updated.
It'll wait for `stopAnimation` to be called or a CSS transition ends.
If `animated` is set to a `number`, `stopAnimation` will be called only
after the same number of milliseconds have passed.
### `Disclosure`
- **`disabled`**
<code>boolean | undefined</code>
Same as the HTML attribute.
- **`focusable`**
<code>boolean | undefined</code>
When an element is `disabled`, it may still be `focusable`. It works
similarly to `readOnly` on form elements. In this case, only
`aria-disabled` will be set.
<details><summary>3 state props</summary>
> 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`**
<code>boolean</code>
Whether it's visible or not.
- **`baseId`**
<code>string</code>
ID that will serve as a base for all the items IDs.
- **`toggle`**
<code>() => void</code>
Toggles the `visible` state
</details>
### `DisclosureContent`
<details><summary>5 state props</summary>
> 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`**
<code>string</code>
ID that will serve as a base for all the items IDs.
- **`visible`**
<code>boolean</code>
Whether it's visible or not.
- **`animated`**
<code>number | boolean</code>
If `true`, `animating` will be set to `true` when `visible` is updated.
It'll wait for `stopAnimation` to be called or a CSS transition ends.
If `animated` is set to a `number`, `stopAnimation` will be called only
after the same number of milliseconds have passed.
- **`animating`**
<code>boolean</code>
Whether it's animating or not.
- **`stopAnimation`**
<code>() => void</code>
Stops animation. It's called automatically if there's a CSS transition.
</details>