Skip to main content

Drawer

Displays an overlay area at any side of the screen.

github
View source code

Migration Information

For teams migrating from the V1 to V2 component, please refer to the migration guide for changes to the component.
Component Guide
import { Drawer } from '@uhg-abyss/web/ui/Drawer';
      () => {
      const [isOpen, setIsOpen] = useState(false);
      return (
      <React.Fragment>
      <Drawer
      header="Drawer header"
      isOpen={isOpen}
      onClose={() => setIsOpen(false)}
      >
      Press escape to close the drawer
      </Drawer>
      <Button
      onClick={() => setIsOpen(true)}
      aria-haspopup="dialog"
      >
      Toggle Drawer
      </Button>
      </React.Fragment>
      );
      };

      Opening the drawer​

      There are two methods of controlling the open state of a Drawer: by using the useOverlay hook or by using the isOpen prop.

      useOverlay​

      The useOverlay hook returns an object containing various methods used to control the state of the modal. Each Drawer must be assigned a unique model prop and be a descendent of an OverlayProvider.

      Tip

      If you're encountering issues, ensure your application is properly wrapped in an OverlayProvider and that all other props are being used correctly.

      useState​

      Using the useState hook to set the open state of the drawer.

      Use the header prop to set the header of the drawer. This prop is optional but strongly recommended. It accepts a React node, and is typically a string representing the title of the drawer.

      If not using header, please use ariaLabel to provide a title and keep the component accessible.

      Use the footer prop to add a footer to the drawer. It accepts a React node, typically one or two buttons.

      Info

      If closing the drawer with a button, please refer to our Drawer.Close documentation.

      Closing the drawer​

      Drawer shows a close (X) button in the top right corner by default (to hide it, set hideClose={true}).

      Important

      For proper animation, always use the Drawer.Close sub-component when adding a button for closing the drawer. Direct state manipulation (like setIsOpen(false)) will skip the closing animation.

      Drawer.Close​

      The Drawer.Close sub-component is used to close Drawer with animation, and accepts all the same props as the Button component.

      The only difference is in how the onClick handler works: If your onClick callback returns { preventClose: true }, the drawer will remain open. Otherwise, the drawer will close and play its animation.

      <Drawer.Close
      onClick={() => {
      form.handleSubmit(onSubmit)();
      // If the form is not valid, prevent the drawer from closing
      if (!form.formState.isValid) {
      return { preventClose: true };
      }
      // Otherwise, the drawer will close
      }}
      >
      Submit / Close
      </Drawer.Close>

      closeOnClickOutside​

      Use the closeOnClickOutside prop to control whether a user can dismiss the drawer by clicking on the overlay. The default value is true.

      Passing data​

      External data can be passed into the drawer when using the useOverlay hook. The open and toggle methods returned by the hook accept an object of the following type:

      {
      isOpen?: boolean;
      data?: object;
      }

      The getState method retrieves the state of the drawer as an object of the same type.

      Size​

      Use the size prop to set the width of the drawer. The options are 'sm' or custom.

      Position​

      Use the position prop to set the position of the drawer. The default is 'right'.

      Overflow​

      Overflow is handled within the content of the Drawer. The header and footer, if present, will remain fixed.

      Note

      If no header is provided and hideClose is false, the close button will be fixed to the top right corner of the drawer.

      Responsive visibility​

      Warning

      Avoid hiddenFrom and visibleFrom on Drawer and on the control that opens it. Neither placement behaves the way it does on a presentational component and hiding the control can cause keyboard users to lose their place on the page. Prefer deciding what to render before the drawer is reachable, for example, routing small screens to a different flow.

      On the drawer itself​

      Hiding an open drawer with CSS alone would leave it holding the scroll lock, the focus trap, and the escape-key handler while invisible. Drawer therefore treats the breakpoint as part of its open state: when an open drawer crosses into a hidden range it closes for real, unmounting its content and calling onClose.

      So, onClose fires on a viewport resize with nobody having interacted with the drawer. If your handler does more than set state, such as logging an abandoned flow, discarding a draft, or submitting an answer, it can run when a user resizes the window or rotates their device.

      Opening a drawer that is already in its hidden range does nothing visible and does not call onClose: the drawer never opened, so there is nothing to close. Its isOpen prop stays true, though, so the drawer appears on its own once the viewport returns to a visible range, acting on a click the user made at a width where nothing happened.

      On the control that opens it​

      Hiding the trigger rather than the drawer does not avoid the problem. In fact, it introduces a worse one. A drawer returns focus to the control that opened it, and a hidden control cannot take focus. If a keyboard user opens the drawer and the viewport then crosses the breakpoint so that control is hidden, closing the drawer drops focus onto the page body: the user loses their place and the next Tab starts again from the top of the document.

      Note

      This is not unique to these props. A control that unmounts while its overlay is open loses focus the same way.

      Alert dialog​

      Use the alertDialog prop to set the role of the Drawer to "alertdialog". This is useful if the contents of the drawer require immediate attention, as it will cause assistive technologies to immediately announce the drawer content. The default value is false, which results in a role of "dialog".

      Drawer Props​

      Name
      Type
      DefaultA11yDescription
      alertDialog
      boolean | undefined
      false
      If true, the component will use role="alertdialog" instead of the default role="dialog". This should only be used if the dialog contains an important message and requires immediate attention, as it will cause assistive technologies to immediately announce the dialog content.
      ariaLabel
      string | undefined
      Provides the dialog's accessible name for screen readers when no visible header is rendered.
      ariaLive
      "off" | "assertive" | "polite" | undefined
      Controls how assistive technologies announce updates to the dialog content.
      Useful with disableAriaDescribedBy for dynamic updates where full content re-announcement should be avoided.
      children
      string | number | bigint | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<...> | null | undefined
      The contents of the modal base
      className
      string | undefined
      CSS class name to apply to each element within the component
      closeOnClickOutside
      boolean | undefined
      true
      If true, the modal base will close when the overlay is clicked
      css
      CSSProperties | Partial<Record<"abyss-modal-base-body" | "abyss-modal-base-footer" | "abyss-modal-base-header" | "abyss-modal-base-content-container" | "abyss-modal-base-close-button" | "abyss-modal-base-close-icon" | "abyss-modal-base-overlay" | "abyss-modal-base-header-container", CSSProperties>> | undefined
      Object containing CSS styling to apply; uses the classes listed in the "Integration" tab of the component's documentation
      See CSS Prop Styling for more information
      data-testid
      string | undefined
      Suffix used to create a unique ID used for automated testing
      See Component Testing for more information
      disableAriaDescribedBy
      boolean | undefined
      false
      If true, the dialog will not set an aria-describedby value. Use this only when the dialog content is long or already fully described elsewhere.
      disableDefaultProviderProps
      boolean | undefined
      false
      If set to true, the component will not use the DefaultPropsProvider values.

      If you aren’t using the DefaultPropsProvider, this prop can be ignored.
      footer
      string | number | bigint | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<...> | null | undefined
      If present, the modal base will have a footer
      header
      string | number | bigint | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<...> | null | undefined
      The header of the modal base.
      hiddenFrom
      Breakpoint | undefined
      When defined, the component is hidden once the screen width reaches the specified breakpoint.
      hideClose
      boolean | undefined
      false
      If true, the modal base will not have a close button in the top right corner
      isOpen
      boolean | undefined
      false
      If true, the modal base is open
      model
      string | undefined
      A unique identifier for the modal base; only used when using OverlayProvider
      onClose
      (() => void) | undefined
      Callback function executed when the modal base is closed
      position
      "left" | "right" | undefined
      The position of the Drawer
      size
      string | number | undefined
      'sm'
      The size of the Drawer
      slotProps
      Partial<Record<ToSlotKey<{ overlay: LeafSlotDef<"div", undefined>; contentContainer: LeafSlotDef<"div", undefined>; headerContainer: LeafSlotDef<"div", undefined>; ... 4 more ...; footer: LeafSlotDef<...>; }, "modal-base">, SlotPropsLeafValue>> | undefined
      Props to pass to individual internal slots of the component.
      See slotProps for more information
      visibleFrom
      Breakpoint | undefined
      When defined, the component is hidden until the screen width reaches the specified breakpoint.

      Drawer Classes​

      Class NameDescription
      .abyss-modal-base-bodyModalBase body container
      .abyss-modal-base-close-buttonModalBase close button element
      .abyss-modal-base-close-iconModalBase close button icon element
      .abyss-modal-base-content-containerModalBase content container
      .abyss-modal-base-footerModalBase footer container
      .abyss-modal-base-headerModalBase header element
      .abyss-modal-base-header-containerModalBase header container
      .abyss-modal-base-overlayModalBase overlay element

      Keyboard Interactions​

      KeyDescription
      EscCloses the Drawer.
      TabMoves focus to the next interactive element within the Drawer. Once the last interactive element in the Drawer is reached, pressing Tab again moves focus to the first interactive element within the Drawer.
      Shift + TabMoves focus to the previous interactive element within the Drawer. Once the first interactive element in the Drawer is reached, pressing Shift + Tab again moves focus to the last interactive element within the Drawer.

      Drawer Accessible Example​

      Drawer Content​

      The content included on the Drawer must be accessible.

      Triggering Elements​

      Use the aria-haspopup attribute on buttons or other triggering elements that open content like dialogs, listboxes, trees, menus, grids, etc. Use a corresponding value that indicates what kind of popup will be displayed when the trigger element is activated. In turn, the element that pops up must be of the role indicated. In the case of Drawer, use aria-haspopup="dialog" on the element that opens the drawer. Drawer sets role="dialog" on the dialog container internally.

      See the docs on aria-haspopupopens in a new window for more details.

      Dialog descriptions​

      Drawer sets aria-describedby to the drawer body by default, which works best for short drawers that need a concise description. For long or content-heavy drawers (where the full body would be repeated), set disableAriaDescribedBy to remove the attribute and avoid redundant announcements.

      When your drawer content updates dynamically (for example, multi-step forms or submission results), set ariaLive="polite" so updates are announced without re-announcing the full container content.

      Alert dialog​

      Reduced Motion​

      Animations and transitions that have been changed when a user has prefers-reduced-motion set to reduced:

      • Transition upon expand/collapse is removed

      Component Tokens​

      Tip

      Click on the token row to copy the token to your clipboard.

      Drawer Tokens​

      Token NameValue
      drawer.color.border.separator
      #E5E5E6
      drawer.color.icon.active
      #000000
      drawer.color.icon.hover
      #323334
      drawer.color.icon.rest
      #4B4D4F
      drawer.color.surface.container
      #FFFFFF
      drawer.color.surface.overlay
      rgba(0,0,0,0.4)
      drawer.color.text.heading
      #002677
      drawer.color.text.paragraph
      #4B4D4F
      drawer.border-width.bottom.separator
      1px
      drawer.border-width.top.separator
      1px
      drawer.sizing.all.icon
      24px
      drawer.spacing.gap.horizontal.header
      8px
      drawer.spacing.gap.vertical.content
      16px
      drawer.spacing.padding.bottom.content
      16px
      drawer.spacing.padding.bottom.footer
      16px
      drawer.spacing.padding.bottom.header
      12px
      drawer.spacing.padding.horizontal.content
      16px
      drawer.spacing.padding.horizontal.footer
      16px
      drawer.spacing.padding.horizontal.header
      12px
      drawer.spacing.padding.top.content
      16px
      drawer.spacing.padding.top.header
      12px
      drawer.spacing.padding.vertical.footer
      16px
      drawer.spacing.padding.left.overlay.web
      40px
      drawer.spacing.padding.left.overlay.mobile
      16px
      drawer.spacing.padding.right.overlay.web
      40px
      drawer.spacing.padding.right.overlay.mobile
      16px
      Table of Contents