Back to cookbook

AI Prompt to Build an Accessible React Modal Component

2 views Updated
Share

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 isOpen prop and onClose callback
  • 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", 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 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}>
<div
className="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.

Found this prompt useful? Share it.

Share

More in React Prompt Recipes

React Prompt Recipes

AI Prompt to Turn a Design Screenshot into a React Component

This is a React prompt for converting a design screenshot or mockup into working React component code — built for frontend developers who ge…

ROLE: You are a frontend developer converting a design image into a React component.

CONTEXT: I'm attaching a design screenshot/mockup. Tech stack: [REACT VERSION, STYLING APPROACH e.g. Tailwind/CSS Modules/styled-components, COMPONENT LIBRARY IF ANY]

TASK:
1. First, describe what you see in the image: overall layout, spacing, typography (font sizes/weights), colors, and any visible interactive states
2. Flag anything you're uncertain about (illegible text, ambiguous spacing, colors that are hard to read precisely) instead of guessing silently
3. Then write the React component matching this description, split into: component structure (JSX), styling, and any props needed to make it reusable

CONSTRAINTS:
- Match the layout and spacing as closely as possible to what's visible in the image
- Use [STYLING APPROACH] only, no inline styles unless the project convention requires it
- Make the component accept props for any content that would realistically vary (text, images, links)
- [ANY ADDITIONAL CONSTRAINT, e.g. must be accessible, must support dark mode]

OUTPUT FORMAT:
1. A short description of the design (layout, spacing, typography, colors, flagged uncertainties)
2. The complete component code in a fenced code block
3. A one-line note on anything that will need visual verification once rendered
React Prompt Recipes

ChatGPT Prompt to Build a Reusable React Form Component with Validation

This ChatGPT prompt generates a reusable React form component with built in field validation, meant for frontend developers who want a worki…

Role: You are a frontend engineer who writes clean, reusable React components.

Context:
- Component purpose: [E.G., SIGNUP FORM, CONTACT FORM, SETTINGS FORM]
- Fields and validation rules: [FIELD NAME: TYPE AND RULE, repeat for each field, e.g., "email: required, must match email format"]
- State approach: [PLAIN useState, TYPESCRIPT, OR A LIBRARY LIKE REACT HOOK FORM]
- Styling approach: [CSS MODULES, TAILWIND, PLAIN CSS, OR INLINE STYLES]
- Submit behavior: [WHAT SHOULD HAPPEN ON VALID SUBMIT, E.G., "CALL AN onSubmit PROP WITH FORM VALUES"]

Constraints:
- Component must be self-contained and reusable, accepting props for initial values and onSubmit
- Each field must show its own inline error message that clears once the field becomes valid
- Submit button must be disabled while any required field is invalid

Task: Write the complete React component code implementing the form above.

Output format:
1. The full component code in a single fenced code block
2. A short explanation of the validation logic and how to use the component
3. Any assumptions made about props or styling