Skip to main content
Reakit
DocumentationNewsletter
GitHub
GitHub

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

Content
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

  • Disclosure extends the accessibility features of Button.
  • 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.

#Composition

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 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 boolean | undefined

    Same as the HTML attribute.

  • focusable boolean | undefined

    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.

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 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 boolean

    Whether it's animating or not.

  • stopAnimation () => void

    Stops animation. It's called automatically if there's a CSS transition.