react-modal
Skill1 starsChanged 7 months agoMITView on GitHub
Create modal dialogs in the frontend using a custom Modal component built on top of Radix UI Dialog. Use this skill whenever the user asks to create, add, or modify a modal, dialog, popup, or confirmation prompt in the React application.
- Installs packages
1 other project uses a version of this.
Adopted by
Most recently taken up by
- emaginebr/AvaBotword for word, first seen Apr 8, 2026
Other versions1 project uses a version of this.
Word for word · 1
- emaginebr/AvaBot★ 2100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository39 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- readme-generatorSkill
- add-react-i18nSkill
- band-album-campaignSkill
- band-music-reflectionSkill
- band-streaming-launchSkill
- ckm:banner-designSkill
- ckm:brandSkill
- deploy-prodSkill
- ckm:designSkill
- ckm:design-systemSkill
- docker-compose-configSkill
- doc-managerSkill
- dotnet-architectureSkill
- dotnet-doc-controllerSkill
- dotnet-envSkill
- dotnet-fluent-validationSkill
- dotnet-graphqlSkill
- dotnet-multi-tenantSkill
- dotnet-test-apiSkill
- dotnet-testSkill
- frontend-designSkill
- gitversionSkill
- maui-architectureSkill
- mermaid-chartSkill
- nauth-guideSkill
- nnews-guideSkill
- react-alertSkill
- react-architectureSkill
- react-archSkill
- readme-generatorSkill
- ckm:slidesSkill
- social-media-assetsSkill
- social-media-schedulingSkill
- social-post-authoringSkill
- ckm:ui-stylingSkill
- ui-ux-pro-maxSkill
- wsl-dockerSkill
- ztools-guideSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 1 is distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 10
Before creating any modal, ensure @radix-ui/react-dialog is installed:
- Distinctive: line 330
When you need to control the modal state programmatically (e.g., close after an async action):
- Very specific: line 386
Always include a ModalTitle for accessibility.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
This skill defines the standard approach for creating modal dialogs in this project. All modals MUST use the custom Modal component located at src/components/ui/Modal.tsx, which wraps Radix UI Dialog primitives.
Prerequisites
Before creating any modal, ensure @radix-ui/react-dialog is installed:
npm list @radix-ui/react-dialog || npm install @radix-ui/react-dialogThe Modal Component
The base Modal component lives at src/components/ui/Modal.tsx. If it does not exist yet, create it with the following implementation:
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { cn } from "../../lib/utils";
const Modal = DialogPrimitive.Root;
const ModalTrigger = DialogPrimitive.Trigger;
const ModalPortal = DialogPrimitive.Portal;
const ModalClose = DialogPrimitive.Close;
const ModalOverlay = React.forwardRef<
React.ComponentRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/60 backdrop-blur-sm",
"data-[state=open]:animate-in data-[state=open]:fade-in-0",
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
className
)}
{...props}
/>
));
ModalOverlay.displayName = "ModalOverlay";
const ModalContent = React.forwardRef<
React.ComponentRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<ModalPortal>
<ModalOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-1/2 top-1/2 z-50 w-full max-w-lg -translate-x-1/2 -translate-y-1/2",
"bg-white dark:bg-gray-800 rounded-xl shadow-xl border border-gray-200 dark:border-gray-700",
"p-6 duration-200",
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]",
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]",
className
)}
{...props}
>
{children}
</DialogPrimitive.Content>
</ModalPortal>
));
ModalContent.displayName = "ModalContent";
const ModalHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-1.5 mb-4", className)} {...props} />
);
ModalHeader.displayName = "ModalHeader";
const ModalFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("flex justify-end gap-3 mt-6", className)}
{...props}
/>
);
ModalFooter.displayName = "ModalFooter";
const ModalTitle = React.forwardRef<
React.ComponentRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-gray-900 dark:text-white", className)}
{...props}
/>
));
ModalTitle.displayName = "ModalTitle";
const ModalDescription = React.forwardRef<
React.ComponentRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-gray-600 dark:text-gray-400", className)}
{...props}
/>
));
ModalDescription.displayName = "ModalDescription";
export {
Modal,
ModalPortal,
ModalOverlay,
ModalTrigger,
ModalClose,
ModalContent,
ModalHeader,
ModalFooter,
ModalTitle,
ModalDescription,
};The ConfirmModal Component
The ConfirmModal is a specialized, ready-to-use confirmation dialog that lives at src/components/ui/ConfirmModal.tsx. It replaces all uses of window.confirm(). If it does not exist yet, create it with the following implementation:
import * as React from "react";
import {
Modal,
ModalContent,
ModalHeader,
ModalFooter,
ModalTitle,
ModalDescription,
ModalClose,
} from "./Modal";
import { cn } from "../../lib/utils";
type ConfirmVariant = "danger" | "warning" | "default";
interface ConfirmModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onConfirm: () => void | Promise<void>;
title: string;
description?: string;
confirmLabel?: string;
cancelLabel?: string;
variant?: ConfirmVariant;
loading?: boolean;
}
const variantStyles: Record<ConfirmVariant, string> = {
danger:
"bg-red-600 hover:bg-red-700 text-white focus:ring-red-500",
warning:
"bg-yellow-500 hover:bg-yellow-600 text-white focus:ring-yellow-400",
default:
"bg-blue-600 hover:bg-blue-700 text-white focus:ring-blue-500",
};
export function ConfirmModal({
open,
onOpenChange,
onConfirm,
title,
description,
confirmLabel = "Confirm",
cancelLabel = "Cancel",
variant = "default",
loading = false,
}: ConfirmModalProps) {
const handleConfirm = async () => {
await onConfirm();
onOpenChange(false);
};
return (
<Modal open={open} onOpenChange={onOpenChange}>
<ModalContent className="max-w-md">
<ModalHeader>
<ModalTitle>{title}</ModalTitle>
{description && (
<ModalDescription>{description}</ModalDescription>
)}
</ModalHeader>
<ModalFooter>
<ModalClose asChild>
<button
type="button"
disabled={loading}
className="px-4 py-2 rounded-lg text-sm font-medium bg-gray-100 hover:bg-gray-200 text-gray-700 dark:bg-gray-700 dark:hover:bg-gray-600 dark:text-gray-200 transition-colors focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2"
>
{cancelLabel}
</button>
</ModalClose>
<button
type="button"
onClick={handleConfirm}
disabled={loading}
className={cn(
"px-4 py-2 rounded-lg text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed",
variantStyles[variant]
)}
>
{loading ? "Loading..." : confirmLabel}
</button>
</ModalFooter>
</ModalContent>
</Modal>
);
}Usage Rules
- Always use the
Modalcomponent fromsrc/components/ui/Modal.tsxfor any modal, dialog, or popup — unless the user explicitly requests an exception. - Always use
ConfirmModalfromsrc/components/ui/ConfirmModal.tsxfor any confirmation prompt (e.g., "Are you sure you want to delete?"). Never build confirmation dialogs manually fromModalprimitives. - Never use raw Radix UI Dialog primitives directly in page or feature components. Always go through the
ModalorConfirmModalwrappers. - Never use
window.alert(),window.confirm(), orwindow.prompt()— useConfirmModalfor confirmations, theModalcomponent for other dialogs, andtoastfromsonnerfor alerts. - Never create a custom modal from scratch with manual overlay/backdrop logic. The Modal component already handles overlay, backdrop blur, animations, focus trapping, and accessibility.
Usage Patterns
ConfirmModal — Confirmation prompt (replaces window.confirm())
This is the standard way to ask the user to confirm a destructive or important action:
import * as React from "react";
import { ConfirmModal } from "@/components/ui/ConfirmModal";
import { toast } from "sonner";
function DeleteUserButton({ userId }: { userId: string }) {
const [open, setOpen] = React.useState(false);
const [loading, setLoading] = React.useState(false);
const handleDelete = async () => {
setLoading(true);
try {
await deleteUser(userId);
toast.success("User deleted successfully!");
} catch (error) {
toast.error("Failed to delete user.");
} finally {
setLoading(false);
}
};
return (
<>
<button onClick={() => setOpen(true)}>Delete User</button>
<ConfirmModal
open={open}
onOpenChange={setOpen}
onConfirm={handleDelete}
title="Delete User"
description="Are you sure you want to delete this user? This action cannot be undone."
confirmLabel="Delete"
cancelLabel="Cancel"
variant="danger"
loading={loading}
/>
</>
);
}ConfirmModal variants:
variant="danger"— Red button. Use for destructive actions (delete, remove, revoke).variant="warning"— Yellow button. Use for risky but reversible actions.variant="default"— Blue button. Use for neutral confirmations.
General Modal — Custom content dialogs
For modals with custom content (forms, detail views, etc.):
import {
Modal,
ModalTrigger,
ModalContent,
ModalHeader,
ModalFooter,
ModalTitle,
ModalDescription,
ModalClose,
} from "@/components/ui/Modal";
function Example() {
return (
<Modal>
<ModalTrigger asChild>
<button>Open Modal</button>
</ModalTrigger>
<ModalContent>
<ModalHeader>
<ModalTitle>Modal Title</ModalTitle>
<ModalDescription>
A brief description of what this modal does.
</ModalDescription>
</ModalHeader>
{/* Modal body content goes here */}
<div>Your content here</div>
<ModalFooter>
<ModalClose asChild>
<button>Cancel</button>
</ModalClose>
<button>Confirm</button>
</ModalFooter>
</ModalContent>
</Modal>
);
}Controlled Modal (programmatic open/close)
When you need to control the modal state programmatically (e.g., close after an async action):
function ControlledExample() {
const [open, setOpen] = React.useState(false);
const handleConfirm = async () => {
await someAsyncAction();
setOpen(false);
};
return (
<Modal open={open} onOpenChange={setOpen}>
<ModalTrigger asChild>
<button>Open</button>
</ModalTrigger>
<ModalContent>
<ModalHeader>
<ModalTitle>Confirm Action</ModalTitle>
</ModalHeader>
<ModalFooter>
<ModalClose asChild>
<button>Cancel</button>
</ModalClose>
<button onClick={handleConfirm}>Confirm</button>
</ModalFooter>
</ModalContent>
</Modal>
);
}When to use which
| Scenario | Component |
| "Are you sure you want to delete?" | ConfirmModal |
| "Discard unsaved changes?" | ConfirmModal |
| Edit form in a dialog | Modal |
| Detail/preview popup | Modal |
| Alert/notification message | toast from sonner (not a modal) |
Styling
- The Modal component follows the same styling conventions as other
src/components/ui/components (Tailwind CSS,cn()utility, dark mode support viadark:variants). - Override styles by passing
classNameto any Modal sub-component. - The default width is
max-w-lg. Pass a differentclassNametoModalContentto change it (e.g.,className="max-w-2xl"for wider modals).
Accessibility
The Radix UI Dialog primitive already provides:
- Focus trapping within the modal
Escapekey to close- Click outside (overlay) to close
- Proper
aria-attributes viaModalTitleandModalDescription
Always include a ModalTitle for accessibility. If the title should be visually hidden, use sr-only class.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

