Workday Canvas

Modal

Modals are interactive pop-ups reserved for situations which require immediate attention.

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

Anatomy

Image of a modal container with annotation markers.

  1. Card: The Card contains content for a modal. It has no stroke or depth applied because it appears in front of an overlay.
  2. Heading (Optional): Heading should display the title of the content or task.
  3. Body: Modals contain many different types of content in the body. Typical types of content include media, alerts, dialogs, and/or task-oriented flows.
  4. In-line Buttons (Optional): Action should be at the bottom of the container when used. There are multiple alignments available for use; Left (Default), Center, Full Width & Full Width Stacked, or Right aligned.
  5. Close “X” Icon (Optional): Users must be able to intentionally dismiss a modal. This icon inherits styling and interactions from our Tertiary Icon-Only Button Variant.
  6. Overlay: Used to block user interaction with content behind it. When there are no Close “X” Icon, clicking on the overlay doesn’t dismiss the modal.

Usage Guidance

  • Modals allow for entry of data or alert users on any given page after an action has been initiated and require immediate attention.
  • On web platforms with browser windows wider than 766px, Modals show up in the center of the screen and in front of an overlay.
  • On web platforms with browser windows less than 767px width, Dialogs show up at the bottom of the screen and in front of an overlay.
  • In-line buttons used in modal dialogs and non-user input modals, the alignment could be Left (Default), Center, Full Width & Full Width Stacked, or Right aligned.

When to Use

  • Use Modal to gather immediate input from the user by blocking interaction with the rest of the page.
  • Use Modal when alert content and text are too large for a standard Toast or Pop-up notification.

When to Use Something Else

  • Consider a Dialog to gather non-critical input from the user without blocking interaction with the rest of the page.
  • Do not use Modals to serve up easily accessible links or simple messages that can be dismissed quickly (use Toasts or Popups for this).
  • Do not use Modals to display dense information, such as Tables or Multi-View Containers.
  • 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.

Responsive View

Modal components adjust width and content presentation based on screen size. When content exceeds the length of the screen, the modal content will become scrollable in the body section of the modal. For long content on a small screen, inline buttons will continue to scroll with the content.

Touch Based Behavior

The overlay on modals are not click or touch enabled to close the modal component view on small screens between 320-767px. This accounts for accidental touch on mobile devices. Background overlays will close the modal when clicked on larger devices when the screen reaches the minimum width.

Examples

Basic

The basic behavior of a modal is to hide all content from all users that is “behind” the modal dialog.

import {Modal} from '@workday/canvas-kit-react/modal';
import {PrimaryButton} from '@workday/canvas-kit-react/button';
import {Flex, Box} from '@workday/canvas-kit-react/layout';

export default () => {
  const handleAcknowledge = () => {
    console.log('License Acknowledged');
  };

  const handleCancel = () => {
    console.log('Cancel clicked');
  };

  return (
    <Modal>
      <Modal.Target as={PrimaryButton}>Open License</Modal.Target>
      <Modal.Overlay>
        <Modal.Card>
          <Modal.CloseIcon aria-label="Close" />
          <Modal.Heading>MIT License</Modal.Heading>
          <Modal.Body>
            <Box as="p" marginY="zero">
              Permission is hereby granted, free of charge, to any person obtaining a copy of this
              software and associated documentation files (the "Software").
            </Box>
          </Modal.Body>
          <Flex gap="s" padding="xxs">
            <Modal.CloseButton as={PrimaryButton} onClick={handleAcknowledge}>
              Acknowledge
            </Modal.CloseButton>
            <Modal.CloseButton onClick={handleCancel}>Cancel</Modal.CloseButton>
          </Flex>
        </Modal.Card>
      </Modal.Overlay>
    </Modal>
  );
};

Without Close Icon

If you wish to remove the close icon button, you can simply omit the Modal.CloseButton subcomponent. If you have a modal dialog that requires the user to accept instead of dismiss though an escape key or clicking outside the modal, you must create a new PopupModel without those behaviors and hand that model to the Modal dialog component.

import React from 'react';

import {Modal} from '@workday/canvas-kit-react/modal';
import {
  usePopupModel,
  useInitialFocus,
  useReturnFocus,
  useFocusTrap,
  useAssistiveHideSiblings,
  useDisableBodyScroll,
} from '@workday/canvas-kit-react/popup';
import {DeleteButton} from '@workday/canvas-kit-react/button';
import {Flex, Box} from '@workday/canvas-kit-react/layout';
import {useUniqueId} from '@workday/canvas-kit-react/common';

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

  // disable useCloseOnEscape and useCloseOnOverlayClick
  useInitialFocus(model);
  useReturnFocus(model);
  useFocusTrap(model);
  useAssistiveHideSiblings(model);
  useDisableBodyScroll(model);
  const handleDelete = () => {
    console.log('Deleted item');
  };

  return (
    <Modal model={model}>
      <Modal.Target as={DeleteButton}>Delete Item</Modal.Target>
      <Modal.Overlay>
        <Modal.Card aria-describedby={longDescId}>
          <Modal.Heading>Delete Item</Modal.Heading>
          <Modal.Body>
            <Box as="p" id={longDescId} marginY="zero">
              Are you sure you want to delete the item?
            </Box>
          </Modal.Body>
          <Flex gap="s" padding="xxs">
            <Modal.CloseButton ref={cancelBtnRef}>Cancel</Modal.CloseButton>
            <Modal.CloseButton as={DeleteButton} onClick={handleDelete}>
              Delete
            </Modal.CloseButton>
          </Flex>
        </Modal.Card>
      </Modal.Overlay>
    </Modal>
  );
};

Custom Focus

By default, the Modal makes sure the first focusable element receives focus when the Modal is opened. Most of the time, this is the Modal.CloseIcon button. If that element isn’t present, the Modal will use the Modal Heading to make sure screen reader users have focus near the start of the Modal’s content. This allows screen reader users to discover the Modal’s content more naturally without having to navigate back up again. Sometimes, it is a better user experience to focus on a different element. The following example shows how initialFocusRef can be used to change which element receives focus when the modal opens.

import React from 'react';
import {Modal, useModalModel} from '@workday/canvas-kit-react/modal';
import {PrimaryButton} from '@workday/canvas-kit-react/button';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {Flex, Box} from '@workday/canvas-kit-react/layout';
import {useUniqueId} from '@workday/canvas-kit-react/common';

export default () => {
  const longDescID = useUniqueId();
  const ref = React.useRef<HTMLInputElement>(null);
  const [value, setValue] = React.useState('');
  const model = useModalModel({
    initialFocusRef: ref,
  });

  const handleAcknowledge = () => {
    console.log('Acknowledged license');
  };

  return (
    <Modal model={model}>
      <Modal.Target as={PrimaryButton}>Acknowledge License</Modal.Target>
      <Modal.Overlay>
        <Modal.Card aria-describedby={longDescID}>
          <Modal.CloseIcon aria-label="Close" />
          <Modal.Heading>Acknowledge License</Modal.Heading>
          <Modal.Body>
            <Box as="p" id={longDescID} marginTop={0} marginBottom="m">
              Enter your initials to acknowledge the license.
            </Box>
            <FormField cs={{marginBottom: 0}}>
              <FormField.Label>Initials</FormField.Label>
              <FormField.Input
                as={TextInput}
                ref={ref}
                value={value}
                onChange={e => setValue(e.currentTarget.value)}
              />
            </FormField>
          </Modal.Body>
          <Flex gap="s" padding="xxs">
            <Modal.CloseButton as={PrimaryButton} onClick={handleAcknowledge}>
              Acknowledge
            </Modal.CloseButton>
            <Modal.CloseButton>Cancel</Modal.CloseButton>
          </Flex>
        </Modal.Card>
      </Modal.Overlay>
    </Modal>
  );
};

Return Focus

By default, the Modal will return focus to the Modal.Target element, but it is possible the Modal was triggered by an element that won’t exist when the modal is closed. An example might be a Modal that was opened from an Menu item and the act of opening the Modal also closes the Menu, meaning the Menu item can no longer receive focus. The also probably means the Modal.Target component might not suite your needs. The Modal.Target adds both a ref and an onClick. If you provide a returnFocusRef, you only need to worry about the onClick. If you’re using a menu, you might need to use a different callback. Calling model.events.show() will show the Modal.

import React from 'react';
import {Modal, useModalModel} from '@workday/canvas-kit-react/modal';
import {DeleteButton} from '@workday/canvas-kit-react/button';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {Flex, Box} from '@workday/canvas-kit-react/layout';
import {Select} from '@workday/canvas-kit-react/select';

export default () => {
  const ref = React.useRef(null);
  const [value, setValue] = React.useState('');
  const model = useModalModel({
    returnFocusRef: ref,
  });

  const handleDelete = () => {
    console.log('Deleted item');
  };

  return (
    <Modal model={model}>
      <Select items={['', 'Delete', 'Two']}>
        <FormField>
          <FormField.Label>Choose an option</FormField.Label>
          <FormField.Input
            as={Select.Input}
            ref={ref}
            onChange={e => {
              const option = e.currentTarget.value;
              if (option === 'Delete') {
                model.events.show();
                setValue('');
              } else {
                setValue(e.currentTarget.value);
              }
            }}
          />
          <Select.Popper>
            <Select.Card>
              <Select.List>{item => <Select.Item>{item}</Select.Item>}</Select.List>
            </Select.Card>
          </Select.Popper>
        </FormField>
      </Select>
      <Modal.Overlay>
        <Modal.Card>
          <Modal.CloseIcon aria-label="Close" />
          <Modal.Heading>Delete Item</Modal.Heading>
          <Modal.Body>
            <Box as="p" marginY="zero">
              Are you sure you want to delete the item?
            </Box>
          </Modal.Body>
          <Flex gap="s" padding="xxs">
            <Modal.CloseButton as={DeleteButton} onClick={handleDelete}>
              Delete
            </Modal.CloseButton>
            <Modal.CloseButton>Cancel</Modal.CloseButton>
          </Flex>
        </Modal.Card>
      </Modal.Overlay>
    </Modal>
  );
};

Custom Target

It is common to have a custom target for your modal. Use the as prop to use your custom component. The Modal.Target element will add onClick and ref to the provided component. Your provided target component must forward the onClick to an element for the Modal to open. The as will cause Modal.Target to inherit the interface of your custom target component. This means any props your target requires, Modal.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 Modal without the user interacting with the target button first, you’ll also need to use React.forwardRef in your target component. Without this, the Modal will open at the top-left of the window instead of around the target.

import React from 'react';

import {Modal} from '@workday/canvas-kit-react/modal';

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

const MyTarget = ({label, ...props}: MyTargetProps) => {
  return <button {...props}>{label}</button>;
};

export default () => {
  return (
    <Modal>
      <Modal.Target as={MyTarget} label="Open" />
      <Modal.Overlay>
        <Modal.Card>
          <Modal.CloseIcon aria-label="Close" />
          <Modal.Heading>Modal Heading</Modal.Heading>
          <Modal.Body>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec semper facilisis dolor
            quis facilisis. Aenean tempor eget quam et semper. Nam malesuada rhoncus euismod.
            Quisque vel urna feugiat, dictum risus sed, pulvinar nulla. Sed gravida, elit non
            iaculis blandit, ligula tortor posuere mauris, vitae cursus turpis nunc non arcu.
          </Modal.Body>
        </Modal.Card>
      </Modal.Overlay>
    </Modal>
  );
};

Body Content Overflow

The Modal automatically handles overflowing content inside the Modal.Body element. If contents are larger than the browser’s height will allow, the content will overflow with a scrollbar. You may need to restrict the height of your browser to observe the overflow.

import React from 'react';

import {Modal} from '@workday/canvas-kit-react/modal';
import {PrimaryButton} from '@workday/canvas-kit-react/button';
import {Flex} from '@workday/canvas-kit-react/layout';

export default () => {
  const handleAcknowledge = () => {
    console.log('License Acknowledged');
  };

  const handleCancel = () => {
    console.log('Cancel clicked');
  };

  return (
    <Modal>
      <Modal.Target as={PrimaryButton}>Open License</Modal.Target>
      <Modal.Overlay>
        <Modal.Card>
          <Modal.CloseIcon aria-label="Close" />
          <Modal.Heading>MIT License</Modal.Heading>
          <Modal.Body tabIndex={0}>
            <p style={{marginTop: 0}}>
              Permission is hereby granted, free of charge, to any person obtaining a copy of this
              software and associated documentation files (the "Software"), to deal in the Software
              without restriction, including without limitation the rights to use, copy, modify,
              merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
              permit persons to whom the Software is furnished to do so, subject to the following
              conditions:
            </p>
            <p>
              The above copyright notice and this permission notice shall be included in all copies
              or substantial portions of the Software.
            </p>
            <p>
              THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
              INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
              PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
              HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
              CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
              OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
            </p>
            <p>
              Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
              incididunt ut labore et dolore magna aliqua. Amet massa vitae tortor condimentum
              lacinia quis. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl. Sed lectus
              vestibulum mattis ullamcorper velit sed. Rutrum tellus pellentesque eu tincidunt
              tortor aliquam nulla. Vitae turpis massa sed elementum tempus egestas sed sed risus.
              Cursus vitae congue mauris rhoncus aenean vel elit scelerisque mauris. Id neque
              aliquam vestibulum morbi blandit cursus risus at. Vel eros donec ac odio tempor orci.
              Ac felis donec et odio pellentesque diam volutpat. Laoreet non curabitur gravida arcu
              ac tortor dignissim. Rhoncus urna neque viverra justo nec ultrices dui. Bibendum arcu
              vitae elementum curabitur vitae nunc sed velit dignissim. Sed risus pretium quam
              vulputate dignissim suspendisse in est. Curabitur gravida arcu ac tortor. Nam libero
              justo laoreet sit amet cursus sit amet. Arcu dui vivamus arcu felis bibendum ut
              tristique et egestas. Eros donec ac odio tempor orci dapibus ultrices. At erat
              pellentesque adipiscing commodo elit at. Dignissim cras tincidunt lobortis feugiat
              vivamus at augue.
            </p>
            <p>
              Amet commodo nulla facilisi nullam vehicula ipsum. Blandit libero volutpat sed cras.
              Quam lacus suspendisse faucibus interdum posuere. Aenean euismod elementum nisi quis
              eleifend. Orci nulla pellentesque dignissim enim sit amet venenatis. Diam vel quam
              elementum pulvinar etiam non quam lacus. Sit amet dictum sit amet justo donec enim
              diam vulputate. Tincidunt ornare massa eget egestas purus. Pulvinar neque laoreet
              suspendisse interdum consectetur libero id faucibus. Morbi tincidunt augue interdum
              velit. Nullam non nisi est sit amet.
            </p>
            <p style={{marginBottom: 0}}>
              Aliquet enim tortor at auctor urna nunc id cursus metus. Leo urna molestie at
              elementum eu facilisis. Consectetur purus ut faucibus pulvinar elementum integer.
              Volutpat est velit egestas dui id ornare arcu odio. At consectetur lorem donec massa
              sapien. Condimentum vitae sapien pellentesque habitant. Pellentesque habitant morbi
              tristique senectus. Et molestie ac feugiat sed lectus vestibulum. Arcu risus quis
              varius quam quisque. Turpis massa tincidunt dui ut ornare lectus sit amet. Magna eget
              est lorem ipsum dolor sit. Suspendisse faucibus interdum posuere lorem ipsum. Nisi
              vitae suscipit tellus mauris a diam maecenas sed. Ipsum dolor sit amet consectetur
              adipiscing. Ultricies integer quis auctor elit sed. Scelerisque varius morbi enim nunc
              faucibus a. Tortor consequat id porta nibh venenatis cras. Consectetur adipiscing elit
              ut aliquam purus sit.
            </p>
          </Modal.Body>
          <Flex gap="s" padding="xxs">
            <Modal.CloseButton as={PrimaryButton} onClick={handleAcknowledge}>
              Acknowledge
            </Modal.CloseButton>
            <Modal.CloseButton onClick={handleCancel}>Cancel</Modal.CloseButton>
          </Flex>
        </Modal.Card>
      </Modal.Overlay>
    </Modal>
  );
};

Full overlay scrolling

If content is large, scrolling the entire overlay container is an option. Use the Modal.OverflowOverlay component instead of the Modal.Overlay component. The Modal.Card’s maxHeight and height will need to be reset to inherit to prevent any internal overflow.

This has the effect of scrolling the heading, close button, and any action buttons. If this type of scrolling behavior is not desired, try the Body Content Overflow method.

import React from 'react';

import {Modal} from '@workday/canvas-kit-react/modal';
import {PrimaryButton} from '@workday/canvas-kit-react/button';
import {Flex} from '@workday/canvas-kit-react/layout';

export default () => {
  const handleAcknowledge = () => {
    console.log('License Acknowledged');
  };

  const handleCancel = () => {
    console.log('Cancel clicked');
  };

  return (
    <Modal>
      <Modal.Target as={PrimaryButton}>Open License</Modal.Target>
      <Modal.OverflowOverlay>
        <Modal.Card maxHeight="inherit" height="inherit">
          <Modal.CloseIcon aria-label="Close" />
          <Modal.Heading>MIT License</Modal.Heading>
          <Modal.Body tabIndex={0}>
            <p style={{marginTop: 0}}>
              Permission is hereby granted, free of charge, to any person obtaining a copy of this
              software and associated documentation files (the "Software"), to deal in the Software
              without restriction, including without limitation the rights to use, copy, modify,
              merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
              permit persons to whom the Software is furnished to do so, subject to the following
              conditions:
            </p>
            <p>
              The above copyright notice and this permission notice shall be included in all copies
              or substantial portions of the Software.
            </p>
            <p>
              THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
              INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
              PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
              HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
              CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
              OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
            </p>
            <p>
              Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
              incididunt ut labore et dolore magna aliqua. Amet massa vitae tortor condimentum
              lacinia quis. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl. Sed lectus
              vestibulum mattis ullamcorper velit sed. Rutrum tellus pellentesque eu tincidunt
              tortor aliquam nulla. Vitae turpis massa sed elementum tempus egestas sed sed risus.
              Cursus vitae congue mauris rhoncus aenean vel elit scelerisque mauris. Id neque
              aliquam vestibulum morbi blandit cursus risus at. Vel eros donec ac odio tempor orci.
              Ac felis donec et odio pellentesque diam volutpat. Laoreet non curabitur gravida arcu
              ac tortor dignissim. Rhoncus urna neque viverra justo nec ultrices dui. Bibendum arcu
              vitae elementum curabitur vitae nunc sed velit dignissim. Sed risus pretium quam
              vulputate dignissim suspendisse in est. Curabitur gravida arcu ac tortor. Nam libero
              justo laoreet sit amet cursus sit amet. Arcu dui vivamus arcu felis bibendum ut
              tristique et egestas. Eros donec ac odio tempor orci dapibus ultrices. At erat
              pellentesque adipiscing commodo elit at. Dignissim cras tincidunt lobortis feugiat
              vivamus at augue.
            </p>
            <p>
              Amet commodo nulla facilisi nullam vehicula ipsum. Blandit libero volutpat sed cras.
              Quam lacus suspendisse faucibus interdum posuere. Aenean euismod elementum nisi quis
              eleifend. Orci nulla pellentesque dignissim enim sit amet venenatis. Diam vel quam
              elementum pulvinar etiam non quam lacus. Sit amet dictum sit amet justo donec enim
              diam vulputate. Tincidunt ornare massa eget egestas purus. Pulvinar neque laoreet
              suspendisse interdum consectetur libero id faucibus. Morbi tincidunt augue interdum
              velit. Nullam non nisi est sit amet.
            </p>
            <p style={{marginBottom: 0}}>
              Aliquet enim tortor at auctor urna nunc id cursus metus. Leo urna molestie at
              elementum eu facilisis. Consectetur purus ut faucibus pulvinar elementum integer.
              Volutpat est velit egestas dui id ornare arcu odio. At consectetur lorem donec massa
              sapien. Condimentum vitae sapien pellentesque habitant. Pellentesque habitant morbi
              tristique senectus. Et molestie ac feugiat sed lectus vestibulum. Arcu risus quis
              varius quam quisque. Turpis massa tincidunt dui ut ornare lectus sit amet. Magna eget
              est lorem ipsum dolor sit. Suspendisse faucibus interdum posuere lorem ipsum. Nisi
              vitae suscipit tellus mauris a diam maecenas sed. Ipsum dolor sit amet consectetur
              adipiscing. Ultricies integer quis auctor elit sed. Scelerisque varius morbi enim nunc
              faucibus a. Tortor consequat id porta nibh venenatis cras. Consectetur adipiscing elit
              ut aliquam purus sit.
            </p>
          </Modal.Body>
          <Flex gap="s" padding="xxs">
            <Modal.CloseButton as={PrimaryButton} onClick={handleAcknowledge}>
              Acknowledge
            </Modal.CloseButton>
            <Modal.CloseButton onClick={handleCancel}>Cancel</Modal.CloseButton>
          </Flex>
        </Modal.Card>
      </Modal.OverflowOverlay>
    </Modal>
  );
};

Form Modal

The Modal.Card can be turned into a form element to make a form modal. The model should be hoisted to allow for form validation and allow you to control when the modal closes.

import React from 'react';

import {Modal, useModalModel} from '@workday/canvas-kit-react/modal';
import {PrimaryButton} from '@workday/canvas-kit-react/button';
import {Flex} from '@workday/canvas-kit-react/layout';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {plusIcon} from '@workday/canvas-system-icons-web';

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

  const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault(); // prevent a page reload

    // do form validation here

    console.log('form data', {
      first: (event.currentTarget.elements.namedItem('first') as HTMLInputElement).value,
      last: (event.currentTarget.elements.namedItem('last') as HTMLInputElement).value,
    });

    // if it looks good, submit to the server and close the modal
    model.events.hide();
  };

  return (
    <Modal model={model}>
      <Modal.Target icon={plusIcon}>Create New User</Modal.Target>
      <Modal.Overlay>
        <Modal.Card as="form" onSubmit={onSubmit}>
          <Modal.CloseIcon aria-label="Close" />
          <Modal.Heading>New User</Modal.Heading>
          <Modal.Body>
            <FormField>
              <FormField.Label>First Name</FormField.Label>
              <FormField.Input as={TextInput} name="first" />
            </FormField>
            <FormField>
              <FormField.Label>Last Name</FormField.Label>
              <FormField.Input as={TextInput} name="last" />
            </FormField>
          </Modal.Body>
          <Flex gap="s" padding="xxs">
            <Modal.CloseButton>Cancel</Modal.CloseButton>
            <PrimaryButton type="submit">Submit</PrimaryButton>
          </Flex>
        </Modal.Card>
      </Modal.Overlay>
    </Modal>
  );
};

Component API

This component is the container component and does not render any semantic elements. It provides a React Context model for the Modal subcomponents. If you manually pass a model to all subcomponents, this container component isn't needed. If you do not pass a model, the Modal container component will build a default one using useModalModel. Modal is a composition of a component and has a similar structure to Popup.

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

NameTypeDescriptionDefault
childrenReactNode

The contents of the Dialog. Can be Dialog 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.

Modal.Overlay

The Modal.Overlay is the component that hooks a Modal up to the {@link PopupStack } as well as the semi-transparent overlay commonly used with modals. Internally, the Modal.Overlay component uses two div elements to ensure proper rendering of the Modal content. The default element is a div element and can be changed via the as prop.

Layout Component

Modal.Overlay 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.

Modal.Card

The Modal.Card is wraps a which wraps a {@link Card }. It is the role="dialog" element and is uses useModalCard behavior hook which sets aria-modal="false" and sets the aria-labelledby that points to the id of the . If you don't use a Modal.Heading, add an aria-label instead. The default element is a div and can be changed via the as prop.

Layout Component

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

useModalCard

(
  ,
  (
    model: ,
    elemProps: {},
    ref: React.Ref
  ) => {
    aria-modal: false;
  }
)

Modal.Heading

The Modal.Heading semantically labels the Modal via adding an id that the points to via aria-labelledby. If this component is not used, you must add an aria-label to the Modal.Card to label the Modal for screen reader users. This component uses the useModalHeading behavior hook which sets an id and also does some focus management logic for situations where there is no component used. Please use Modal.Heading and don't use your own unless you also use the useModalHeading hook in your component. Consult accessibility if you cannot use this component. The default element is an h2 and can be changed via the as prop.

Layout Component

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

useModalHeading

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

Modal.Body

Layout Component

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

Modal.OverflowOverlay

If content is large, scrolling the entire overlay container is an option. Use the Modal.OverflowOverlay component instead of the component. The 's maxHeight and height will need to be reset to inherit to prevent any internal overflow.

This component should be used in place of the component if full body overflow is desired.

Layout Component

Modal.OverflowOverlay 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.

useModalModel

This model hook uses and pre-configures behaviors that are required for an accessible modal. useModalModel should be used in most cases, but if you require custom behaviors, you can use usePopupModel directly. Be sure to add proper popup behaviors to ensure the modal is accessible.

The following behaviors are added to the PopupModel:

You can pass the Modal model config either directly to the Modal component or to the useModalModel hook, but not both. A model prop always takes precedence over the config passed to the useModalModel hook. If no model is passed to a Modal component, a ModalModel will be created for you. Creating your own model hoists the modal's state to the level of your component and allows you to access the model's state and events.

const model = useModalModel(config);

<Modal model={model}>
  // ...
</Modal>
useModalModel (config: ):

Specifications

Accessibility Guidelines

How Modal Dialogs Impact the Accessible Experience

Modal dialog components require special care and attention for delivering an accessible experience. Modal dialog content is most commonly appended at the bottom of the browser’s DOM, so neglecting to move focus inside the Modal will likely block keyboard access to the content inside the Modal dialog.

For an accessible listening experience with a screen reader, users must be able to understand the context of the screen has changed from a full page to a Modal dialog window. This can be accomplished by using a few well-supported ARIA roles and properties on the Modal dialog container.

Keyboard Interaction

When the Modal dialog appears on screen, keyboard focus must be set to an element inside of the Modal dialog. If possible, keyboard focus should be returned back to the element that invoked the modal dialog when it is dismissed.

Any interactive elements in the Modal dialog must have a focus indicator that is highly visible against the background and against the non-focused state. Refer to Accessible Colors for more information.

Modal dialog must support the following keyboard interactions:

  • Tab: Focuses interactive elements included inside the modal dialog (e.g. buttons, links, inputs, selects, etc.)
  • Esc: Dismisses the modal dialog

Screen Reader Interaction

Modal dialogs must communicate the following to users:

  • The context has changed to a “modal dialog” or “web dialog”
  • The title heading of the modal dialog
  • The focused element inside the dialog (e.g. “Close, button”)

Design Annotations Needed

  • Specify initial focus when the Modal dialog appears
  • Specify keyboard focus after the Modal dialog is dismissed

Implementation Markup Needed

  • When body content overflows and creates a scrollable area, setting tabindex=”0” may be necessary to support keyboard scrolling.
  • When setting keyboard focus to a custom target, or when the Close Icon button is not used, the aria-describedby property should be set on the Modal container referencing a unique id of the body text content. This practice supports screen readers when pushing keyboard focus after static text in the modal.
  • [Included in component] When Modal dialog appears, set keyboard focus to the first interactive element inside the Modal dialog.
  • [Included in component] Keyboard focus must be trapped inside the Modal dialog container.
  • [Included in component] When Modal dialog is dismissed, return keyboard focus back to the target element that invoked the Modal dialog.
  • [Included in component] The ARIA role=”dialog” property must be set on the Modal dialog container element, and the aria-labelledby property must reference the unique id of the Modal dialog title heading.
  • [Included in component] The aria-modal=”true” property is required for Modal dialogs.

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: