A modal is a small UI element that will appear in the foreground of a website, usually triggered as a prompt for the user to do something
Let's build an ideal modal component in react from basics to advance
Table Of Contents
- Creating a basic modal
- Styling modal
- Closing the modal
- Hardware backbutton to close the modal
- Making Modals More Usable And Accessible
1. Creating a basic modal
A basic modal involves creating an overlay and inside the overlay, we render the modal component which will include the children passed by the consumer.
const Modal = ({
position, // set the position of modal on viewport
isFullScreen,
modalStyle,
containerStyle,
height,
children,
}) => {
return (
<ModalOverlay style={containerStyle}>
<ModalComponent
position={position}
isFullScreen={isFullScreen}
customHeight={height}
style={modalStyle}
>
{children}
</ModalComponent>
</ModalOverlay>
);
};
Modal.defaultProps = {
position: "center",
isFullScreen: false,
height: "auto",
modalStyle: {},
containerStyle: {},
};
Modal.propTypes = {
position: PropTypes.oneOf(["center", "top", "bottom"]),
isFullScreen: PropTypes.bool,
height: PropTypes.string,
modalStyle: PropTypes.shape({}),
containerStyle: PropTypes.shape({}),
children: PropTypes.node.isRequired,
};
2. Styling modal
For styling I have used styled-component
Since we have props such as position
, height
, isFullScreen
we need to have conditional styling.
const ModalOverlay = styled.div`
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
opacity: 1;
position: fixed;
overflow-x: hidden;
overflow-y: auto;
background-color: rgba(34, 34, 34, 0.8);
`;
const ModalComponent = styled.div`
position: ${(props) =>
props.position !== "center" ? "absolute" : "relative"};
top: ${(props) => (props.position === "top" ? "0" : "auto")};
bottom: ${(props) => (props.position === "bottom" ? "0" : "auto")};
height: ${(props) => (props.isFullScreen ? "100%" : props.customHeight)};
max-height: ${(props) => (props.isFullScreen ? "100%" : props.customHeight)};
width: 100%;
`;
3. Closing the modal
There are three ways to close a modal
- Pressing ESC key
- Clicking outside of the modal body
- Clicking on close icon or button on the modal body which closes the modal
const Modal = ({ close, children }) => {
const modalRef = useRef();
const modalOverlayRef = useRef();
const handleClose = () => {
close();
};
const handleClick = (event) => {
if (modalRef.current && !modalRef.current.contains(event.target)) {
handleClose();
}
};
const handleKeyDown = (event) => {
if (event.keyCode === 13) {
return handleClose();
}
};
useEffect(() => {
const modalOverlayRefCurrent = modalOverlayRef.current;
modalOverlayRefCurrent.addEventListener("click", handleClick);
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("keydown", handleKeyDown);
modalOverlayRefCurrent.removeEventListener("click", handleClick);
};
}, []);
return (
<ModalOverlay ref={modalOverlayRef}>
<ModalComponent ref={modalRef}>{children}</ModalComponent>
</ModalOverlay>
);
};
4. Hardware backbutton to close the modal
One of the most searched questions on modals is how to close the modal by clicking the hardware back button on a mobile device.
One solution which I found to be working well is to leverage react-router
in order to achieve it
We usually use state variable to show/hide the modal something like this
const [isModalVisible, setIsModalVisible] = useState(false)
const handleShowModal = () => {
setIsModalVisible(true)
}
return isModalVisible ? <Modal/> : null
We need to change the way we show/hide the modal component, instead of changing the state variable we will push a new route with a state variable
like this
import { useHistory } from 'react-router-dom'
const historyHook = useHistory()
const handleShowModal = () => {
history.push(window.location.pathname, { isModalVisible: true })
}
return historyHook.location.state.isModalVisible ? <Modal /> : null
Now when the user clicks on to show the modal a new route is pushed
with the same pathname but with a state variable named isModalVisible
Then, when a user clicks on the back button it will remove the route from the history stack thus closing the modal or we can simply call the below function
window.history.back() // closes the modal
5. Making Modals More Usable And Accessible
Basic accessibility is a prerequisite for usability.
An accessible modal dialog is one where keyboard focus is managed properly, and the correct information is exposed to screen readers.
HTML
and WAI-ARIA
((Web Accessibility Initiative - Accessible Rich Internet Applications)) can be used to provide the necessary semantic information, CSS the appearance, and Javascript the behavior.
Three basics point to achieve accessibility in modal are:
-> Basic semantics has to be followed
The modal itself must be constructed from a combination of HTML and WAI-ARIA attributes, as in this example:
<div id="dialog" role="dialog" aria-labelledby="title" aria-describedby="description">
<h1 id="title">Title</h1>
<p id="description">Information</p>
<button id="close" aria-label="close">×</button>
</div>
Note the dialog role, which tells assistive technologies that the element is a dialog.
The aria-labelledby
and aria-describedby
attributes are relationship attributes that connect the dialog to its title and description explicitly.
So when the focus is moved to the dialog or inside it, the text within those two elements will be read in succession.
-> Saving last active element
When a modal window loads, the element that the user last interacted with should be saved.
That way, when the modal window closes and the user returns to where they were, the focus on that element will have been maintained.
let lastFocus;
function handleShowModal () {
lastFocus = document.activeElement; // save activeElement
}
function handleCloseModal () {
lastFocus.focus(); // place focus on the saved element
}
-> Shifting focus from main content to modal
When the modal loads, the focus should shift from the last active element either to the modal window itself or to the first interactive element in the modal, such as an input element.
const modal = document.getElementById('modal-id');
function modalShow () {
modal.setAttribute('tabindex', '0');
modal.focus();
}
Conclusion
Component creation often involves multiple points to be kept in mind, right from creating a basic structure to solving common and complex problems such as accessibility and usability.
The article covers most parts of a modal and its uses and can easily be integrated into a live project.
Top comments (2)
Nice article! You should probably also trap focus inside the modal. Otherwise users will be able to tab outside the modal using the keyboard. This is usually not desirable.
You can do this by mapping over all focusable elements in the modal, and once the user tab from the last element, the first element should gain focus. Furthermore shift-tab from first element should give focus to last element.
I just finished of a project written in React with a lot of different modals. Check it out at bruce-willis.rocks/en/ - there is even a url to the sourceCode at github 😊
@larsejaas , completely agree trapping focus inside the modal is another important point to improve accessibility. Thanks for the solution as well. :)