useDisclosure
Provides the behavior and accessibility implementation for a disclosure component.
| install | yarn add react-aria | 
|---|---|
| version | 3.40.0 | 
| usage | import {useDisclosure} from 'react-aria' | 
API#
useDisclosure(
  props: AriaDisclosureProps,
  state: DisclosureState,
  ref: RefObject<Element
    |  | null>
): DisclosureAria
Features#
A disclosure is a collapsible section of content. It is composed of a trigger button and a panel that contains the content. useDisclosure can be used to implement these in an accessible way.
- Support for mouse, touch, and keyboard interactions to open and close the disclosure
- Support for disabled disclosures
- Follows the disclosure ARIA pattern, semantically linking the trigger button and panel
- Uses hidden="until-found" in supported browsers, enabling find-in-page search support and improved search engine visibility for collapsed content
Anatomy#
A disclosure consists of a trigger button and a panel. Clicking on or pressing Enter or Space while the trigger button is focused toggles the visibility of the panel.
useDisclosure returns props to spread onto the trigger button and panel.
| Name | Type | Description | 
| buttonProps | AriaButtonProps | Props for the disclosure button. | 
| panelProps | HTMLAttributes<HTMLElement> | Props for the disclosure panel. | 
State is managed by the useDisclosureState
hook in @react-stately/disclosure. The state object should be passed as an option to useDisclosure.
Example#
This example displays a basic disclosure with a button that toggles the visibility of the panel.
import {useButton, useDisclosure} from 'react-aria';
import {useDisclosureState} from 'react-stately';
import {mergeProps, useFocusRing} from 'react-aria';
function Disclosure(props) {
  let state = useDisclosureState(props);
  let panelRef = React.useRef<HTMLDivElement | null>(null);
  let triggerRef = React.useRef<HTMLButtonElement | null>(null);
  let { buttonProps: triggerProps, panelProps } = useDisclosure(
    props,
    state,
    panelRef
  );
  let { buttonProps } = useButton(triggerProps, triggerRef);
  let { isFocusVisible, focusProps } = useFocusRing();
  return (
    <div className="disclosure">
      <h3>
        <button
          className="trigger"
          ref={triggerRef}
          {...mergeProps(buttonProps, focusProps)}
          style={{ outline: isFocusVisible ? '2px solid dodgerblue' : 'none' }}
        >
          <svg viewBox="0 0 24 24">
            <path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
          </svg>
          {props.title}
        </button>
      </h3>
      <div className="panel" ref={panelRef} {...panelProps}>
        <p>
          {props.children}
        </p>
      </div>
    </div>
  );
}
<Disclosure title="System Requirements">
  Details about system requirements here.
</Disclosure>import {useButton, useDisclosure} from 'react-aria';
import {useDisclosureState} from 'react-stately';
import {mergeProps, useFocusRing} from 'react-aria';
function Disclosure(props) {
  let state = useDisclosureState(props);
  let panelRef = React.useRef<HTMLDivElement | null>(null);
  let triggerRef = React.useRef<HTMLButtonElement | null>(
    null
  );
  let { buttonProps: triggerProps, panelProps } =
    useDisclosure(props, state, panelRef);
  let { buttonProps } = useButton(triggerProps, triggerRef);
  let { isFocusVisible, focusProps } = useFocusRing();
  return (
    <div className="disclosure">
      <h3>
        <button
          className="trigger"
          ref={triggerRef}
          {...mergeProps(buttonProps, focusProps)}
          style={{
            outline: isFocusVisible
              ? '2px solid dodgerblue'
              : 'none'
          }}
        >
          <svg viewBox="0 0 24 24">
            <path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
          </svg>
          {props.title}
        </button>
      </h3>
      <div className="panel" ref={panelRef} {...panelProps}>
        <p>
          {props.children}
        </p>
      </div>
    </div>
  );
}
<Disclosure title="System Requirements">
  Details about system requirements here.
</Disclosure>import {
  useButton,
  useDisclosure
} from 'react-aria';
import {useDisclosureState} from 'react-stately';
import {
  mergeProps,
  useFocusRing
} from 'react-aria';
function Disclosure(
  props
) {
  let state =
    useDisclosureState(
      props
    );
  let panelRef = React
    .useRef<
      | HTMLDivElement
      | null
    >(null);
  let triggerRef = React
    .useRef<
      | HTMLButtonElement
      | null
    >(null);
  let {
    buttonProps:
      triggerProps,
    panelProps
  } = useDisclosure(
    props,
    state,
    panelRef
  );
  let { buttonProps } =
    useButton(
      triggerProps,
      triggerRef
    );
  let {
    isFocusVisible,
    focusProps
  } = useFocusRing();
  return (
    <div className="disclosure">
      <h3>
        <button
          className="trigger"
          ref={triggerRef}
          {...mergeProps(
            buttonProps,
            focusProps
          )}
          style={{
            outline:
              isFocusVisible
                ? '2px solid dodgerblue'
                : 'none'
          }}
        >
          <svg viewBox="0 0 24 24">
            <path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
          </svg>
          {props.title}
        </button>
      </h3>
      <div
        className="panel"
        ref={panelRef}
        {...panelProps}
      >
        <p>
          {props
            .children}
        </p>
      </div>
    </div>
  );
}
<Disclosure title="System Requirements">
  Details about system
  requirements here.
</Disclosure>Show CSS
@import "@react-aria/example-theme";
.disclosure {
  .trigger {
    background: none;
    border: none;
    box-shadow: none;
    font-weight: bold;
    font-size: 16px;
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--text-color);
    svg {
      rotate: 0deg;
      transition: rotate 200ms;
      width: 12px;
      height: 12px;
      fill: none;
      stroke: currentColor;
      stroke-width: 3px;
    }
    &[aria-expanded="true"] svg {
      rotate: 90deg;
    }
    &:disabled {
      color: var(--gray-300);
    }
  }
}
.panel {
  margin-left: 32px;
}@import "@react-aria/example-theme";
.disclosure {
  .trigger {
    background: none;
    border: none;
    box-shadow: none;
    font-weight: bold;
    font-size: 16px;
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--text-color);
    svg {
      rotate: 0deg;
      transition: rotate 200ms;
      width: 12px;
      height: 12px;
      fill: none;
      stroke: currentColor;
      stroke-width: 3px;
    }
    &[aria-expanded="true"] svg {
      rotate: 90deg;
    }
    &:disabled {
      color: var(--gray-300);
    }
  }
}
.panel {
  margin-left: 32px;
}@import "@react-aria/example-theme";
.disclosure {
  .trigger {
    background: none;
    border: none;
    box-shadow: none;
    font-weight: bold;
    font-size: 16px;
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--text-color);
    svg {
      rotate: 0deg;
      transition: rotate 200ms;
      width: 12px;
      height: 12px;
      fill: none;
      stroke: currentColor;
      stroke-width: 3px;
    }
    &[aria-expanded="true"] svg {
      rotate: 90deg;
    }
    &:disabled {
      color: var(--gray-300);
    }
  }
}
.panel {
  margin-left: 32px;
}Usage#
The following examples show how to use the Disclosure component created in the above example.
Default expansion#
Whether or not the disclosure is expanded or not by default can be set with the defaultExpanded prop.
<Disclosure title="System Requirements" defaultExpanded>
  Details about system requirements here.
</Disclosure><Disclosure title="System Requirements" defaultExpanded>
  Details about system requirements here.
</Disclosure><Disclosure
  title="System Requirements"
  defaultExpanded
>
  Details about system
  requirements here.
</Disclosure>Controlled expansion#
Expansion can be controlled using the isExpanded prop, paired with the onExpandedChange event. The onExpandedChange event is fired when the user presses the trigger button.
function ControlledDisclosure(props) {
  let [isExpanded, setExpanded] = React.useState(false);
  return (
    <Disclosure
      title="System Requirements"
      isExpanded={isExpanded}
      onExpandedChange={setExpanded}
    >
      Details about system requirements here.
    </Disclosure>
  );
}
function ControlledDisclosure(props) {
  let [isExpanded, setExpanded] = React.useState(false);
  return (
    <Disclosure
      title="System Requirements"
      isExpanded={isExpanded}
      onExpandedChange={setExpanded}
    >
      Details about system requirements here.
    </Disclosure>
  );
}
function ControlledDisclosure(
  props
) {
  let [
    isExpanded,
    setExpanded
  ] = React.useState(
    false
  );
  return (
    <Disclosure
      title="System Requirements"
      isExpanded={isExpanded}
      onExpandedChange={setExpanded}
    >
      Details about
      system requirements
      here.
    </Disclosure>
  );
}
Disabled#
A disclosure can be disabled with the isDisabled prop. This will disable the trigger button and prevent the panel from being opened or closed.
<Disclosure title="System Requirements" isDisabled>
  Details about system requirements here.
</Disclosure><Disclosure title="System Requirements" isDisabled>
  Details about system requirements here.
</Disclosure><Disclosure
  title="System Requirements"
  isDisabled
>
  Details about system
  requirements here.
</Disclosure>Disclosure Group#
A disclosure group (i.e. accordion) is a set of disclosures where only one disclosure can be expanded at a time. The following example shows how to create a DisclosureGroup component with the useDisclosureGroupState hook. We'll also create a DisclosureItem component that uses the DisclosureGroupState context for managing its state.
import {useId} from 'react-aria';
import {useDisclosureGroupState} from 'react-stately';
const DisclosureGroupStateContext = React.createContext(null);
function DisclosureGroup(props) {
  let state = useDisclosureGroupState(props);
  return (
    <div className="group">
      <DisclosureGroupStateContext.Provider value={state}>
        {props.children}
      </DisclosureGroupStateContext.Provider>
    </div>
  );
}
function DisclosureItem(props) {
  let defaultId = useId();
  let id = props.id || defaultId;
  let groupState = React.useContext(DisclosureGroupStateContext);
  let isExpanded = groupState
    ? groupState.expandedKeys.has(id)
    : props.isExpanded;
  let state = useDisclosureState({
    ...props,
    isExpanded,
    onExpandedChange(isExpanded) {
      if (groupState) {
        groupState.toggleKey(id);
      }
      props.onExpandedChange?.(isExpanded);
    }
  });
  let panelRef = React.useRef<HTMLDivElement | null>(null);
  let triggerRef = React.useRef<HTMLButtonElement | null>(null);
  let isDisabled = props.isDisabled || groupState?.isDisabled || false;
  let { buttonProps: triggerProps, panelProps } = useDisclosure(
    {
      ...props,
      isExpanded,
      isDisabled
    },
    state,
    panelRef
  );
  let { buttonProps } = useButton(triggerProps, triggerRef);
  let { isFocusVisible, focusProps } = useFocusRing();
  return (
    <div className="disclosure">
      <h3>
        <button
          className="trigger"
          ref={triggerRef}
          {...mergeProps(buttonProps, focusProps)}
          style={{ outline: isFocusVisible ? '2px solid dodgerblue' : 'none' }}
        >
          <svg viewBox="0 0 24 24">
            <path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
          </svg>
          {props.title}
        </button>
      </h3>
      <div className="panel" ref={panelRef} {...panelProps}>
        <p>
          {props.children}
        </p>
      </div>
    </div>
  );
}
import {useId} from 'react-aria';
import {useDisclosureGroupState} from 'react-stately';
const DisclosureGroupStateContext = React.createContext(
  null
);
function DisclosureGroup(props) {
  let state = useDisclosureGroupState(props);
  return (
    <div className="group">
      <DisclosureGroupStateContext.Provider value={state}>
        {props.children}
      </DisclosureGroupStateContext.Provider>
    </div>
  );
}
function DisclosureItem(props) {
  let defaultId = useId();
  let id = props.id || defaultId;
  let groupState = React.useContext(
    DisclosureGroupStateContext
  );
  let isExpanded = groupState
    ? groupState.expandedKeys.has(id)
    : props.isExpanded;
  let state = useDisclosureState({
    ...props,
    isExpanded,
    onExpandedChange(isExpanded) {
      if (groupState) {
        groupState.toggleKey(id);
      }
      props.onExpandedChange?.(isExpanded);
    }
  });
  let panelRef = React.useRef<HTMLDivElement | null>(null);
  let triggerRef = React.useRef<HTMLButtonElement | null>(
    null
  );
  let isDisabled = props.isDisabled ||
    groupState?.isDisabled || false;
  let { buttonProps: triggerProps, panelProps } =
    useDisclosure(
      {
        ...props,
        isExpanded,
        isDisabled
      },
      state,
      panelRef
    );
  let { buttonProps } = useButton(triggerProps, triggerRef);
  let { isFocusVisible, focusProps } = useFocusRing();
  return (
    <div className="disclosure">
      <h3>
        <button
          className="trigger"
          ref={triggerRef}
          {...mergeProps(buttonProps, focusProps)}
          style={{
            outline: isFocusVisible
              ? '2px solid dodgerblue'
              : 'none'
          }}
        >
          <svg viewBox="0 0 24 24">
            <path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
          </svg>
          {props.title}
        </button>
      </h3>
      <div className="panel" ref={panelRef} {...panelProps}>
        <p>
          {props.children}
        </p>
      </div>
    </div>
  );
}
import {useId} from 'react-aria';
import {useDisclosureGroupState} from 'react-stately';
const DisclosureGroupStateContext =
  React.createContext(
    null
  );
function DisclosureGroup(
  props
) {
  let state =
    useDisclosureGroupState(
      props
    );
  return (
    <div className="group">
      <DisclosureGroupStateContext.Provider
        value={state}
      >
        {props.children}
      </DisclosureGroupStateContext.Provider>
    </div>
  );
}
function DisclosureItem(
  props
) {
  let defaultId =
    useId();
  let id = props.id ||
    defaultId;
  let groupState = React
    .useContext(
      DisclosureGroupStateContext
    );
  let isExpanded =
    groupState
      ? groupState
        .expandedKeys
        .has(id)
      : props.isExpanded;
  let state =
    useDisclosureState({
      ...props,
      isExpanded,
      onExpandedChange(
        isExpanded
      ) {
        if (groupState) {
          groupState
            .toggleKey(
              id
            );
        }
        props
          .onExpandedChange?.(
            isExpanded
          );
      }
    });
  let panelRef = React
    .useRef<
      | HTMLDivElement
      | null
    >(null);
  let triggerRef = React
    .useRef<
      | HTMLButtonElement
      | null
    >(null);
  let isDisabled =
    props.isDisabled ||
    groupState
      ?.isDisabled ||
    false;
  let {
    buttonProps:
      triggerProps,
    panelProps
  } = useDisclosure(
    {
      ...props,
      isExpanded,
      isDisabled
    },
    state,
    panelRef
  );
  let { buttonProps } =
    useButton(
      triggerProps,
      triggerRef
    );
  let {
    isFocusVisible,
    focusProps
  } = useFocusRing();
  return (
    <div className="disclosure">
      <h3>
        <button
          className="trigger"
          ref={triggerRef}
          {...mergeProps(
            buttonProps,
            focusProps
          )}
          style={{
            outline:
              isFocusVisible
                ? '2px solid dodgerblue'
                : 'none'
          }}
        >
          <svg viewBox="0 0 24 24">
            <path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
          </svg>
          {props.title}
        </button>
      </h3>
      <div
        className="panel"
        ref={panelRef}
        {...panelProps}
      >
        <p>
          {props
            .children}
        </p>
      </div>
    </div>
  );
}
Usage#
The following examples show how to use the DisclosureGroup component created in the above example.
<DisclosureGroup>
  <DisclosureItem title="Personal Information">
    Personal information form here.
  </DisclosureItem>
  <DisclosureItem title="Billing Address">
    Billing address form here.
  </DisclosureItem>
</DisclosureGroup><DisclosureGroup>
  <DisclosureItem title="Personal Information">
    Personal information form here.
  </DisclosureItem>
  <DisclosureItem title="Billing Address">
    Billing address form here.
  </DisclosureItem>
</DisclosureGroup><DisclosureGroup>
  <DisclosureItem title="Personal Information">
    Personal
    information form
    here.
  </DisclosureItem>
  <DisclosureItem title="Billing Address">
    Billing address
    form here.
  </DisclosureItem>
</DisclosureGroup>Default expansion#
Which disclosure is expanded by default can be set with the defaultExpandedKeys prop.
<DisclosureGroup defaultExpandedKeys={['billing']}>
  <DisclosureItem id="personal" title="Personal Information">
    Personal information form here.
  </DisclosureItem>
  <DisclosureItem id="billing" title="Billing Address">
    Billing address form here.
  </DisclosureItem>
</DisclosureGroup><DisclosureGroup defaultExpandedKeys={['billing']}>
  <DisclosureItem
    id="personal"
    title="Personal Information"
  >
    Personal information form here.
  </DisclosureItem>
  <DisclosureItem id="billing" title="Billing Address">
    Billing address form here.
  </DisclosureItem>
</DisclosureGroup><DisclosureGroup
  defaultExpandedKeys={[
    'billing'
  ]}
>
  <DisclosureItem
    id="personal"
    title="Personal Information"
  >
    Personal
    information form
    here.
  </DisclosureItem>
  <DisclosureItem
    id="billing"
    title="Billing Address"
  >
    Billing address
    form here.
  </DisclosureItem>
</DisclosureGroup>Controlled expansion#
Expansion can be controlled using the expandedKeys prop, paired with the onExpandedChange event. The onExpandedChange event is fired when one of the disclosures is expanded or collapsed.
function ControlledDisclosureGroup(props) {
  let [expandedKeys, setExpandedKeys] = React.useState(['personal']);
  return (
    <DisclosureGroup
      expandedKeys={expandedKeys}
      onExpandedChange={setExpandedKeys}
    >
      <DisclosureItem id="personal" title="Personal Information">
        Personal information form here.
      </DisclosureItem>
      <DisclosureItem id="billing" title="Billing Address">
        Billing address form here.
      </DisclosureItem>
    </DisclosureGroup>
  );
}
function ControlledDisclosureGroup(props) {
  let [expandedKeys, setExpandedKeys] = React.useState([
    'personal'
  ]);
  return (
    <DisclosureGroup
      expandedKeys={expandedKeys}
      onExpandedChange={setExpandedKeys}
    >
      <DisclosureItem
        id="personal"
        title="Personal Information"
      >
        Personal information form here.
      </DisclosureItem>
      <DisclosureItem id="billing" title="Billing Address">
        Billing address form here.
      </DisclosureItem>
    </DisclosureGroup>
  );
}
function ControlledDisclosureGroup(
  props
) {
  let [
    expandedKeys,
    setExpandedKeys
  ] = React.useState([
    'personal'
  ]);
  return (
    <DisclosureGroup
      expandedKeys={expandedKeys}
      onExpandedChange={setExpandedKeys}
    >
      <DisclosureItem
        id="personal"
        title="Personal Information"
      >
        Personal
        information form
        here.
      </DisclosureItem>
      <DisclosureItem
        id="billing"
        title="Billing Address"
      >
        Billing address
        form here.
      </DisclosureItem>
    </DisclosureGroup>
  );
}
Multiple expanded#
Multiple disclosures can be expanded at the same time by setting the allowsMultipleExpanded prop to true.
<DisclosureGroup allowsMultipleExpanded>
  <DisclosureItem title="Personal Information">
    Personal information form here.
  </DisclosureItem>
  <DisclosureItem title="Billing Address">
    Billing address form here.
  </DisclosureItem>
</DisclosureGroup><DisclosureGroup allowsMultipleExpanded>
  <DisclosureItem title="Personal Information">
    Personal information form here.
  </DisclosureItem>
  <DisclosureItem title="Billing Address">
    Billing address form here.
  </DisclosureItem>
</DisclosureGroup><DisclosureGroup
  allowsMultipleExpanded
>
  <DisclosureItem title="Personal Information">
    Personal
    information form
    here.
  </DisclosureItem>
  <DisclosureItem title="Billing Address">
    Billing address
    form here.
  </DisclosureItem>
</DisclosureGroup>Disabled#
An entire disclosure group can be disabled with the isDisabled prop. This will disable all trigger buttons and prevent the panels from being opened or closed.
<DisclosureGroup isDisabled>
  <DisclosureItem title="Personal Information">
    Personal information form here.
  </DisclosureItem>
  <DisclosureItem title="Billing Address">
    Billing address form here.
  </DisclosureItem>
</DisclosureGroup><DisclosureGroup isDisabled>
  <DisclosureItem title="Personal Information">
    Personal information form here.
  </DisclosureItem>
  <DisclosureItem title="Billing Address">
    Billing address form here.
  </DisclosureItem>
</DisclosureGroup><DisclosureGroup
  isDisabled
>
  <DisclosureItem title="Personal Information">
    Personal
    information form
    here.
  </DisclosureItem>
  <DisclosureItem title="Billing Address">
    Billing address
    form here.
  </DisclosureItem>
</DisclosureGroup>