Skip to content
lab components / Overlay

Modal

A modal appears over the original content and interrupts the user's workflow to present critical information or request the user to take action.

This is a Lab component!

That means it doesn't satisfy our definition of done and may be changed or even deleted. For an exact status, please reach out to the Fancy team through the dev_fancy or ux_fancy channels.

import { Modal } from "@siteimprove/fancylab";

#Examples

#Default

In most cases, a Modal should always be dismissible and initiated by the user. This gives the user the freedom and control to decide when they want to see that modal. In certain cases, a system-initiated Modal is required to prevent or correct critical errors. Use a system-initiated Modal sparingly as it interrupts the user's current task. The user may be surprised by the unexpected behavior of a system-initiated Modal.

The example serves as a base Modal used for information-only content and does not prompt the user for an action. A Modal consists of a header, a close button, and body content.

  • Header: includes the modal title (with an optional tooltip) and an optional icon.
  • Body content: provides information and/or controls necessary to complete the modal's task.
  • Close button: use the close button to close the modal without submitting any data.
  • Overlay: screen overlay, also referred to as backdrop, hides the page's content.
const [showModal, setShowModal] = useState(false); return ( <> <Button aria-haspopup="dialog" onClick={() => setShowModal(true)}> Trigger modal </Button> <Modal shown={showModal} headerTitle="Default modal" onClose={() => setShowModal(false)}> <Modal.Content> <Paragraph> Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro nemo repellendus obcaecati praesentium illo, voluptate fugiat quisquam vitae nostrum modi earum eos nesciunt at, placeat sint vel quas quae aliquid. </Paragraph> </Modal.Content> </Modal> </> );

Modals can optionally contain a footer. This section is intended for information and actions that relate to the Modal as a whole. In most cases, that will be the Action Bar component, which required the user to complete or cancel the modal task.

Add a footer by placing a Modal.Footer component inside a Modal as its last child.

In most cases,

  • use two buttons to give the user a clear choice.
  • avoid using more than three buttons.
  • a destructive button can replace a primary button in the footer to confirm actions that will cause significant data loss if clicked accidentally.
const [showModal, setShowModal] = useState(false); return ( <> <Button aria-haspopup="dialog" onClick={() => setShowModal(true)}> Trigger modal </Button> <Modal shown={showModal} headerTitle="Modal with footer" onClose={() => setShowModal(false)}> <Modal.Content> <Paragraph> Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro nemo repellendus obcaecati praesentium illo, voluptate fugiat quisquam vitae nostrum modi earum eos nesciunt at, placeat sint vel quas quae aliquid. </Paragraph> </Modal.Content> <Modal.Footer> <ActionBar primary={{ children: "Confirm", onClick: () => setShowModal(false) }} cancel={{ children: "Cancel", onClick: () => setShowModal(false) }} /> </Modal.Footer> </Modal> </> );

#Sizes

Choose a size that works best for the amount of modal content you have. For short messages, use a small size of a Modal to maintain user focus. When using medium and fullWidth Modal, use them sparingly with consideration for readability.

Use the size property to adjust the width of the Modal. These are the available sizes and their intended use cases:

  • small is the default value and is intended to be used for single-column layouts.
  • medium is intended to be used for multiple-column layouts.
  • fullWidth is intended to present images or present a layout with columns.

Here's an example of a medium sized Modal.

const [showModal, setShowModal] = useState(false); return ( <> <Button aria-haspopup="dialog" onClick={() => setShowModal(true)}> Trigger modal </Button> <Modal shown={showModal} size="medium" headerTitle="Medium sized modal" onClose={() => setShowModal(false)} > <Modal.Content> <Paragraph> Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro nemo repellendus obcaecati praesentium illo, voluptate fugiat quisquam vitae nostrum modi earum eos nesciunt at, placeat sint vel quas quae aliquid. </Paragraph> </Modal.Content> </Modal> </> );

Max heights

Each Modal size has a max height to maintain a proper window ratio. Consider sizing up the modal if vertical scrolling is too much of a problem due to a max height. See the scrolling behavior below. It may be necessary to use a full page instead of a fullWidth modal if the height of the fullWidth modal is still not sufficient. Especially if the content contains a lot of data and it takes a while to load.

Here's an example of a fullWidth Modal.

const [showModal, setShowModal] = useState(false); return ( <> <Button aria-haspopup="dialog" onClick={() => setShowModal(true)}> Trigger modal </Button> <Modal shown={showModal} size="fullWidth" headerTitle="Full width modal" onClose={() => setShowModal(false)} > <Modal.Content> <Paragraph> Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro nemo repellendus obcaecati praesentium illo, voluptate fugiat quisquam vitae nostrum modi earum eos nesciunt at, placeat sint vel quas quae aliquid. </Paragraph> </Modal.Content> </Modal> </> );

#Padding

The Modal.Content component provides the same padding options as the Content component:

  • large
  • medium (default value)
  • small
  • none
  • compact

The example shows a Modal.Content with the padding property set to large.

const [showModal, setShowModal] = useState(false); return ( <> <Button aria-haspopup="dialog" onClick={() => setShowModal(true)}> Trigger modal </Button> <Modal shown={showModal} headerTitle="Modal with large padding" onClose={() => setShowModal(false)} > <Modal.Content padding="large"> <Paragraph> Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro nemo repellendus obcaecati praesentium illo, voluptate fugiat quisquam vitae nostrum modi earum eos nesciunt at, placeat sint vel quas quae aliquid. </Paragraph> </Modal.Content> </Modal> </> );

#Tooltip in header

Adding a tooltip to the header is not recommended. The header should be clear and inform the user about the purpose of the modal.

A header should only contain a tooltip if we need to:

  • introduce an unfamiliar term;
  • reassure the user to perform the required actions.

A tooltip in the header should not prompt the user to take any additional action.

To add a tooltip to the header you must use the headerTitleTooltipText prop to ensure proper accessibility. Do not add the Tooltip component to the headerTitle prop.

If you need to change the placement of the tooltip you can use the headerTitleTooltipPlacement prop.

const [showModal, setShowModal] = useState(false); return ( <> <Button aria-haspopup="dialog" onClick={() => setShowModal(true)}> Trigger modal </Button> <Modal shown={showModal} headerTitle="Header title with tooltip" headerTitleTooltipText="Lorem ipsum dolor sit amet consectetur adipisicing elit." headerTitleTooltipPlacement="bottom" onClose={() => setShowModal(false)} > <Modal.Content> <Paragraph> Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro nemo repellendus obcaecati praesentium illo, voluptate fugiat quisquam vitae nostrum modi earum eos nesciunt at, placeat sint vel quas quae aliquid. </Paragraph> </Modal.Content> </Modal> </> );

#Icon in header

Use a header with an information icon to remind the user of the importance of taking a specific action. Use a consistent color and purpose for each type of information icon, as described in the Message component.

const [showModal, setShowModal] = useState(false); return ( <> <Button aria-haspopup="dialog" onClick={() => setShowModal(true)}> Trigger modal </Button> <Modal headerIcon={<IconPotentialIssue />} headerTitle="Header title with icon" shown={showModal} onClose={() => setShowModal(false)} > <Modal.Content> <Paragraph> Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro nemo repellendus obcaecati praesentium illo, voluptate fugiat quisquam vitae nostrum modi earum eos nesciunt at, placeat sint vel quas quae aliquid. </Paragraph> </Modal.Content> </Modal> </> );

#No close button in header

Be careful when using this variant. If the close button is not provided, the user is forced to understand the content and make a decision about which button to choose to continue the workflow.

This variant is recommended to use only when

  • the system requires the user to acknowledge the presented information.
  • the user’s actions may destroy data or cause significant changes. This allows the user to confirm the actions.
const [showModal, setShowModal] = useState(false); return ( <> <Button aria-haspopup="dialog" onClick={() => setShowModal(true)}> Trigger modal </Button> <Modal headerTitle="Header without close button" shown={showModal} onClose={() => setShowModal(false)} hideClose > <Modal.Content> <Paragraph> Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro nemo repellendus obcaecati praesentium illo, voluptate fugiat quisquam vitae nostrum modi earum eos nesciunt at, placeat sint vel quas quae aliquid. </Paragraph> </Modal.Content> <Modal.Footer> <ActionBar primary={{ children: "Confirm", onClick: () => setShowModal(false) }} cancel={{ children: "Cancel", onClick: () => setShowModal(false) }} /> </Modal.Footer> </Modal> </> );

#Dropzone

DialogDropzone is not a feature for end users. It is a structural component that allows developers to control where the Dialog component will appear in the Document Object Model (DOM).

By default, modals are appended to the DOM in the bottom of the body element. The DialogDropzone component lets you control where in the DOM the Modal is rendered, allowing it to inherit styles, if you don't use a global stylesheet.

<DialogDropzone />

#Properties

PropertyDescriptionDefinedValue
shownRequired
boolean
onCloseRequired
function
childrenOptional
element
onUnmountOptional
function
sizeOptional
"fullWidth" | "medium" | "small"
hideHeaderOptional
unknown
aria-labelledbyOptional
string
headerTitleOptional
element
classNameOptional
stringCustom className that's applied to the outermost element (only intended for special cases)
styleOptional
objectStyle object to apply custom inline styles (only intended for special cases)
idOptional
string
headerTitleTooltipTextOptional
| string | number | element
headerTitleTooltipPlacementOptional
"bottom" | "bottom-end" | "bottom-start" | "left" | "left-end" | "left-start" | "right" | "right-end" | "right-start" | "top" | "top-end" | "top-start"
headerIconOptional
element
hideCloseOptional
boolean
data-observe-keyOptional
stringUnique string, used by external script e.g. for event tracking

#Guidelines

#Best practices

#General

Use Modal sparingly to limit the interruption to the user.

Use Modal when

  • important warnings are displayed to avoid or correct critical errors.
  • inviting the user to enter the information needed to continue the process. Preferably, the requested input will help the user to reduce effort or streamline the current task.

#Actions

Modal is typically used for the following actions:

  • Customize data or displayed content

    Modal can be used to require the user to respond immediately or to perform targeted tasks. This type of action should be immediate. If a longer load time is required, you can use the Spinner to inform the user that their action is being processed. The primary action button should have a load status and disable other buttons while the load is in progress. See the Action Bar guideline. An example of the modal can be found in Accessibility Overview > Set site target.
  • Inform the user about critical information

    Use a modal to present information that the user needs to pay attention to in their current workflow. An example of the modal can be found when first entering Analytics > Analytics settings > Data Retention.
  • Confirm a user decision

    This action should only be used when the user takes uncommon destructive and irreversible actions. Inform the user about the consequence of their actions, suggest alternatives, and remind them if the action cannot be reversed. If the destructive action can be undone, the user does not need to confirm the action. An example of the modal can be found in Performance > Performance profiles.

#Scrolling behavior

A Modal with a scrolling view makes it more difficult for the user to read and complete the task. Therefore, scrolling behavior is discouraged in most cases. Be sure to minimize the potential for scrolling by keeping the modal's header short and containing only the necessary content.

  • The user should never scroll horizontally to see the content of the Modal.
  • Avoid vertical scrolling when possible. If you must use the scroll bar, make sure there is a clear visual connection to the content and a close button or a Cancel button so the user can exit the Modal at any time.
  • The header and footer of the Modal remain fixed when you display a scrollable list of options.
  • Avoid double scrolling. Clearly define the scrolling area. The user should not scroll with elements outside the Modal, such as the background.

#Do not use when

  • the user flow is complex and involves multiple steps or needs to be scrollable. Instead, create the flow on a separate page.
  • presenting complex or large amounts of data that require additional sources of information that are not included in the modal.
  • there is no connection between the content and the current workflow. For example, ads and email signups are important to generate business leads, but they are not crucial to the user. This only makes users annoyed and increases the interaction cost of closing a modal, as it does not serve the user's goal.
  • displaying low-priority information. For example, displaying an error, warning, or success status. In this case, use Toast instead.
  • displaying prominent or medium-priority information. In this case, use Message instead.

#Accessibility

For designers

  • Modal should never be nested. For example, one modal cannot be triggered by another modal.
  • the user should not be able to interact with any element on the page outside of the Modal as long it is open.
  • Modal must contain a <h1> header.
  • It would be best if both the close button and the Cancel button were present. This would help both users who use only the keyboard and screen readers who navigate slowly using assistive technologies.
  • Ensure when a Modal closes, the keyboard focus is returned to the button that triggered the Modal, if possible.

For developers

  • Make sure that a Modal trigger button has the aria-haspopup="dialog" attribute. This way, screen reader users know what to expect when they click the button.
  • Allow the user to close a Modal with the Esc key at any time.

Explore detailed guidelines for this component: Accessibility Specifications

#Writing

Header

  • Write a short question or statement, using a verb+noun combination that clearly describes the modal purpose. For example: 'Add keywords to track your ranking'.
  • Use sentence case.
  • Avoid unnecessary words such as the, an, or a.
  • Avoid apologies ('Sorry for the interruption'), alarm ('Warning!'), or ambiguity ('Are you sure?').

Body content

  • Only provide the user with content relevant to the task or flow.
  • Present the most critical information first.
  • Be direct and use a neutral, approachable tone. See standard UX microcopy to better understand users' emotions.

Footer

  • Button text should start with a verb to encourage action. For example, a modal titled "Add a widget to the dashboard" has a button titled "Add widget". Follow the writing guideline for Button.
  • Button text should enable the user to predict what will happen when they click the button.