Interactive Modal Component in React with TypeScript
Develop a reusable and accessible modal component in React using TypeScript. This component will be a fundamental building block for modern web applications, enabling users to view important information or take actions without navigating away from the current page.
Problem Description
You are tasked with creating a React functional component called Modal that displays content within a modal overlay. This component should be highly customizable and adhere to accessibility best practices.
Key Requirements:
- Component Structure: The
Modalcomponent should acceptchildrenas its content, aisOpenboolean prop to control its visibility, anonClosefunction prop to handle closing the modal, and atitlestring prop for the modal's header. - Visibility Control: The modal should only be rendered when
isOpenistrue. - Content Display: The
childrenprop should be rendered within the modal's body. - Closing Mechanism:
- A close button (e.g., an "X" icon) must be present in the modal header. Clicking this button should trigger the
onClosefunction. - Clicking outside the modal content (on the overlay) should also trigger the
onClosefunction. - Pressing the
Escapekey should also trigger theonClosefunction.
- A close button (e.g., an "X" icon) must be present in the modal header. Clicking this button should trigger the
- Accessibility:
- The modal should be focusable. When opened, focus should be managed within the modal.
- The modal should have appropriate ARIA attributes (
role="dialog",aria-modal="true",aria-labelledby).
- Styling: Provide basic, presentational CSS (or a mechanism to pass styles) to ensure the modal is visually distinct, overlays the page, and has a defined structure (header, body). You can use inline styles or a separate CSS file.
- TypeScript: All component props and internal logic must be typed using TypeScript.
Expected Behavior:
- When
isOpenisfalse, the modal should not be rendered. - When
isOpenistrue, the modal overlay and its content should appear. - Clicking the close button, clicking outside the modal content, or pressing
Escapeshould close the modal by calling theonClosefunction. - Focus should be trapped within the modal when it's open.
- The modal should have a title displayed in its header.
Edge Cases:
- Empty Children: The modal should gracefully handle cases where
childrenis empty ornull. - Modal with no title: The component should still function correctly if the
titleprop is omitted or an empty string.
Examples
Example 1: Basic Modal Usage
import React, { useState } from 'react';
import Modal from './Modal'; // Assuming Modal is in ./Modal.tsx
function App() {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
return (
<div>
<h1>My App</h1>
<button onClick={openModal}>Open Modal</button>
<Modal
isOpen={isModalOpen}
onClose={closeModal}
title="Welcome!"
>
<p>This is the content of my first modal.</p>
<p>It can contain any React elements!</p>
</Modal>
</div>
);
}
export default App;
Expected Output (when "Open Modal" button is clicked):
A modal overlay appears covering the page. In the center, a modal box is displayed with:
- A header containing the title "Welcome!" and a close button (e.g., "X").
- A body containing the text "This is the content of my first modal." and "It can contain any React elements!".
Clicking the "X" button, clicking the overlay behind the modal, or pressing Escape will close the modal.
Example 2: Modal with Different Content
import React, { useState } from 'react';
import Modal from './Modal';
function AnotherComponent() {
const [showInfoModal, setShowInfoModal] = useState(false);
const openInfoModal = () => setShowInfoModal(true);
const closeInfoModal = () => setShowInfoModal(false);
return (
<div>
<button onClick={openInfoModal}>Show Info</button>
<Modal
isOpen={showInfoModal}
onClose={closeInfoModal}
title="Information"
>
<h2>More Details</h2>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<button onClick={closeInfoModal}>Got It</button>
</Modal>
</div>
);
}
export default AnotherComponent;
Expected Output (when "Show Info" button is clicked):
A modal overlay appears. The modal box has:
- A header with the title "Information" and a close button.
- A body containing "More Details", an unordered list with two items, and a "Got It" button.
Clicking the "X" button or the overlay will close the modal. Clicking the "Got It" button will also close the modal.
Example 3: Empty Modal Content
import React, { useState } from 'react';
import Modal from './Modal';
function EmptyModalExample() {
const [isModalVisible, setIsModalVisible] = useState(false);
const toggleModal = () => setIsModalVisible(!isModalVisible);
return (
<div>
<button onClick={toggleModal}>Toggle Empty Modal</button>
<Modal
isOpen={isModalVisible}
onClose={toggleModal}
title="Empty State"
>
{/* No children provided */}
</Modal>
</div>
);
}
export default EmptyModalExample;
Expected Output (when "Toggle Empty Modal" is clicked):
A modal overlay appears with a header "Empty State" and a close button. The body is empty. The modal should still be functional and dismissible.
Constraints
- The
Modalcomponent should be a functional component. - Use React Hooks for state management and lifecycle effects (e.g.,
useState,useEffect). - The solution must be written entirely in TypeScript.
- Do not use external modal libraries (e.g.,
react-modal,Material-UImodals). The goal is to build it from scratch. - Styling should be manageable. You can use CSS Modules, styled-components, or simple inline styles for presentation.
- The
onCloseprop will always be provided.
Notes
- Consider using
ReactDOM.createPortalto render the modal outside the DOM hierarchy of its parent, which can help with z-index issues and isolation. - For focus management, you'll need to:
- When the modal opens, move focus to the first focusable element within the modal (e.g., the close button or the first interactive element in
children). - When the modal closes, return focus to the element that triggered the modal (if possible and appropriate).
- Trap focus within the modal so users cannot tab out of it while it's open.
- When the modal opens, move focus to the first focusable element within the modal (e.g., the close button or the first interactive element in
- The
useEffecthook will be crucial for handling theEscapekey press and managing focus. - Think about how to structure your JSX to facilitate easy styling and accessibility.
- Ensure the overlay dimming effect is visually distinct from the modal content area.