Modals focus a user's attention on one task via a window that sits on top of the page. They present critical information, which may or may not require user input, before continuing the workflow.
By design, modals interrupt a user's workflow. Modals disable the main content area and prevent users from returning to their previous workflow until they complete the modal task or dismiss the modal.
Modals are mostly used for noting errors (original purpose) but they can be used for alerts, confirmations and custom requirements.
Anatomy
Modals have a header, body and a footer.
- Header: The header contains the title of the modal window and communicates its purpose.
- Body: Contains supportive text that is necessary for users to complete the modal's task. Modals can also accommodate other components within the body.
- Buttons: The main actions that the user needs to complete or cancel the modal's task.
- Close icon: Closes the dialog without submitting any data.
- Overlay: Covers the main page content and renders it inactive until the modal's task is completed or dismissed.
Best practices
- Use modals for important warnings and to prevent or correct critical errors.
- Use modals for critical information that requires user input before continuing the workflow.
- Use to partition a complex workflow into simpler steps.
- Use to ask for input that, when entered, could significantly lessen the user's work or effort.
- Use modals for essential information, related only to the current workflow.
- Use modals for actions that require final confirmation before committing to it.
- Be careful to not use a modal which will end up interrupting high-stakes processes such as a checkout flow.
- Ensure the modal has all the needed information for a single decision.
- Do not use modals for complicated decisions which requires additional information than what is available on the modal.
- Customized modals should inherit the simplicity of regular models, with clear instructions and actions for the user.
- The overlay should cover the entire page to bring attention to the modal.
- When the modal is closed, the user is returned to the on-page content and their previous workflow.
Interactions
When a modal is triggered, certain elements such as buttons have an active, focus and hover state.
- Focus: Provides feedback indicating element is highlighted using a keyboard or mouse.
- Active: Provides feedback indicating the user is clicking or tapping the element.
- Hover: Provides feedback indicating the user has placed a cursor over the element. (desktop only).
Clicking the close icon in the upper right will close the modal without submitting user data.
Clicking the primary action button completes the task and closes the modal.
The secondary action offers a way for the user to back out and take no action. Clicking the secondary button closes the modal and returns the user to their previous context.
Errors
- Error messages should follow error messaging guidelines.
UX writing considerations
- Headers should ask a single, clear question or communicate a single concise message. If the modal is informational or educational with no decision required, then the title can be a statement.
- Avoid using "Are you sure" as a modal title. It's vague and users might not know what you're really asking. Instead, ask users the question you want them to answer using the specific action, e.g., Send payment?
Body
- Body text should clarify any consequences and explain options in simple terms.
- Avoid including information that is unrelated to the decision or complicated descriptions of rare situations.
- For informational modals, use the correct tone to match the nature of the message. For example, when seeking to prevent an error, UX text should be clear and straightforward.
- Primary button text should state an unambiguous action that answers the question posed in the headline.
- Make sure the question and the actions use the same words. Avoid using disjointed language. For example, if you ask, "Delete portfolio?" in the header, the primary CTA should also read "Delete."
- Primary actions go on the right, dismissive or secondary actions on the left.
- For modals that require a decision, use words that describe the literal action rather than something vague like Yes, OK or Sure.
- Reserve "OK" for situations like acknowledgment modals, where you're simply asking the user to acknowledge the presented information.
Accessibility
The Jutro modal components are secondary window that appear over the primary window. They enable users to maintain the context of the primary task.
- Screen reader interaction: The modal components include a WAI-ARIA role="dialog" and
aria-modal="true"
to indicate to assistive technologies that the window beneath is currently inert. Focus order, accessible names and programmatic relationships are dependent on the individual content within each modal. The element with ARIA role="dialog" includes both an aria-labelledby
& an aria-describedby
reference, which refer to the primary modal title & modal content respectively.
- Keyboard interaction: When triggered, the tab sequence is contained & keyboard focus is trapped within the modal. The primary window is inert and cannot be interacted with until the modal is dismissed.
- Color: The contrast ratio of textual elements against their background is above 4.5:1.
- Zoom: All content is visible and functional up to & including a zoom factor of 200%.
This component has been validated to meet the WCAG 2.0 AA accessibility guidelines. However, changes made by the content author can affect accessibility conformance. Refer to the WAI-ARIA Authoring Practices for Modals, and within your application, be sure to check that:
- Upon triggering, keyboard focus is placed on the first focusable item within the modal dialogue.
- Upon dismissal, keyboard focus returns to the same component which triggered the modal dialogue.
- Focus should not move outside the modal until it is closed.
If you are working on a micro frontend, use ModalNextContext. ModalNext
does not work in embedded applications.
ModalNext
renders children in front of an overlay. It offers the following:
- Blocks interaction below the modal
- Disables scrolling while open
- Manages focus according to accessibility best practices (see accessibility)
Basic implementation
import React, { useState } from 'react';
import {
Button,
ModalBody,
ModalFooter,
ModalHeader,
ModalNext,
} from '@jutro/components';
const MyModal = ({ isOpen, onResolve }) =>
isOpen ? (
<ModalNext isOpen>
<ModalHeader onClose={() => onResolve()} />
<ModalBody>
<p>You can put anything you want here!</p>
</ModalBody>
<ModalFooter>
<Button onClick={() => onResolve('Cancel')}>Cancel</Button>
<Button onClick={() => onResolve('OK')}>OK</Button>
<Button onClick={() => onResolve('More')}>More options</Button>
</ModalFooter>
</ModalNext>
) : null;
export const MyComponent = () => {
const [isOpen, setIsOpen] = useState(false);
const handleOpen = () => setIsOpen(true);
const handleModalResult = (result) => {
switch (result) {
case 'Cancel':
alert('Cancelled');
break;
case 'OK':
alert("So you're okay with this?");
break;
case 'More':
alert('Your other options');
break;
default:
alert('The default thing happened.');
break;
}
setIsOpen(false);
};
const handleClose = () => {
alert('Closed with the X button');
};
return (
<div>
<MyModal
isOpen={isOpen}
onResolve={handleModalResult}
/>
<Button onClick={handleOpen}>Show Modal</Button>
</div>
);
};
The simplest way to display a modal is to import <ModalNext>
and its child components and display it using state in the parent component.
To implement business logic, set up a handler and pass it as onResolve
. In the example above, we trigger different actions using with each button. You can run several operations in the callback function, and close the modal when you are ready.
Notice that <ModalHeader>
takes a onClose
callback which runs when the use clicks the X (close) button.
Props
ModalNext
Prop | Type | Description |
---|
contentLayout | {component: string, componentProps: object}, LayoutShape | defines the layout to be used with a 'component' property set to either Flex or Grid and componentProperties to set properties for that layout component |
isOpen | bool | Optional flag indicating whether the Modal is currently open |
onAfterOpen | func | Callback function that, if provided, is called when the Modal dialog has been opened and is visible to the user |
onAfterClose | func | Callback function that, if provided, is called when the Modal dialog has been close and is hidden from the user |
onRequestClose | func | Callback function that, if provided, is called when the Modal dialog has been requested to be closed (either by clicking on overlay or pressing ESC) |
closeTimeoutMS | number | Number indicating the milliseconds to wait before closing the modal. |
contentLabel | IntlMessageShape | String indicating how the content container should be announced to screen readers |
overlayClassName | string | The optional CSS class for the overlay for the Modal dialog |
className | string | The optional CSS class for the the Modal dialog |
shouldFocusAfterRender | bool | Optional flag indicating whether the Modal will autofocus to itself on open |
shouldCloseOnOverlayClick | bool | When false will not close the dialog when the overlay is clicked. |
shouldCloseOnEsc | bool | Optional flag indicating whether keyboard support for closing the Modal is available (via the ESC key) |
shouldReturnFocusAfterClose | bool | Optional flag indicating if the Modal should restore focus to the element that had focus prior to its display. |
parentSelector | func | Function that will be called to get the parent element that the Modal will be attached to. |
The ModalNext
component is the base component that should wrap the below components, and determines some of the overarching behaviors of the modal.
Prop | Type | Description |
---|
contentLayout | {component: string, componentProps: object}, LayoutShape | defines the content layout to be used with a 'component' property set to either Flex or Grid and componentProps to set properties for that layout component |
titleLayout | {component: string, componentProps: object}, LayoutShape | defines the header layout to be used with a 'component' property set to either Flex or Grid and componentProps to set properties for that layout component |
status | success , info , warning , error | The status of this modal. Either a 'success', 'info', 'warning', error'. Default to no status |
icon | string | See Icon |
title | IntlMessageShape | Text to display for the title |
subtitle | IntlMessageShape | Text to display for the subtitle |
onClose | func | The function to be called when the close button is clicked. If you do not set this function, the modal will not have a close (x) button in the corner. |
The ModalHeader
component should be used to display the top portion of the modal - the status color bar, the optional icon to the left of the title, and the title itself.
ModalBody
Prop | Type | Description |
---|
id | string | Used to identify modal body component. |
contentLayout | {component: string, componentProps: object}, LayoutShape | Defines the layout to be used with a 'component' property set to either Flex or Grid and componentProps property to set properties for that layout component. |
autoFocus | bool | Causes focus to be drawn to the body of the modal on mount. Defaults to true . |
The ModalBody
component is a simple component that serves to wrap the content of the modal and should be placed after the ModalHeader
Prop | Type | Description |
---|
contentLayout | {component: string, componentProps: object}, LayoutShape | defines the layout to be used with a 'component' property set to either Flex or Grid (otherwise, will default to a div) and componentProperties to set properties for that layout component |
The ModalFooter
component is a simple component that serves to wrap the footer content of the modal and should be placed as the last component wrapped by ModalNext
. You will usually wrap buttons within this component, but it does not restrict you if other components are necessary for the implementation.
Async example
const MyModal = ({ isOpen, onResolve }) =>
isOpen ? (
<ModalNext isOpen>
<ModalHeader onClose={() => onResolve()} />
<ModalBody>
<p>You can put anything you want here!</p>
</ModalBody>
<ModalFooter>
<Button onClick={() => onResolve('Cancel')}>Cancel</Button>
<Button onClick={() => onResolve('Process request')}>
Process request
</Button>
</ModalFooter>
</ModalNext>
) : null;
export const MyComponent = () => {
const [isOpen, setIsOpen] = useState(false);
const [processing, setProcessing] = useState(false);
const handleOpen = () => setIsOpen(true);
const processRequest = async () => {
return new Promise((resolve) => {
setTimeout(() => {
alert('Request successful');
resolve();
}, [3000]);
});
};
const handleModalResult = async (result) => {
if (result === 'Process request') {
if (!processing) {
setProcessing(true);
await processRequest();
setProcessing(false);
setIsOpen(false);
}
} else {
setIsOpen(false);
}
};
return (
<div>
<MyModal
isOpen={isOpen}
onResolve={handleModalResult}
/>
<Button onClick={handleOpen}>Show Modal</Button>
</div>
);
};
Accessibility
This component has been validated to meet the WCAG 2.0 AA accessibility guidelines. However, changes made by the content author can affect accessibility conformance. Refer to the WAI-ARIA Authoring Practices for Modals, and within your application, be sure to check that:
- Upon triggering, keyboard focus is placed on the first focusable item within the modal dialogue.
- Upon dismissal, keyboard focus returns to the same component which triggered the modal dialogue.
- Focus should not move outside the modal until it is closed.
ModalNextContext
ModalNextContext
(modal provider) is the only way to create a modal in a micro frontend.
import React, { useContext } from 'react';
import { ModalNextContext } from '@jutro/components';
const { showAlert } = useContext(ModalNextContext);
const showSomething = (msg) => {
showAlert({
status: 'success',
title: messages.genericSuccessMessage,
message: msg,
});
};
<button onClick={() => showSomething('Surprise!')}>
Click here to get a surprise
</button>;
Generic modals
Alert
The Alert modal is a generic modal that will display a modal with a status color, optional icon, title, message and a confirm button. The results of the Alert modal can be captured as well (confirm or close (as a reject)).
import React, { useState, useContext } from 'react';
import { ModalNextContext } from '@jutro/components';
const AlertModalExample = () => {
const [result, setResult] = useState(null);
const { showAlert } = useContext(ModalNextContext);
async function triggerAlert() {
const results = await showAlert({
status: 'info' ,
icon: 'gw-error-outline' ,
title:
'Test Alert' ,
message:
'Just testing an Alert!' ,
confirmButtonText:
'OK' ,
});
setResult(`modal result was: ${results}`);
}
return (
<div>
<button onClick={triggerAlert}>Show Alert Modal</button>
<div>{result}</div>
</div>
);
};
Confirmation
The Confirmation modal is a generic modal that will display a modal with a status color, optional icon, title, message and a confirm and cancel button. The results of the Confirmation modal can be captured as well (confirm, cancel or close (as a reject)).
import React, { useState, useContext } from 'react';
import { ModalNextContext } from '@jutro/components';
const ConfirmationModalExample = () => {
const [result, setResult] = useState(null);
const { showConfirm } = useContext(ModalNextContext);
async function triggerConfirmation() {
const results = await showConfirm({
status: 'info' ,
icon: 'gw-error-outline' ,
title:
'Test Confirm Modal' ,
message:
'Just testing a Confirmation Modal!' ,
confirmButtonText:
'OK' ,
cancelButtonText:
'Cancel' ,
});
setResult(`modal result was: ${results}`);
}
return (
<div>
<button onClick={triggerConfirmation}>Show Confirmation Modal</button>
<div>{result}</div>
</div>
);
};
Custom modals
Custom modals can be implemented with the ModalNext
, ModalHeader
, ModalBody
and ModalFooter
components, and displayed using showModal
from ModalNextContext
similar to the usage above for the generic modals.