Workday Canvas

Popup

Custom popups communicate relevant and timely information to users in response to user action or through system-generated messages.

v14.2.34
Install
yarn add @workday/canvas-kit-react

Anatomy

Image of a pop container with annotation markers.

  1. Title (Optional): Titles should display the title of the content or dialog.
  2. Content: Popups contain different types of content. Typical types of content include alerts and dialogs.
  3. Buttons(Optional): When there is a user action, use the action bar. When displaying informational content, use in-line buttons.
  4. Close “X” Icon (Optional): Users are able to intentionally dismiss a popup.

Usage Guidance

Popup components are generally used in place of Non-Modal Dialogs. Because Non-Modal Dialogs only minimally obstruct the page, they are ideal for drawing attention to optional, non-critical information or new features while keeping page content still visible. Popups appear within the context of a page and do not interrupt normal workflow.

When to use

  • Use Popups when needing to customize a popup element beyond the offerings of other popup components such as a Modal, Tooltip, etc.
  • Do make Popups easily dismissible in context of the trigger element.
  • The popup component is used to display content that doesn’t fit the use cases of more specific notification components such as Tooltips, Modals, Dropdown menus, etc.
  • Popups can be used to display confirmation messages, validate user inputs, or display short informational content in the context of a user action.

When to Use Something Else

  • Do not use Popups to display dense information, such as Tables or Multi-View Containers.
  • Popups are easy to dismiss. Consider using a Modal if you require more user attention or interactive form components in your popup.
  • Consider a Toast if you are communicating status or confirmation of the application process to the user.
  • Consider a Menu if the input is a single selection of options.
  • Use a Tooltip to add context a button, link, to other element.
  • See Error and Alert Notifications for more information on types of notifications and their use cases.

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.

import {DeleteButton} from '@workday/canvas-kit-react/button';
import {
  Popup,
  usePopupModel,
  useCloseOnEscape,
  useCloseOnOutsideClick,
  useInitialFocus,
  useReturnFocus,
} from '@workday/canvas-kit-react/popup';
import {Box, Flex} from '@workday/canvas-kit-react/layout';

export default () => {
  const model = usePopupModel();

  useCloseOnOutsideClick(model);
  useCloseOnEscape(model);
  useInitialFocus(model);
  useReturnFocus(model);

  const handleDelete = () => {
    console.log('Delete Item');
  };

  return (
    <Popup model={model}>
      <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>
      <Popup.Popper placement="top">
        <Popup.Card width={400}>
          <Popup.CloseIcon aria-label="Close" />
          <Popup.Heading>Delete Item</Popup.Heading>
          <Popup.Body>
            <Box as="p" marginY="zero">
              Are you sure you'd like to delete the item titled 'My Item'?
            </Box>
          </Popup.Body>
          <Flex gap="s" padding="xxs">
            <Popup.CloseButton as={DeleteButton} onClick={handleDelete}>
              Delete
            </Popup.CloseButton>
            <Popup.CloseButton>Cancel</Popup.CloseButton>
          </Flex>
        </Popup.Card>
      </Popup.Popper>
    </Popup>
  );
};

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.

import React from 'react';

import {
  useCloseOnEscape,
  useCloseOnOutsideClick,
  Popup,
  usePopupModel,
  useInitialFocus,
  useReturnFocus,
} from '@workday/canvas-kit-react/popup';
import {Box, Flex} from '@workday/canvas-kit-react/layout';

export default () => {
  const initialFocusRef = React.useRef(null);
  const model = usePopupModel({
    initialFocusRef,
  });

  useCloseOnOutsideClick(model);
  useCloseOnEscape(model);
  useInitialFocus(model);
  useReturnFocus(model);

  return (
    <Popup model={model}>
      <Popup.Target>Send Message</Popup.Target>
      <Popup.Popper placement={'bottom'}>
        <Popup.Card width={400}>
          <Popup.CloseIcon aria-label="Close" />
          <Popup.Heading>Confirmation</Popup.Heading>
          <Popup.Body>
            <Box as="p" marginY="zero" id="popup-message">
              Your message has been sent!
            </Box>
          </Popup.Body>
          <Flex gap="s" padding="xxs">
            <Popup.CloseButton ref={initialFocusRef} aria-describedby="popup-message">
              OK
            </Popup.CloseButton>
          </Flex>
        </Popup.Card>
      </Popup.Popper>
    </Popup>
  );
};

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.

import * as React from 'react';

import {DeleteButton, SecondaryButton} from '@workday/canvas-kit-react/button';
import {
  Popup,
  useCloseOnEscape,
  useCloseOnOutsideClick,
  useInitialFocus,
  useReturnFocus,
  useFocusRedirect,
  usePopupModel,
} from '@workday/canvas-kit-react/popup';
import {Box, Flex} from '@workday/canvas-kit-react/layout';

export default () => {
  const model = usePopupModel();

  useCloseOnOutsideClick(model);
  useCloseOnEscape(model);
  useInitialFocus(model);
  useReturnFocus(model);
  useFocusRedirect(model);

  const handleDelete = () => {
    console.log('Delete Item');
  };

  const popupId = 'popup-test-id';
  const visible = model.state.visibility !== 'hidden';
  React.useLayoutEffect(() => {
    if (visible && model.state.stackRef.current) {
      model.state.stackRef.current.setAttribute('id', popupId);
    }
  }, [model.state.stackRef, visible]);

  return (
    <Popup model={model}>
      <Flex gap="s">
        <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>
        <div aria-owns={popupId} style={{position: 'absolute'}} />
        <Popup.Popper>
          <Popup.Card width={400}>
            <Popup.CloseIcon aria-label="Close" />
            <Popup.Heading>Delete Item</Popup.Heading>
            <Popup.Body>
              <Box as="p" marginY="zero">
                Are you sure you'd like to delete the item titled 'My Item'?
              </Box>
            </Popup.Body>
            <Flex gap="s" padding="xxs">
              <Popup.CloseButton as={DeleteButton} onClick={handleDelete}>
                Delete
              </Popup.CloseButton>
              {/* Disabled elements should not be focusable and focus should move to the next focusable element */}
              <Popup.CloseButton disabled>Cancel</Popup.CloseButton>
            </Flex>
          </Popup.Card>
        </Popup.Popper>
        <SecondaryButton>Next Focusable Button</SecondaryButton>
        <SecondaryButton>Focusable Button After Popup</SecondaryButton>
      </Flex>
    </Popup>
  );
};

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.

import * as React from 'react';

import {DeleteButton, SecondaryButton} from '@workday/canvas-kit-react/button';
import {
  Popup,
  useCloseOnEscape,
  useCloseOnOutsideClick,
  useFocusTrap,
  useInitialFocus,
  useReturnFocus,
  usePopupModel,
} from '@workday/canvas-kit-react/popup';
import {Box, Flex} from '@workday/canvas-kit-react/layout';

export default () => {
  const model = usePopupModel();

  useCloseOnOutsideClick(model);
  useCloseOnEscape(model);
  useInitialFocus(model);
  useReturnFocus(model);
  useFocusTrap(model);

  const handleDelete = () => {
    console.log('Delete Item');
  };

  const popupId = 'popup-test-id';
  const visible = model.state.visibility !== 'hidden';
  React.useLayoutEffect(() => {
    if (visible && model.state.stackRef.current) {
      model.state.stackRef.current.setAttribute('id', popupId);
    }
  }, [model.state.stackRef, visible]);

  return (
    <Popup model={model}>
      <Flex gap="s">
        <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>
        <div aria-owns={popupId} style={{position: 'absolute'}} />
        <Popup.Popper>
          <Popup.Card width={400}>
            <Popup.CloseIcon aria-label="Close" />
            <Popup.Heading>Delete Item</Popup.Heading>
            <Popup.Body>
              <Box as="p" marginY="zero">
                Are you sure you'd like to delete the item titled 'My Item'?
              </Box>
            </Popup.Body>
            <Flex gap="s" padding="xxs">
              <Popup.CloseButton as={DeleteButton} onClick={handleDelete}>
                Delete
              </Popup.CloseButton>
              <Popup.CloseButton>Cancel</Popup.CloseButton>
            </Flex>
          </Popup.Card>
        </Popup.Popper>
        <SecondaryButton>Next Focusable Button</SecondaryButton>
        <SecondaryButton>Focusable Button After Popup</SecondaryButton>
      </Flex>
    </Popup>
  );
};

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.

import {
  Popup,
  useCloseOnOutsideClick,
  useCloseOnEscape,
  usePopupModel,
} from '@workday/canvas-kit-react/popup';
import {Flex} from '@workday/canvas-kit-react/layout';

export default () => {
  const popup1 = usePopupModel();
  const popup2 = usePopupModel();

  useCloseOnOutsideClick(popup1);
  useCloseOnEscape(popup1);

  useCloseOnOutsideClick(popup2);
  useCloseOnEscape(popup2);

  return (
    <Flex gap="s">
      <Popup model={popup1}>
        <Popup.Target>Open Popup 1</Popup.Target>
        <Popup.Popper>
          <Popup.Card aria-label="Popup 1">
            <Popup.CloseIcon aria-label="Close" size="small" />
            <Popup.Body>
              <p>Contents of Popup 1</p>
            </Popup.Body>
          </Popup.Card>
        </Popup.Popper>
      </Popup>
      <Popup model={popup2}>
        <Popup.Target>Open Popup 2</Popup.Target>
        <Popup.Popper>
          <Popup.Card aria-label="Popup 2">
            <Popup.CloseIcon aria-label="Close" size="small" />
            <Popup.Body>
              <p>Contents of Popup 2</p>
            </Popup.Body>
          </Popup.Card>
        </Popup.Popper>
      </Popup>
    </Flex>
  );
};

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.

import * as React from 'react';

import {
  Popup,
  useCloseOnOutsideClick,
  useCloseOnEscape,
  usePopupModel,
  usePopupCloseButton,
  useInitialFocus,
  useReturnFocus,
} from '@workday/canvas-kit-react/popup';
import {SecondaryButton} from '@workday/canvas-kit-react/button';
import {Flex} from '@workday/canvas-kit-react/layout';

export default () => {
  const popup1 = usePopupModel();
  const popup2 = usePopupModel();

  useCloseOnOutsideClick(popup1);
  useCloseOnEscape(popup1);
  useInitialFocus(popup1);
  useReturnFocus(popup1);

  useCloseOnOutsideClick(popup2);
  useCloseOnEscape(popup2);
  useInitialFocus(popup2);
  useReturnFocus(popup2);

  const closeBothProps = usePopupCloseButton(popup1, usePopupCloseButton(popup2));

  return (
    <>
      <Popup model={popup1}>
        <Popup.Target>Open Popup 1</Popup.Target>
        <Popup.Popper>
          <Popup.Card aria-label="Popup 1">
            <Popup.CloseIcon aria-label="Close" size="small" />
            <Popup.Body>
              <p style={{marginTop: 0, marginBottom: 0}}>Contents of Popup 1</p>
            </Popup.Body>
            <Flex gap="s" padding="xxs">
              <Popup model={popup2}>
                <Popup.Target>Open Popup 2</Popup.Target>
                <Popup.Popper>
                  <Popup.Card aria-label="Popup 2">
                    <Popup.CloseIcon aria-label="Close" size="small" />
                    <Popup.Body>
                      <p style={{marginTop: 0, marginBottom: 0}}>Contents of Popup 2</p>
                    </Popup.Body>
                    <Flex gap="s" padding="xxs">
                      <Popup.CloseButton as={Popup.CloseButton} model={popup1}>
                        Close Both (as)
                      </Popup.CloseButton>
                      <SecondaryButton {...closeBothProps}>Close Both (props)</SecondaryButton>
                    </Flex>
                  </Popup.Card>
                </Popup.Popper>
              </Popup>
            </Flex>
          </Popup.Card>
        </Popup.Popper>
      </Popup>
    </>
  );
};

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.

import React from 'react';

import {
  Popup,
  usePopupModel,
  useCloseOnEscape,
  useCloseOnOutsideClick,
  useInitialFocus,
  useReturnFocus,
} from '@workday/canvas-kit-react/popup';

interface MyTargetProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  label: string;
}

const MyTarget = React.forwardRef<HTMLButtonElement, MyTargetProps>(({label, ...props}, ref) => {
  return (
    <button {...props} ref={ref}>
      {label}
    </button>
  );
});

export default () => {
  const model = usePopupModel();

  useCloseOnOutsideClick(model);
  useCloseOnEscape(model);
  useInitialFocus(model);
  useReturnFocus(model);

  return (
    <Popup model={model}>
      <Popup.Target as={MyTarget} label="Open" />
      <Popup.Popper>
        <Popup.Card>
          <Popup.CloseIcon aria-label="Close" />
          <Popup.Heading>Popup</Popup.Heading>
          <Popup.Body>Contents</Popup.Body>
        </Popup.Card>
      </Popup.Popper>
    </Popup>
  );
};

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.

import * as React from 'react';

import {SecondaryButton} from '@workday/canvas-kit-react/button';
import {
  Popup,
  useCloseOnEscape,
  useCloseOnOutsideClick,
  useFocusTrap,
  useInitialFocus,
  useReturnFocus,
  usePopupModel,
  useCloseOnFullscreenExit,
  useTransferOnFullscreenExit,
  useTransferOnFullscreenEnter,
} from '@workday/canvas-kit-react/popup';
import {Flex} from '@workday/canvas-kit-react/layout';
import {useIsFullscreen} from '@workday/canvas-kit-react/common';
import screenfull from 'screenfull';

const SelfClosePopup = () => {
  const model = usePopupModel();

  useCloseOnOutsideClick(model);
  useCloseOnEscape(model);
  useInitialFocus(model);
  useReturnFocus(model);
  useFocusTrap(model);
  useCloseOnFullscreenExit(model);

  return (
    <Popup model={model}>
      <Popup.Target>Open Self-close Popup</Popup.Target>
      <Popup.Popper>
        <Popup.Card width={400} padding="s">
          <Popup.CloseIcon aria-label="Close" />
          <Popup.Heading>Self-close Popup</Popup.Heading>
          <Popup.Body>
            <p>
              When in fullscreen, the escape key will be highjacked by the browser to exit
              fullscreen and <code>useCloseOnEscape</code> hook will not receive the escape key. To
              close when fullscreen is exited, use the <code>useCloseOnFullscreenExit</code> hook.
            </p>
          </Popup.Body>
          <Popup.CloseButton>Close</Popup.CloseButton>
        </Popup.Card>
      </Popup.Popper>
    </Popup>
  );
};

const TransferClosePopup = () => {
  const model = usePopupModel();

  useCloseOnEscape(model);
  useInitialFocus(model);
  useReturnFocus(model);
  useFocusTrap(model);
  useTransferOnFullscreenEnter(model);
  useTransferOnFullscreenExit(model);

  return (
    <Popup model={model}>
      <Popup.Target>Open Transfer Popup</Popup.Target>
      <Popup.Popper>
        <Popup.Card width={400} padding="s">
          <Popup.CloseIcon aria-label="Close" />
          <Popup.Heading>Transfer Popup</Popup.Heading>
          <Popup.Body>
            <p>
              When in fullscreen, the escape key will be highjacked by the browser to exit
              fullscreen and <code>useCloseOnEscape</code> hook will not receive the escape key. To
              close when fullscreen is exited, use the <code>useTransferOnFullscreenExit</code>{' '}
              hook.
            </p>
          </Popup.Body>
          <Popup.CloseButton>Close</Popup.CloseButton>
        </Popup.Card>
      </Popup.Popper>
    </Popup>
  );
};

export default () => {
  // you could make this a hook depending on which fullscreen library your application uses
  const fullscreenElementRef = React.useRef<HTMLDivElement>();
  const isFullscreen = useIsFullscreen();

  const enterFullScreen = () => {
    screenfull.request(fullscreenElementRef.current);
  };

  const exitFullscreen = () => {
    screenfull.exit();
  };

  return (
    <>
      <SecondaryButton onClick={enterFullScreen}>Open Fullscreen</SecondaryButton>
      <Flex
        ref={fullscreenElementRef}
        alignItems="center"
        justifyContent="center"
        background="white"
      >
        <Flex gap="s">
          <SelfClosePopup />
          <TransferClosePopup />
          {isFullscreen ? (
            <SecondaryButton onClick={exitFullscreen}>Exit fullscreen</SecondaryButton>
          ) : null}
        </Flex>
      </Flex>
    </>
  );
};

Opening an External Window

A popup can open an external window. This isn’t supported directly. The Popup.Popper subcomponent is replaced with a custom subcomponent that connects to the Popup model and controls the lifecycle of the extenal window. Be sure to connect the unload event of both the parent window and the external child window to the lifecycle of the Popup model to prevent memory leaks or zombie windows.

Popup that opens a new Operating System Window

Popup visibility: hidden

import React from 'react';
import ReactDOM from 'react-dom';

import {system} from '@workday/canvas-tokens-web';
import {createStyles} from '@workday/canvas-kit-styling';
import {infoIcon} from '@workday/canvas-system-icons-web';

import {
  CanvasProvider,
  ContentDirection,
  createSubcomponent,
  PartialEmotionCanvasTheme,
  useMount,
  useTheme,
} from '@workday/canvas-kit-react/common';
import {Tooltip} from '@workday/canvas-kit-react/tooltip';
import {Popup, usePopupModel} from '@workday/canvas-kit-react/popup';
import {SecondaryButton} from '@workday/canvas-kit-react/button';
import {Flex} from '@workday/canvas-kit-react/layout';

const mainContentStyles = createStyles({
  padding: system.space.x4,
});

export interface ExternalWindowPortalProps {
  /**
   * Child components of WindowPortal
   */
  children: React.ReactNode;
  /**
   * Callback to close the popup
   */
  onWindowClose?: () => void;
  /**
   * Width of the popup window
   */
  width?: number;
  /**
   * Height of the popup window
   */
  height?: number;
  /**
   * The name of the popup window. If another popup opens with the same name, that instance will
   * be reused. Use caution with setting this value
   */
  target?: string;
}

async function copyAssets(sourceDoc: Document, targetDoc: Document) {
  for (const font of (sourceDoc as any).fonts.values()) {
    (targetDoc as any).fonts.add(font);

    font.load();
  }

  await (targetDoc as any).fonts.ready;

  // The current ES lib version doesn't include iterable interfaces, so we cast as an iterable
  for (const styleSheet of sourceDoc.styleSheets as StyleSheetList & Iterable<CSSStyleSheet>) {
    if (styleSheet.cssRules) {
      // text based styles
      const styleEl = targetDoc.createElement('style');
      for (const cssRule of styleSheet.cssRules as CSSRuleList & Iterable<CSSRule>) {
        styleEl.appendChild(targetDoc.createTextNode(cssRule.cssText));
      }
      targetDoc.head.appendChild(styleEl);
    } else if (styleSheet.href) {
      // link based styles
      const linkEl = targetDoc.createElement('link');

      linkEl.rel = 'stylesheet';
      linkEl.href = styleSheet.href;
      targetDoc.head.appendChild(linkEl);
    }
  }
}

const ExternalWindowPortal = ({
  children,
  width = 300,
  height = 500,
  target = '',
  onWindowClose,
}: ExternalWindowPortalProps) => {
  const [portalElement, setPortalElement] = React.useState<HTMLDivElement | null>(null);

  useMount(() => {
    const newWindow = window.open(
      '', // url
      target,
      `width=${width},height=${height},left=100,top=100,popup=true`
    );

    if (newWindow) {
      // copy fonts and styles
      copyAssets(document, newWindow.document);

      const element = newWindow.document.createElement('div');
      newWindow.document.body.appendChild(element);
      setPortalElement(element);
    } else {
      onWindowClose();
    }

    const closeWindow = event => {
      onWindowClose();
    };

    window.addEventListener('unload', closeWindow);
    newWindow?.addEventListener('unload', closeWindow);

    return () => {
      window.removeEventListener('unload', closeWindow);
      newWindow?.removeEventListener('unload', closeWindow);
      newWindow?.close();
    };
  });

  if (!portalElement) {
    return null;
  }

  return ReactDOM.createPortal(<CanvasProvider>{children}</CanvasProvider>, portalElement);
};

const PopupExternalWindow = createSubcomponent()({
  displayName: 'Popup.ExternalWindow',
  modelHook: usePopupModel,
})<ExternalWindowPortalProps>(({children, ...elemProps}, Element, model) => {
  if (model.state.visibility === 'visible') {
    return (
      <ExternalWindowPortal onWindowClose={model.events.hide} {...elemProps}>
        {children}
      </ExternalWindowPortal>
    );
  }

  return null;
});

export default () => {
  // useTheme is filling in the Canvas theme object if any keys are missing
  const canvasTheme: PartialEmotionCanvasTheme = useTheme({
    canvas: {
      // Switch to `ContentDirection.RTL` to change direction
      direction: ContentDirection.LTR,
    },
  });

  const model = usePopupModel();

  return (
    <CanvasProvider theme={canvasTheme}>
      <>
        <main className={mainContentStyles}>
          <p>Popup that opens a new Operating System Window</p>
          <Popup model={model}>
            <Tooltip title="Open External Window Tooltip">
              <Popup.Target>Open External Window</Popup.Target>
            </Tooltip>
            <PopupExternalWindow>
              <p>External Window Contents! Mouse over the info icon to get a tooltip</p>
              <Flex gap="s">
                <Tooltip title="More information">
                  <SecondaryButton icon={infoIcon} />
                </Tooltip>
                <Popup.CloseButton>Close Window</Popup.CloseButton>
              </Flex>
            </PopupExternalWindow>
          </Popup>
          <p>Popup visibility: {model.state.visibility}</p>
        </main>
      </>
    </CanvasProvider>
  );
};

RTL

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

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

import React from 'react';

import {SecondaryButton, DeleteButton} from '@workday/canvas-kit-react/button';
import {CanvasProvider} from '@workday/canvas-kit-react/common';
import {Popup} from '@workday/canvas-kit-react/popup';
import {Box, Flex} from '@workday/canvas-kit-react/layout';

export default () => {
  return (
    <CanvasProvider dir="rtl">
      <Popup.Card width={400}>
        <Popup.CloseIcon aria-label="סגור" />
        <Popup.Heading>למחוק פריט</Popup.Heading>
        <Popup.Body>
          <Box as="p" marginY="zero">
            האם ברצונך למחוק פריט זה
          </Box>
        </Popup.Body>
        <Flex gap="s" padding="xxs">
          <DeleteButton>לִמְחוֹק</DeleteButton>
          <SecondaryButton>לְבַטֵל</SecondaryButton>
        </Flex>
      </Popup.Card>
    </CanvasProvider>
  );
};

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 {@link PopupStack } system. Popper has no UI and will render any children to the body element and position around a provided anchorElement.

Prefer using 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.

Props

NameTypeDescriptionDefault
anchorElement <Element> Element null

The reference element used to position the Popper. Popper content will try to follow the anchorElement if it moves and will reposition itself if there is no longer room in the window.

children ((props: {
    placement: ;
  }) => ReactNode)
ReactNode

The content of the Popper. If a function is provided, it will be treated as a Render Prop and pass the placement chosen by PopperJS. This placement value is useful if your popup needs to animate and that animation depends on the direction of the content in relation to the anchorElement.

getAnchorClientRect() => 

When provided, this optional callback will be used to determine positioning for the Popper element instead of calling getBoundingClientRect on the anchorElement prop. Use this when you need complete control over positioning. When this prop is specified, it is safe to pass null into the anchorElement prop. If null is passed into the anchorElement prop, an owner will not be provided for the PopupStack.

openboolean

Determines if Popper content should be rendered. The content only exists in the DOM when open is true

true
placement

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

fallbackPlacements[]

Define fallback placements by providing a list of in array (in order of preference). The default preference is following the order of top, right, bottom, and left. Once the initial and opposite placements are not available, the fallback placements will be in use. Use an empty array to disable the fallback placements.

onPlacementChange(placement: ) => void

A callback function that will be called whenever PopperJS chooses a placement that is different from the provided placement preference. If a placement preference doesn't fit, PopperJS will choose a new one and call this callback.

popperOptions<PopperOptions>

The additional options passed to the Popper's popper.js instance.

portalboolean

If false, render the Popper within the DOM hierarchy of its parent. A non-portal Popper will constrained by the parent container overflows. If you set this to false, you may experience issues where you content gets cut off by scrollbars or overflow: hidden

true
popperInstanceRefRef<>

Reference to the PopperJS instance. Useful for making direct method calls on the popper instance like update.

This component is a container component that has no semantic element. It provides a React Context model for all Popup subcomponents. A model can be manually passed to subcomponents to override the model context.

// using Popup
<Popup model={model}>
  <Popup.Target /> // no model here
</Popup>

// using models on subcomponents
<>
  <Popup.Target model={model} />
</>

Props extend from . If a model is passed, props from PopupModelConfig are ignored.

NameTypeDescriptionDefault
childrenReactNode

The contents of the Popup. Can be Popup children or any valid elements.

model

Optional 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(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional 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 {@link SecondaryButton } element. You can override this by passing the desired component via as. Many examples above use as={DeleteButton}. If you want to render a {@link TertiaryButton } instead, use as={TertiaryButton}. The behavior hook used is called {@link usePopupTarget }.

const model = usePopupModel();

// using this component
<Popup.Target>Show Popup</Popup.Target>

// 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 {@link 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.

Props

Props extend from . Changing the as prop will change the element interface.

NameTypeDescriptionDefault
model

Optional 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(
  model: ,
  elemProps: TProps
) => HTML Attributes

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

usePopupTarget

Adds the necessary props to a subcomponent.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  ref: (instance:  null) => void;
  onClick: (event: ) => void;
}

Popup.Popper

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

Note: Popup.Popper renders any children to a div element created by the {@link 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 instead.

Props

Props extend from div. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
placement

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

fallbackPlacements[]

Define fallback placements by providing a list of in array (in order of preference). The default preference is following the order of top, right, bottom, and left. Once the initial and opposite placements are not available, the fallback placements will be in use. Use an empty array to disable the fallback placements.

popperOptions<PopperOptions>

The additional options passed to the Popper's popper.js instance.

anchorElement <Element> Element null

The reference element used to position the Popper. Popper content will try to follow the anchorElement if it moves and will reposition itself if there is no longer room in the window.

children ((props: {
    placement: ;
  }) => ReactNode)
ReactNode

The content of the Popper. If a function is provided, it will be treated as a Render Prop and pass the placement chosen by PopperJS. This placement value is useful if your popup needs to animate and that animation depends on the direction of the content in relation to the anchorElement.

getAnchorClientRect() => 

When provided, this optional callback will be used to determine positioning for the Popper element instead of calling getBoundingClientRect on the anchorElement prop. Use this when you need complete control over positioning. When this prop is specified, it is safe to pass null into the anchorElement prop. If null is passed into the anchorElement prop, an owner will not be provided for the PopupStack.

openboolean

Determines if Popper content should be rendered. The content only exists in the DOM when open is true

true
onPlacementChange(placement: ) => void

A callback function that will be called whenever PopperJS chooses a placement that is different from the provided placement preference. If a placement preference doesn't fit, PopperJS will choose a new one and call this callback.

portalboolean

If false, render the Popper within the DOM hierarchy of its parent. A non-portal Popper will constrained by the parent container overflows. If you set this to false, you may experience issues where you content gets cut off by scrollbars or overflow: hidden

true
popperInstanceRefRef<>

Reference to the PopperJS instance. Useful for making direct method calls on the popper instance like update.

asReact.ElementType

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.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

div
refReact.Ref<R = div>

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

Optional 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(
  model: ,
  elemProps: TProps
) => HTML Attributes

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

usePopupPopper

Adds the necessary props to a {@link Popper } component. Used by the subcomponent.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  open: boolean;
  anchorElement: <>;
  ref: (instance:  null) => void;
  onPlacementChange: (placement: ) => void;
}

Popup.Card

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

Layout Component

Popup.Card supports all props from thelayout component.

Props

Props extend from div. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
childrenReactNode

Children of the Card. Should contain a <Card.Body> and an optional <Card.Heading>

variant 'borderless' 'filled'

The variant of the Card. Can be default, borderless or filled.

'default'
cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use {@link mergeStyles } instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

// ...

// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.

return (
 <Element
   {...handleCsProp(elemProps, [
     myStyles,
     myModifiers({ size: 'medium' }),
     myVars({ backgroundColor: 'red' })
   ])}
 >
   {children}
 </Element>
)
asReact.ElementType

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.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

div
refReact.Ref<R = div>

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

Optional 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(
  model: ,
  elemProps: TProps
) => HTML Attributes

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

usePopupCard

Adds the necessary props to a subcomponent.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  role: 'dialog';
  aria-labelledby: string;
}

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 {@link usePopupCloseButton }.

Layout Component

Popup.CloseIcon supports all props from thelayout component.

Props

Props extend from button. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
variant'inverse'

Variant has an option for inverse which will inverse the styling

iconPosition 'start' 'end'

Button icon positions can either be start or end. If no value is provided, it defaults to start.

'start'
shouldMirrorIconboolean

If set to true, transform the icon's x-axis to mirror the graphic. Use this if you want to always mirror the icon regardless of the content direction. If the icon should mirror only when in an right-to-left language, use shouldMirrorIconInRTL instead.

false
shouldMirrorIconInRTLboolean

If set to true, transform the icon's x-axis to mirror the graphic when the content direction is rtl. Icons don't have enough context to know if they should be mirrored in all cases. Setting this to true indicates the icon should be mirrored in right-to-left languages.

false
size

There are four button sizes: extraSmall, small, medium, and large. If no size is provided, it will default to medium.

colors

Override default colors of a button. The default will depend on the button type

icon

The icon of the Button. Note: Not displayed at small size

fillstring

The fill color of the SystemIcon. This overrides color.

backgroundstring

The background color of the SystemIcon.

colorstring

The color of the SystemIcon. This defines accent and fill. color may be overwritten by accent and fill.

shouldMirrorboolean

If set to true, transform the SVG's x-axis to mirror the graphic. Use this if you want to always mirror the icon regardless of the content direction. If the SVG should mirror only when in an right-to-left language, use shouldMirrorInRTL instead.

false
shouldMirrorInRTLboolean

If set to true, transform the SVG's x-axis to mirror the graphic when the content direction is rtl. Icons don't have enough context to know if they should be mirrored in all cases. Setting this to true indicates the icon should be mirrored in right-to-left languages.

false
cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use {@link mergeStyles } instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

// ...

// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.

return (
 <Element
   {...handleCsProp(elemProps, [
     myStyles,
     myModifiers({ size: 'medium' }),
     myVars({ backgroundColor: 'red' })
   ])}
 >
   {children}
 </Element>
)
childrenReactNode
accentstring

The accent color of the SystemIcon. This overrides color.

accentHoverstring

The accent color of the SystemIcon on hover. This overrides colorHover.

backgroundHoverstring

The background color of the SystemIcon on hover.

colorHoverstring

The hover color of the SystemIcon. This defines accentHover and fillHover. colorHover may be overwritten by accentHover and fillHover.

fillHoverstring

The fill color of the SystemIcon on hover. This overrides colorHover.

fillIconboolean

Whether the icon should received filled (colored background layer) or regular styles. Corresponds to toggled in ToolbarIconButton

growboolean

True if the component should grow to its container's width. False otherwise.

asReact.ElementType

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.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

button
refReact.Ref<R = button>

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

Optional 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(
  model: ,
  elemProps: TProps
) => HTML Attributes

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

usePopupCloseButton

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

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  onClick: (event:  Event SyntheticEvent) => void;
}

Popup.Heading

A Popup.Heading is a wrapper around that connect the heading to a {@link 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 {@link usePopupHeading }.

Layout Component

Popup.Heading supports all props from thelayout component.

Props

Props extend from h2. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
childrenReactNode
idstring

The id of the Card heading. Tie this to an aria-labelledby for accessibility.

cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use {@link mergeStyles } instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

// ...

// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.

return (
 <Element
   {...handleCsProp(elemProps, [
     myStyles,
     myModifiers({ size: 'medium' }),
     myVars({ backgroundColor: 'red' })
   ])}
 >
   {children}
 </Element>
)
asReact.ElementType

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.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

h2
refReact.Ref<R = h2>

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

Optional 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(
  model: ,
  elemProps: TProps
) => HTML Attributes

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

usePopupHeading

Adds the necessary props to the subcomponent.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  id: string;
}

Popup.Body

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

Layout Component

Popup.Body supports all props from thelayout component.

Props

Props extend from div. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use {@link mergeStyles } instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

// ...

// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.

return (
 <Element
   {...handleCsProp(elemProps, [
     myStyles,
     myModifiers({ size: 'medium' }),
     myVars({ backgroundColor: 'red' })
   ])}
 >
   {children}
 </Element>
)
childrenReactNode
asReact.ElementType

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.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

div
refReact.Ref<R = div>

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

Optional 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(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional 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.CloseButton

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

Props

Props extend from . Changing the as prop will change the element interface.

NameTypeDescriptionDefault
childrenReactNode
asReact.ElementType

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.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

refReact.Ref<R = >

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

Optional 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(
  model: ,
  elemProps: TProps
) => HTML Attributes

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

usePopupCloseButton

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

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  onClick: (event:  Event SyntheticEvent) => void;
}

usePopupModel

usePopupModel (config: ):

Hooks

usePopupStack

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

This hook will add the stackRef element to the on mount and remove on unmount. If you use Popper, the popper stackRef is automatically added/removed from the PopupStack. The PopupStack 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 PopupStack, all popups will close rather than only the topmost one.

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

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

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}
  </>
);
<E extends >(
  ref: Ref<E>,
  target:  <>
) => <>

useAssistiveHideSiblings

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

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {}

useBringToTopOnClick

This hook will bring an element to the top of the stack when any element inside the provided {@link PopupModel }'s state.stackRef element is clicked. If 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).

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {}

useCloseOnEscape

Registers global detection of the Escape key. It will only call the {@link 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.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {}

useCloseOnOutsideClick

Registers global listener for all clicks. It will only call the {@link 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 {@link useAlwaysCloseOnClickOutside } instead.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {}

useAlwaysCloseOnOutsideClick

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

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {}

useCloseOnTargetHidden

Sets up an IntersectionObserver for the target element. When the target is detected as being less than 50% visible, the popup will close. Most likely, this will happen if the user scrolls an overflowed content area of the page and the target is no longer visible.

This should be used with popup elements that are transitory like Tooltips and dropdown menus.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {}

useDisableBodyScroll

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

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {}

useFocusRedirect

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.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {}

useFocusTrap

"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).

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {}

useInitialFocus

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 {@link useFocusRedirect } or {@link 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.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {}

useReturnFocus

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

This should be used on popup elements that use useInitialFocus.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {}

useTransferOnFullscreenEnter

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.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {}

useTransferOnFullscreenExit

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.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {}

Specifications

Content Guidelines

Can't Find What You Need?

Check out our FAQ section which may help you find the information you're looking for. For further information, contact the #ask-canvas-design or #ask-canvas-kitchannels on Slack.

On this Page: