Popup

GitHubStorybook
yarn add @workday/canvas-kit-react
Sources
GitHubStorybook
Install
yarn add @workday/canvas-kit-react

Usage Guidance

Coming soon!

Examples

The Popup component is a generic Compound Component that is used to build popup UIs that are not already covered by Canvas Kit.

Basic Example

The Popup has no pre-defined behaviors built in, therefore the usePopupModel must always be used to create a new model. This model is then used by all behavior hooks to apply additional popup behaviors to the compound component group. The following example creates a typical popup around a target element and adds useCloseOnOutsideClick, useCloseOnEscape, useInitialFocus, and useReturnFocus behaviors. You can read through the hooks section to learn about all the popup behaviors. For accessibility, these behaviors should be included most of the time.

Initial Focus

If you want focus to move to a specific element when the popup is opened, set the initialFocusRef of the model. Check with accessibility before doing this. The following example sets the focus on the "OK" button with an aria-describedby pointing to the model's id state so screen readers properly announce the message of the popup when focus is changed to the button. By default, focus will be placed on the first focusable element when the popup is opened.

Focus Redirect

Focus management is important to accessibility of popup contents. The following example shows useFocusRedirect being used to manage focus in and out of a Popup. This is very useful for Dialog-style popups. Since Popup.Popper renders contents to the bottom of the document body, aria-owns is used for screen readers that support it. This effectively treats a Popup like it exists in between the buttons while it is opened. Screen readers will navigate the content as if the content was not portalled to the bottom of the document body. Focus redirection tries to treat the Popup as if it were inline to the document. Tabbing out of the Popup will close the Popup and move focus to the next appropriate element.

Note: Safari does not support aria-owns. This means that the contents of the Popup will appears out of order to Safari + VoiceOver users. We render popups at the bottom of the document.body to ensure proper rendering. You could use portal=false on the Popper component, but that would risk incorrect rendering in all browsers.

Focus Trapping

Focus trapping is similar to the Focus Redirect example, but will trap focus inside the popup instead of redirecting focus, it will be trapped inside the Popup. This is most useful for modal dialogs where the modal must be interacted with before normal interaction can continue.

Note: Using focus trapping outside a Modal context can give users a different experience depending on how they interact with your application. Focus trapping will not prevent mouse users from breaking out of a focus trap, nor will it prevent screen reader users from using virtual cursors from breaking out. Modals should use additional techniques to truely "trap" focus into the Popup to provide a consistent experience for all users.

Multiple Popups

If you need multiple Popups within the same component, you can create multiple models and pass a unique model to each Popup. Below is an example of 2 different popups within the same component. Since each Popup gets its own model, each Popup behaves independently. The same technique can be used for nested Popups.

Nested Popups

If you need nested Popups within the same component, you can create multiple models and pass a unique model to each Popup. Popup comes with a Popup.CloseButton that uses a Button and adds props via the usePopupCloseButton hook to ensure the popups hides and focus is returned. The as can be used in a powerful way to do this by using <Popup.CloseButton as={Popup.CloseButton}> which will mix in click handlers from both popups. This is not very intuitive, however. You can create props that merge a click handler for both Popups by using usePopupCloseButton directly. The second parameter is props to be merged which will effectively hide both popups. Focus management is preserved.

Custom Target

It is common to have a custom target for your popup. Use the as prop to use your custom component. The Popup.Target element will add onClick and ref to the provided component. Your provided target component must forward the onClick to an element for the Popup to open. The as will cause Popup.Target to inherit the interface of your custom target component. This means any props your target requires, Popup.Target now also requires. The example below has a MyTarget component that requires a label prop.

Note: If your application needs to programmatically open a Popup without the user interacting with the target button first, you'll also need to use React.forwardRef in your target component. Without this, the Popup will open at the top-left of the window instead of around the target.

Full Screen API

By default, popups are created as children of the document.body element, but the PopupStack supports the Fullscreen API. When fullscreen is entered, the PopupStack will automatically create a new stacking context for all future popups. Any existing popups will disappear, but not be removed. They disappear because the fullscreen API is only showing content within the fullscreen element. There are instances where a popup may not close when fullscreen is exited:

  • The escape key is used to exit fullscreen
  • There is a button to exit fullscreen, but the popup doesn't use useCloseOnOutsideClick

If fullscreen is exited, popups within the fullscreen stacking context are not removed or transferred automatically. If you do not handle this case, the popup may not render correctly. This example shows a popup that closes when fullscreen is entered/exited and another popup that transfers the popup's stack context when entering/exiting fullscreen.

RTL

The Popup component automatically handles right-to-left rendering.

Note: This example shows an inaccessible open card for demonstration purposes.

Component API

Popper

A thin wrapper component around the Popper.js positioning engine. For reference: https://popper.js.org/. Popper also automatically works with the PopupStack system. Popper has no UI and will render any children to the body element and position around a provided anchorElement.

Prefer using Popup.Popper instead. Use this to make Popups that don't utilize a PopupModel or any associate popup hooks.

Note: Popper renders any children to a div element created by the PopupStack. This element is not controlled by React, so any extra element props will not be forwarded. The ref will point to the div element created by the PopupStack, however. In v4, an extra div element was rendered and that's where extra props were spread to. In v5+, you can provide your own element if you wish.

This component is a container component that has no semantic element. It provides a React Context model for all Popup subcomponents. If you manually pass a model to all subcomponents, this container component isn't needed.

// using Popup
<Popup model={model}>
<Popup.Target /> // no model here
</Popup>
// using models on subcomponents
<>
<Popup.Target model={model} />
</>
NameTypeDefaultDescription
children*ReactNodeThe contents of the Popup. Can be `Popup` children or any valid elements.
returnFocusRefRefObject<any> | undefinedOptional reference to an element that should receive focus when a popup is hidden. If left blank, focus will return to the `targetRef`
initialFocusRefRefObject<any> | undefinedOptional reference to an element that should receive focus when a popup is shown. If left blank, focus will be moved to the first focusable element inside the popup.
idstring | undefinedID reference of the list. Children ids can be derived from this id
initialVisibilityVisibility | undefined'hidden'The initial visibility of the disclosed content
model{ state: { stackRef: RefObject<HTMLDivElement>; targetRef: RefObject<HTMLButtonElement>; initialFocusRef: RefObject<any> | undefined; returnFocusRef: RefObject<...> | undefined; placement: Placement; id: string; visibility: Visibility; }; events: { ...; }; } | undefinedOptional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.
elemPropsHook(<TProps>(model: { state: { stackRef: RefObject<HTMLDivElement>; targetRef: RefObject<HTMLButtonElement>; initialFocusRef: RefObject<any> | undefined; returnFocusRef: RefObject<...> | undefined; placement: Placement; id: string; visibility: Visibility; }; events: { ...; }; }, elemProps: TProps) => any) | undefinedOptional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

Popup.Target

A Popup.Target is any element that is meant to show the Popup. The default component rendered by this component is a SecondaryButton element. You can override this by passing the desired component via as. Many examples above use as={DeleteButton}. If you want to render an TertiaryButton instead, use as={TertiaryButton}. The behavior hook used is called usePopupTarget.

const model = usePopupModel();
// using this component
<Popup.Target>Show Popup</PopupTarget>
// using props instead
const popupTargetButtonProps = usePopupTarget(model);
<SecondaryButton {...popupTargetButtonProps}>Show Popup</SecondaryButton>

Popup.Target doesn't provide any styling by default. All styling comes from the default component used, which is SecondaryButton. If you don't want any styling, you can do the following:

<Popup.Target as="button">Open</Popup.Target>

To add your own styling, you could either add a css prop, or make a styled button and pass that styled component via the as prop.

Popup.Popper

A Popup.Popper is a Popper component that is hooked up to the PopupModel automatically. The behavior hook used is called usePopupPopper.

Note: Popup.Popper renders any children to a div element created by the PopupStack. This element is not controlled by React, so any extra element props will not be forwarded. The ref will point to the div element created by the PopupStack, however. If you wish to add extra props to an element, add them to the Popup.Card instead.

NameTypeDefaultDescription
placementPlacement | undefinedbottomThe placement of the `Popper` contents relative to the `anchorElement`. Accepts `auto`, `top`, `right`, `bottom`, or `left`. Each placement can also be modified using any of the following variations: `-start` or `-end`.
popperOptionsPartial<Options> | undefinedThe additional options passed to the Popper's `popper.js` instance.
refReact.Ref<any>Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If `as` is set to an element, it will be that element. If `as` is a component, the reference will be to that component (or element if the component uses `React.forwardRef`).
model{ state: { stackRef: RefObject<HTMLDivElement>; targetRef: RefObject<HTMLButtonElement>; initialFocusRef: RefObject<any> | undefined; returnFocusRef: RefObject<...> | undefined; placement: Placement; id: string; visibility: Visibility; }; events: { ...; }; } | undefinedOptional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.
elemPropsHook(<TProps>(model: { state: { stackRef: RefObject<HTMLDivElement>; targetRef: RefObject<HTMLButtonElement>; initialFocusRef: RefObject<any> | undefined; returnFocusRef: RefObject<...> | undefined; placement: Placement; id: string; visibility: Visibility; }; events: { ...; }; }, elemProps: TProps) => any) | undefinedOptional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.
as"symbol" | "object" | "small" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | ... 156 more ... | React.ComponentType<any>Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using `React.forwardRef` and spread extra props to a root element.

Popup.Card

A Popup.Card is a wrapper around the Card component, but hooked up to a PopupModel. By default, this element has a role=dialog and an aria-labelledby. The behavior hook used is called usePopupCard.

NameTypeDefaultDescription
childrenReactNodeChildren of the Card. Should contain a `<Card.Body>` and an optional `<Card.Heading>`
refReact.Ref<any>Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If `as` is set to an element, it will be that element. If `as` is a component, the reference will be to that component (or element if the component uses `React.forwardRef`).
model{ state: { stackRef: RefObject<HTMLDivElement>; targetRef: RefObject<HTMLButtonElement>; initialFocusRef: RefObject<any> | undefined; returnFocusRef: RefObject<...> | undefined; placement: Placement; id: string; visibility: Visibility; }; events: { ...; }; } | undefinedOptional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.
elemPropsHook(<TProps>(model: { state: { stackRef: RefObject<HTMLDivElement>; targetRef: RefObject<HTMLButtonElement>; initialFocusRef: RefObject<any> | undefined; returnFocusRef: RefObject<...> | undefined; placement: Placement; id: string; visibility: Visibility; }; events: { ...; }; }, elemProps: TProps) => any) | undefinedOptional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.
as"symbol" | "object" | "small" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | ... 156 more ... | React.ComponentType<any>Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using `React.forwardRef` and spread extra props to a root element.

Popup.CloseButton

A Popup.CloseButton is a button that will hide a popup. By default, this is a SecondaryButton component, but as can be used to render any button element (i.e <DeleteButton> or <PrimaryButton>). The behavior hook used is called usePopupCloseButton.

NameTypeDefaultDescription
refReact.Ref<any>Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If `as` is set to an element, it will be that element. If `as` is a component, the reference will be to that component (or element if the component uses `React.forwardRef`).
model{ state: { stackRef: RefObject<HTMLDivElement>; targetRef: RefObject<HTMLButtonElement>; initialFocusRef: RefObject<any> | undefined; returnFocusRef: RefObject<...> | undefined; placement: Placement; id: string; visibility: Visibility; }; events: { ...; }; } | undefinedOptional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.
elemPropsHook(<TProps>(model: { state: { stackRef: RefObject<HTMLDivElement>; targetRef: RefObject<HTMLButtonElement>; initialFocusRef: RefObject<any> | undefined; returnFocusRef: RefObject<...> | undefined; placement: Placement; id: string; visibility: Visibility; }; events: { ...; }; }, elemProps: TProps) => any) | undefinedOptional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.
as"symbol" | "object" | "small" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | ... 156 more ... | React.ComponentType<any>Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using `React.forwardRef` and spread extra props to a root element.

Popup.CloseIcon

A Popup.CloseIcon is an icon button that is the X in the top of a popup. It will hide a popup when clicked. The behavior hook used is called usePopupCloseButton.

NameTypeDefaultDescription
variant"inverse" | undefinedundefinedThe variant of the TertiaryButton.
sizeButtonSizes | undefined'medium'There are three button sizes: `extraSmall`, `small`, and `medium`. If no size is provided, it will default to `medium`.
iconPositionIconPositions | undefined'start'Button icon positions can either be `start` or `end`. If no value is provided, it defaults to `start`.
iconCanvasSystemIcon | undefinedThe icon of the TertiaryButton.
shouldMirrorIconboolean | undefinedfalseIf set to `true`, transform the icon's x-axis to mirror the graphic
allCapsboolean | undefined
themeEmotionCanvasTheme | undefined
refReact.Ref<any>Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If `as` is set to an element, it will be that element. If `as` is a component, the reference will be to that component (or element if the component uses `React.forwardRef`).
model{ state: { stackRef: RefObject<HTMLDivElement>; targetRef: RefObject<HTMLButtonElement>; initialFocusRef: RefObject<any> | undefined; returnFocusRef: RefObject<...> | undefined; placement: Placement; id: string; visibility: Visibility; }; events: { ...; }; } | undefinedOptional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.
elemPropsHook(<TProps>(model: { state: { stackRef: RefObject<HTMLDivElement>; targetRef: RefObject<HTMLButtonElement>; initialFocusRef: RefObject<any> | undefined; returnFocusRef: RefObject<...> | undefined; placement: Placement; id: string; visibility: Visibility; }; events: { ...; }; }, elemProps: TProps) => any) | undefinedOptional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.
as"symbol" | "object" | "small" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | ... 156 more ... | React.ComponentType<any>Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using `React.forwardRef` and spread extra props to a root element.

Popup.Heading

A Popup.Heading is a wrapper around Card.Heading that connect the heading to a PopupModel. It will add an id to the element that match the aria-labelledby that is applied to the Popup.Card element for accessibility. The behavior hook used is called usePopupHeading.

NameTypeDefaultDescription
idstring | undefinedThe id of the Card heading. Tie this to an `aria-labelledby` for accessibility.
refReact.Ref<any>Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If `as` is set to an element, it will be that element. If `as` is a component, the reference will be to that component (or element if the component uses `React.forwardRef`).
model{ state: { stackRef: RefObject<HTMLDivElement>; targetRef: RefObject<HTMLButtonElement>; initialFocusRef: RefObject<any> | undefined; returnFocusRef: RefObject<...> | undefined; placement: Placement; id: string; visibility: Visibility; }; events: { ...; }; } | undefinedOptional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.
elemPropsHook(<TProps>(model: { state: { stackRef: RefObject<HTMLDivElement>; targetRef: RefObject<HTMLButtonElement>; initialFocusRef: RefObject<any> | undefined; returnFocusRef: RefObject<...> | undefined; placement: Placement; id: string; visibility: Visibility; }; events: { ...; }; }, elemProps: TProps) => any) | undefinedOptional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.
as"symbol" | "object" | "small" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | ... 156 more ... | React.ComponentType<any>Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using `React.forwardRef` and spread extra props to a root element.

Popup.Body

A Popup.Body is a thin wrapper around Card.Body and doesn't actually take a model. It adds body styling and nothing else.

NameTypeDefaultDescription
refReact.Ref<any>Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If `as` is set to an element, it will be that element. If `as` is a component, the reference will be to that component (or element if the component uses `React.forwardRef`).
model{ state: { stackRef: RefObject<HTMLDivElement>; targetRef: RefObject<HTMLButtonElement>; initialFocusRef: RefObject<any> | undefined; returnFocusRef: RefObject<...> | undefined; placement: Placement; id: string; visibility: Visibility; }; events: { ...; }; } | undefinedOptional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.
elemPropsHook(<TProps>(model: { state: { stackRef: RefObject<HTMLDivElement>; targetRef: RefObject<HTMLButtonElement>; initialFocusRef: RefObject<any> | undefined; returnFocusRef: RefObject<...> | undefined; placement: Placement; id: string; visibility: Visibility; }; events: { ...; }; }, elemProps: TProps) => any) | undefinedOptional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.
as"symbol" | "object" | "small" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | ... 156 more ... | React.ComponentType<any>Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using `React.forwardRef` and spread extra props to a root element.

Model

The PopupModel contains all state and events needed for Popup behaviors and components. It composes a DisclosureModel for showing and hiding content.

Config

NameTypeDefaultDescription

State

NameTypeDefaultDescription

Events

NameTypeDefaultDescription

Hooks

usePopupStack

const stackRef = usePopupStack(forwardRef?: React.RefObject<HTMLElement>): React.RefObject<HTMLDivElement>

Note: If you're using Popper, you do not need to use this hook directly.

This hook will add the stackRef element to the Popup stack on mount and remove on unmount. If you use Popper, the popper stackRef is automatically added/removed from the Popup stack. The Popup stack is required for proper z-index values to ensure Popups are rendered correct. It is also required for global listeners like click outside or escape key closing a popup. Without the Popup stack, all popups will close rather than only the topmost one.

If forwardRef is provided, it will be the same as stackRef. If forwardRef is not provided, this hook will create one and return it.

This hook should be used by all popups unless using the Popper component.

Example:

const model = usePopupModel();
usePopupStack(model.state.stackRef, model.state.targetRef);
// add some popup functionality
useCloseOnOutsideClick(model);
useCloseOnEscape(model);
return (
<>
<button ref={model.state.targetRef}>Open Popup</button>
{model.state.visibility !== 'hidden'
? ReactDOM.createPortal(<div>Popup Contents</div>, model.state.stackRef.current)
: null}
</>
);

useAssistiveHideSiblings

useAssistiveHideSiblings(model: PopupModel): {}

This hook will hide all sibling elements from assistive technology. Very useful for modal dialogs. This will set aria-hidden for sibling elements of the provided PopupModel's state.stackRef element and restore the previous aria-hidden to each component when the component is unmounted. For example, if added to a Modal component, all children of document.body will have an aria-hidden=true applied except for the provided stackRef element (the Modal). This will effectively hide all content outside the Modal from assistive technology including Web Rotor for VoiceOver for example.

This should be used on popup elements that need to hide content (i.e. Modals).

useBringToTopOnClick

useBringToTopOnClick(model: PopupModel): {}

This hook will bring an element to the top of the stack when any element inside the provided PopupModel's state.stackRef element is clicked. If Popup.Popper was used or PopupStack.add provided an owner, all "child" popups will also be brought to the top. A "child" popup is a Popup that was opened from another Popup. Usually this is a Tooltip or Select component inside something like a Modal.

This should be used on popup elements that are meant to persist (i.e. Windows).

useCloseOnEscape

useCloseOnEscape(model: PopupModel): {}

Registers global detection of the Escape key. It will only call the PopupModel's hide event if the provided model's state.stackRef element is the topmost in the stack.

This should be used with popup elements that are dismissible like Tooltips, Modals, non-modal dialogs, dropdown menus, etc.

useCloseOnOutsideClick

useCloseOnOutsideClick(model: PopupModel): {}

Registers global listener for all clicks. It will only call the PopupModel's hide event if the click happened outside the PopupModel's state.stackRef element and its children and the provided stackRef element is the topmost element with this behavior applied in the stack. Adds a data-behavior-click-outside-close="topmost" attribute to ensure proper functionality.

This should be used with popup elements that are dismissible like Modals, non-modal dialogs, dropdown menus, etc. Tooltips and hierarchical menus should use useAlwaysCloseOnClickOutside instead.

useAlwaysCloseOnOutsideClick

useAlwaysCloseOnOutsideClick(model: PopupModel): {}

Registers global listener for all clicks. It will only call the PopupModel's hide event if the click happened outside the stackRef element and its children regardless of the position in the stack. This is useful for Tooltips or hierarchical menus. Adds a data-behavior-click-outside-close="always" attribute to ensure proper functionality.

This should be used with popup elements that should close no matter their position in the stack (i.e. Tooltips).

useCloseOnFullscreenExit

useCloseOnFullscreenExit(model: PopupModel): {}

Closes the popup when fullscreen is exited. Entering/exiting fullscreen changes the context of the entire screen. This should be added to popup types that are very context sensitive like Tooltips.

useDisableBodyScroll

useDisableBodyScroll(model: PopupModel): {}

Disables body scroll by adding overflow: hidden to the body element. This effectively prevents page scrolling while the popup is visible.

This should be used with popup elements that hide all other content and force the user to accept or dismiss the popup before continuing (i.e. Modals).

useFocusRedirect

useFocusRedirect(model: PopupModel): {}

Manages focus around a popup, treating the popup as if it was part of the DOM where it appears. Popups are typically "portalled" (inserted at the end of document.body) to ensure proper rendering. This violates WCAG Focus Order. This hook helps redirect focus as if the popup element appeared in the DOM. aria-owns might also be used to ensure assistive technology places the popup after the button for virtual cursors. This hook does no provide aria-owns and this must be provided yourself. Requires useReturnFocus to work properly. Works well with useInitialFocus.

This should be used with non-modal dialogs.

useFocusTrap

useFocusTrap(model: PopupModel): {}

"Trap" or "loop" focus within a provided stackRef element. This is required for accessibility on modals. If a keyboard users hits the Tab or Shift + Tab, this will force "looping" of focus. It effectively "hides" outside content from keyboard users. Use an overlay to hide content from mouse users and useAssistiveHideSiblings to hide content from assistive technology users. Works well with useInitialFocus and useReturnFocus.

This should be used on popup elements that need to hide content (i.e. Modals).

useInitialFocus

useInitialFocus(model: PopupModel): {}

Moves focus within the popup when the popup becomes visible. This is useful for keyboard and screen reader users alike. This should be used with useFocusRedirect or useFocusTrap for a complete focus management solution.

This should be used for popups that have focusable elements inside, like Modals, non-modal dialogs, menus, etc.

useReturnFocus

useReturnFocus(model: PopupModel): {}

Returns focus to the target element when the popup is hidden. This works well with useInitialFocus. This should be used with useFocusRedirect or useFocusTrap for a complete focus management solution.

This should ble used on popup elements that use useInitialFocus.

useTransferOnFullscreenEnter

useTransferOnFullscreenEnter(model: PopupModel): {}

Makes the popup transfer to the fullscreen element when fullscreen is entered. Without this, the popup would seem to disappear because the popup container element is not a child of the fullscreen element.

Don't use this in conjunction with a hook that will close the popup when entering fullscreen. Doing so would open the popup when the intention was to close it.

useTransferOnFullscreenExit

useTransferOnFullscreenExit(model: PopupModel): {}

Makes the popup transfer to fullscreen when fullscreen is exited. Without this hook, the popup would not operate correctly with other popups on the screen.

Don't use this in conjunction with a hook that will close the popup when exiting fullscreen. Doing so would open the popup when the intention was to close it.

usePopupPopper

usePopupPopper(model: PopupModel, elemProps: {}, ref: React.Ref<any>): {}

Adds the necessary props to a Popper component. Used by the Popup.Popper subcomponent.

usePopupCard

usePopupCard(model: PopupModel, elemProps: {}, ref: React.Ref<any>): {}

Adds the necessary props to a Card component. Used by the Popup.Card subcomponent.

usePopupCloseButton

usePopupCloseButton(model: PopupModel, elemProps: {}, ref: React.Ref<any>): {}

Adds the necessary props to a close button component. Used by the Popup.CloseButton subcomponent and Popup.CloseIcon subcomponent.

usePopupHeading

usePopupHeading(model: PopupModel, elemProps: {}, ref: React.Ref<any>): {}

Adds the necessary props to a Heading component. Used by the Popup.Heading subcomponent.

usePopupTarget

usePopupTarget(model: PopupModel, elemProps: {}, ref: React.Ref<any>): {}

Adds the necessary props to a Target component. Used by the Popup.Target subcomponent.

Specifications

GivenWhenThen
the Basic example is rendered
  • the "Delete Item" button is clicked
  • it should show the popup
the Basic example is rendered
  • the "Delete Item" button is clicked
  • it should not have any axe errors
the Basic example is rendered
  • the "Delete Item" button is clicked
  • popup should have a role of dialog
the Basic example is rendered
  • the "Delete Item" button is clicked
  • popup should have an aria-labelledby attribute when a heading is provided
the Basic example is rendered
  • the "Delete Item" button is clicked
  • popup should be labelled by the heading
the Basic example is rendered
  • the "Delete Item" button is clicked
  • AND THEN the close button is clicked
  • it should hide the popup
the Basic example is rendered
  • the "Delete Item" button is clicked
  • AND THEN the escape key is pressed
  • it should hide the popup
the Basic example is rendered
  • the "Delete Item" button is clicked
  • AND THEN outside the popup is clicked
  • it should close the popup
the MultiplePopups example is rendered
  • Open Popup 1 button is clicked
  • it should open Popup 1
the MultiplePopups example is rendered
  • Open Popup 1 button is clicked
  • AND THEN Open Popup 2 button is clicked
  • it should open Popup 2
the MultiplePopups example is rendered
  • Open Popup 1 button is clicked
  • AND THEN Open Popup 2 button is clicked
  • it should close Popup 1
the PopupWithNonHidablePopup example is rendered
  • Open Popup 1 button is clicked
  • it should open Popup 1
the PopupWithNonHidablePopup example is rendered
  • Open Popup 1 button is clicked
  • AND THEN Open Popup 2 button is clicked
  • it should open Popup 2 in front of Popup 1
the PopupWithNonHidablePopup example is rendered
  • Open Popup 1 button is clicked
  • AND THEN Open Popup 2 button is clicked
  • AND THEN the close button in Popup 2 is clicked
  • it should close Popup 2
the PopupWithNonHidablePopup example is rendered
  • Open Popup 1 button is clicked
  • AND THEN Open Popup 2 button is clicked
  • AND THEN the close button in Popup 2 is clicked
  • it should not close Popup 1
the MixedPopupTypes story is rendered
    • it should start with Window 3 stacked on top of 3 Windows
    the MixedPopupTypes story is rendered
    • Window 2 is clicked
    • it should place Window 2 above others
    the MixedPopupTypes story is rendered
    • "Contents of Window 1" text is hovered
    • it should place Window 1 Tooltip above all other stacked UI elements
    the MixedPopupTypes story is rendered
    • "Delete Item" button is clicked
    • it should open "Delete Item" popup
    the MixedPopupTypes story is rendered
    • "Delete Item" button is clicked
    • AND THEN Window 2 is clicked
    • it should close "Delete Item" popup
    the MixedPopupTypes story is rendered
    • "Delete Item" button is clicked
    • AND THEN Window 2 is clicked
    • it should place Window 2 above others
    the MixedPopupTypes story is rendered
    • "Delete Item" button is clicked
    • AND THEN Window 2 Tooltip is hovered
    • AND THEN "Contents of Window 2" text is clicked
    • it should close "Delete Item" popup
    the MixedPopupTypes story is rendered
    • "Delete Item" button is clicked
    • AND THEN Window 2 Tooltip is hovered
    • AND THEN "Contents of Window 2" text is clicked
    • it should place Window 2 above others
    the MixedPopupTypes story is rendered
    • "Delete Item" button is clicked
    • AND THEN Window 2 Tooltip is hovered
    • AND THEN the Escape key is pressed
    • it should close the Tooltip
    the MixedPopupTypes story is rendered
    • "Delete Item" button is clicked
    • AND THEN Window 2 Tooltip is hovered
    • AND THEN the Escape key is pressed
    • it should not close the "Delete Item" popup
    the MixedPopupTypes story is rendered
    • "Delete Item" button is clicked
    • AND THEN Window 2 Tooltip is hovered
    • AND THEN the Escape key is pressed
    • AND THEN the Escape key is pressed again
    • it should close the "Delete Item" popup
    the MixedPopupTypes story is rendered
    • "Delete Item" button is clicked
    • AND THEN an area outside popups is clicked
    • it should close "Delete Item" popup
    the MixedPopupTypes story is rendered
    • "Delete Item" button is clicked
    • AND THEN the Escape key is pressed
    • it should close "Delete Item" popup
    the MixedPopupTypes story is rendered
    • "Delete Item" button is clicked
    • AND THEN the "Delete" button is clicked
    • it should open the "Really Delete Item" popup
    the MixedPopupTypes story is rendered
    • "Delete Item" button is clicked
    • AND THEN the "Delete" button is clicked
    • AND THEN "Contents of Window 2" text is focused
    • it should open the tooltip
    the MixedPopupTypes story is rendered
    • "Delete Item" button is clicked
    • AND THEN the "Delete" button is clicked
    • AND THEN "Contents of Window 2" text is focused
    • AND THEN an area outside popups is clicked
    • it should close the tooltip
    the MixedPopupTypes story is rendered
    • "Delete Item" button is clicked
    • AND THEN the "Delete" button is clicked
    • AND THEN "Contents of Window 2" text is focused
    • AND THEN an area outside popups is clicked
    • it should close the "Really Delete Item" popup
    the MixedPopupTypes story is rendered
    • "Delete Item" button is clicked
    • AND THEN the "Delete" button is clicked
    • AND THEN "Contents of Window 2" text is focused
    • AND THEN an area outside popups is clicked
    • it should NOT close the "Delete Item" popup
    the CustomTarget example is rendered
    • the "Open" button is clicked
    • it should show the popup
    the CustomTarget example is rendered
    • the "Open" button is clicked
    • AND THEN the "Close" button is clicked
    • it should hide the popup
    the CustomTarget example is rendered
    • the "Open" button is clicked
    • AND THEN the "Close" button is clicked
    • it should move focus back to the "Open" button
    the FocusRedirect example is rendered
    • the "Delete Item" button is clicked
    • it should show the popup
    the FocusRedirect example is rendered
    • the "Delete Item" button is clicked
    • AND THEN the "Delete" button has focus and the tab key is pressed
    • it should hide the popup
    the FocusRedirect example is rendered
    • the "Delete Item" button is clicked
    • AND THEN the "Delete" button has focus and the tab key is pressed
    • it should redirect focus to the "Next Focusable Button" button
    Source: Popup.spec.ts

    Can't Find What You Need?

    Check out our FAQ section which may help you find the information you're looking for.

    FAQ Section