AI Prompt to Build an Accessible React Modal Component
This prompt generates an accessible React modal component, built for frontend developers who need a dialog that actually works with keyboards and screen readers instead of just looking right visually. Modals are deceptively easy to get wrong: focus often escapes behind the overlay, the Escape key does nothing, and screen readers announce the whole page instead of the dialog content.
The prompt specifies the accessibility behavior up front rather than leaving it as an afterthought: focus trapping inside the modal while it's open, returning focus to the triggering element on close, closing on Escape and on an overlay click, and the correct role and aria-* attributes so assistive technology announces the dialog properly. It also asks for the component to be unstyled or minimally styled so it drops into an existing design system without fighting your CSS.
Because accessibility bugs in interactive components are easy to miss in a quick visual review, it's worth testing the generated code with a keyboard-only pass (Tab, Shift+Tab, Escape) before shipping it.
Prompt template
ROLE: You are a senior frontend engineer writing accessible React components.
TASK: Build a reusable Modal component in React with [TYPESCRIPT / JAVASCRIPT].
FUNCTIONAL REQUIREMENTS:
- Opens and closes based on an
isOpenprop andonClosecallback - Renders via [REACT PORTAL / INLINE] into [TARGET DOM NODE OR DESCRIPTION]
- Closes on Escape key press
- Closes on clicking the overlay background (but not when clicking inside the modal content)
ACCESSIBILITY REQUIREMENTS:
- Traps keyboard focus inside the modal while it is open
- Returns focus to the element that triggered the modal when it closes
- Uses
role="dialog",aria-modal="true", andaria-labelledbypointing to the modal title - Prevents background content from being read by screen readers while the modal is open
STYLING:
- [UNSTYLED WITH CLASS NAMES / USE TAILWIND / MATCH THIS DESIGN SYSTEM: DESCRIBE]
OUTPUT FORMAT: Provide the complete component code in a single file, followed by a short usage example showing how to open the modal from a parent component.
Example input
ROLE: You are a senior frontend engineer writing accessible React components.
TASK: Build a reusable Modal component in React with TypeScript.
FUNCTIONAL REQUIREMENTS:
- Opens and closes based on an isOpen prop and onClose callback
- Renders via a React portal into document.body
- Closes on Escape key press
- Closes on clicking the overlay background (but not when clicking inside the modal content)
ACCESSIBILITY REQUIREMENTS:
- Traps keyboard focus inside the modal while it is open
- Returns focus to the element that triggered the modal when it closes
- Uses role="dialog", aria-modal="true", and aria-labelledby pointing to the modal title
- Prevents background content from being read by screen readers while the modal is open
STYLING:
- Unstyled, with plain class names (modal-overlay, modal-content) so I can style it myself
OUTPUT FORMAT: Provide the complete component code in a single file, followed by a short usage example.
Example output
import { useEffect, useRef } from "react";import { createPortal } from "react-dom";interface ModalProps {isOpen: boolean;onClose: () => void;titleId: string;children: React.ReactNode;}export function Modal({ isOpen, onClose, titleId, children }: ModalProps) {const contentRef = useRef<HTMLDivElement>(null);const triggerRef = useRef<HTMLElement | null>(null);useEffect(() => {if (isOpen) {triggerRef.current = document.activeElement as HTMLElement;contentRef.current?.focus();} else {triggerRef.current?.focus();}}, [isOpen]);useEffect(() => {if (!isOpen) return;function handleKeyDown(e: KeyboardEvent) {if (e.key === "Escape") onClose();if (e.key === "Tab") {const focusable = contentRef.current?.querySelectorAll<HTMLElement>('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');if (!focusable || focusable.length === 0) return;const first = focusable[0];const last = focusable[focusable.length - 1];if (e.shiftKey && document.activeElement === first) {e.preventDefault();last.focus();} else if (!e.shiftKey && document.activeElement === last) {e.preventDefault();first.focus();}}}document.addEventListener("keydown", handleKeyDown);return () => document.removeEventListener("keydown", handleKeyDown);}, [isOpen, onClose]);if (!isOpen) return null;return createPortal(<div className="modal-overlay" onClick={onClose} aria-hidden={false}><divclassName="modal-content"role="dialog"aria-modal="true"aria-labelledby={titleId}ref={contentRef}tabIndex={-1}onClick={(e) => e.stopPropagation()}>{children}</div></div>,document.body);}
Usage:
const [isOpen, setIsOpen] = useState(false);<button onClick={() => setIsOpen(true)}>Open settings</button><Modal isOpen={isOpen} onClose={() => setIsOpen(false)} titleId="settings-title"><h2 id="settings-title">Settings</h2><p>Modal content goes here.</p><button onClick={() => setIsOpen(false)}>Close</button></Modal>
When to use it
- Building a confirmation dialog, settings panel, or image lightbox that needs to meet accessibility requirements
- Replacing a third-party modal library with a lightweight custom component
- Auditing an existing modal that doesn't trap focus or respond to the Escape key
- Teaching a junior developer what a fully accessible modal implementation actually requires
Best practices
- Explicitly ask for focus trapping and focus return behavior; most generated components skip this unless asked directly
- Specify whether you're using React state, a portal (
createPortal), or a specific library convention your codebase already follows - Request keyboard behavior (Escape to close, Tab cycling) as a named requirement, not an assumption
- Ask for the component to be unstyled or styled with plain CSS classes so it's easy to adapt to your existing design system
- If you're converting a design file into this component visually, Site to Prompt can turn a real rendered reference page into a structured prompt describing its layout and spacing
Common mistakes
- Not asking for focus trapping, which lets keyboard users Tab out of an open modal into the page behind it
- Forgetting to specify that focus should return to the trigger button when the modal closes
- Leaving out the Escape key handler, which many users expect as a standard way to close any dialog
- Over-specifying visual styling in the prompt instead of behavior, resulting in a component that looks right but fails basic accessibility checks
FAQs
How do I make a React modal accessible to screen readers?
Use role="dialog" and aria-modal="true" on the modal container, point aria-labelledby at the modal's title element, and make sure focus moves into the modal when it opens so screen readers announce it correctly.
What is focus trapping and why does a modal need it?
Focus trapping keeps keyboard Tab navigation cycling within the modal's interactive elements instead of letting it escape into the page behind the overlay, which would otherwise confuse keyboard and screen reader users about what's currently open.
Should a modal close on Escape key by default?
Yes, closing on Escape is a widely expected behavior for dialogs and should be included unless the modal requires an explicit confirmation action before closing, such as a form with unsaved changes.
Can ChatGPT or Claude generate a modal that works with an existing design system?
Both can generate a modal styled to match a described design system if you specify the class names, CSS framework, or component library conventions in the prompt rather than leaving styling unspecified.
What tool can help me verify this generated prompt covers all the edge cases?
Prompt Debugger — scans the prompt for missing constraints or vague requirements, such as unspecified keyboard behavior, before you use it to generate production code.