Back to React Spectrum

useDisclosure

packages/react-aria/docs/disclosure/useDisclosure.mdx

2022-12-1610.7 KB
Original Source

{/* Copyright 2024 Adobe. All rights reserved. This file is licensed to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */}

import {Layout} from '@react-spectrum/docs'; export default Layout;

import docs from 'docs:@react-aria/disclosure'; import utilsDocs from 'docs:@react-aria/utils'; import statelyDocs from 'docs:@react-stately/disclosure'; import {HeaderInfo, FunctionAPI, TypeContext, InterfaceType, TypeLink, PageDescription} from '@react-spectrum/docs'; import {Keyboard} from '@react-spectrum/text'; import packageData from '@react-aria/disclosure/package.json'; import ChevronRight from '@spectrum-icons/workflow/ChevronRight'; import Anatomy from './anatomy.svg';


category: Navigation keywords: [disclosure, collapse, expand, aria]

useDisclosure

<PageDescription>{docs.exports.useDisclosure.description}</PageDescription>

<HeaderInfo packageData={packageData} componentNames={['useDisclosure']} sourceData={[ {type: 'W3C', url: 'https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/'} ]} />

API

<FunctionAPI function={docs.exports.useDisclosure} links={docs.links} />

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

<Anatomy />

A disclosure consists of a trigger button and a panel. Clicking on or pressing <Keyboard>Enter</Keyboard> or <Keyboard>Space</Keyboard> while the trigger button is focused toggles the visibility of the panel.

useDisclosure returns props to spread onto the trigger button and panel.

<TypeContext.Provider value={docs.links}> <InterfaceType properties={docs.links[docs.exports.useDisclosure.return.id].properties} /> </TypeContext.Provider>

State is managed by the <TypeLink links={statelyDocs.links} type={statelyDocs.exports.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.

tsx
import {useDisclosureState} from '@react-stately/disclosure';
import {useDisclosure} from '@react-aria/disclosure';
import {useButton} from '@react-aria/button';
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>
<details> <summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary>
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;
}
</details>

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.

tsx
<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.

tsx
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.

tsx
<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.

tsx
import {useDisclosureGroupState} from '@react-stately/disclosure';
import {useId} from '@react-aria/utils';

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.

tsx
<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.

tsx
<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.

tsx
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.

tsx
<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.

tsx
<DisclosureGroup isDisabled>
  <DisclosureItem title="Personal Information">
    Personal information form here.
  </DisclosureItem>
  <DisclosureItem title="Billing Address">
    Billing address form here.
  </DisclosureItem>
</DisclosureGroup>