Tips & Tricks (अपडेट: 2/6/2026)

Claude Code से accessible modal dialog बनाना: React, dialog और focus

Claude Code से modal dialog बनाएं: dialog element, React code, focus handling, common pitfalls, tests और accessibility.

Claude Code से accessible modal dialog बनाना: React, dialog और focus

Modal dialog वह अस्थायी layer है जो मौजूदा page के ऊपर खुलती है और user से छोटा निर्णय या input मांगती है। सही modal सिर्फ बीच में दिखने वाला box नहीं है। सही modal background को inactive करता है, keyboard focus को dialog के अंदर ले जाता है, predictable तरीके से बंद होता है, और बंद होने के बाद focus उसी button पर लौटाता है जिसने उसे खोला था।

अगर आप Claude Code से सिर्फ “एक अच्छा modal बना दो” कहेंगे, तो दिखने में ठीक component मिल सकता है, लेकिन उपयोग में टूट सकता है: Escape काम नहीं करता, Tab background में चला जाता है, screen reader title नहीं पढ़ता, या mobile पर footer button कट जाता है। यह guide practical brief, runnable examples, real use cases, pitfalls और tests देती है।

Official references साथ रखें: browser primitive के लिए MDN <dialog> element, keyboard behavior के लिए WAI-ARIA APG Modal Dialog Pattern, और focus quality के लिए WCAG Focus Order तथा Focus Visible। संबंधित लेख: accessibility implementation, Radix UI, command palette और toast notifications

Code लिखने से पहले निर्णय

Modal तब अच्छा है जब task छोटा हो और current context को रोकना जरूरी हो: delete confirmation, plan cancel, role change, invite form, checkout से पहले login, या command palette.

Long form, पूरा legal text, multi-page flow, aggressive ad और ऐसी सूचना जिसे user बाद में पढ़ सकता है, modal के लिए सही नहीं हैं। पहले तय करें: क्या page को रोकना जरूरी है? पहला focus कहां जाएगा? कौन सी action बंद करेगी? क्या 320px width पर footer usable है?

Plain words Claude Code को बेहतर दिशा देते हैं। Focus यानी keyboard की वर्तमान जगह। Focus trap यानी Tab movement को dialog के अंदर रखना। inert यानी background interactive नहीं है। ARIA यानी assistive technology को UI का अर्थ बताने वाले attributes।

flowchart TD
  A["User trigger दबाता है"] --> B["dialog.showModal() से खोलें"]
  B --> C["Focus title या पहली action पर भेजें"]
  C --> D["Tab, Shift+Tab, Escape जांचें"]
  D --> E["Confirm, cancel, backdrop अलग रखें"]
  E --> F["Focus trigger पर लौटाएं"]

Claude Code के लिए brief

Styling से पहले behavior लिखें।

Existing React + TypeScript screen में modal dialog जोड़ें।

Requirements:
- Edit करने से पहले existing buttons, forms, CSS और tests पढ़ें।
- HTML dialog element को प्राथमिकता दें; अगर suitable नहीं है तो कारण बताएं।
- Open होने पर focus title या पहली useful action पर ले जाएं।
- Escape, cancel, confirm और backdrop click को अलग-अलग handle करें।
- Close होने पर focus उस button पर लौटाएं जिसने modal खोला।
- aria-labelledby use करें; short description हो तो aria-describedby use करें।
- outline remove न करें; visible focus के लिए :focus-visible use करें।
- 320px width पर content और footer buttons usable रहें।
- Handoff में failure cases और manual verification steps लिखें।

Allowed files:
- src/components/ModalDialog.tsx
- src/components/modal-dialog.css
- tests/modal-dialog.spec.ts

Runnable HTML example

इसे modal-demo.html के रूप में save करके browser में खोलें।

<!doctype html>
<html lang="hi">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Dialog demo</title>
    <style>
      body {
        font-family: system-ui, sans-serif;
        line-height: 1.7;
        padding: 2rem;
      }

      button {
        font: inherit;
        border: 0;
        border-radius: 6px;
        padding: 0.7rem 1rem;
        cursor: pointer;
      }

      .danger {
        background: #dc2626;
        color: white;
      }

      dialog {
        width: min(calc(100vw - 2rem), 28rem);
        border: 0;
        border-radius: 8px;
        padding: 0;
        box-shadow: 0 24px 80px rgb(15 23 42 / 0.3);
      }

      dialog::backdrop {
        background: rgb(15 23 42 / 0.58);
      }

      .modal-body {
        padding: 1.25rem;
      }

      .button-row {
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-end;
        gap: 0.75rem;
        margin-top: 1.5rem;
      }

      :focus-visible {
        outline: 3px solid #f59e0b;
        outline-offset: 3px;
      }
    </style>
  </head>
  <body>
    <main>
      <h1>Project settings</h1>
      <p>Modal सिर्फ उन actions के लिए use करें जो page को रोकनी चाहिए।</p>
      <button id="open-dialog" class="danger" type="button">
        Project delete करें
      </button>
    </main>

    <dialog id="confirm-dialog" aria-labelledby="dialog-title">
      <div class="modal-body">
        <h2 id="dialog-title" tabindex="-1">इस project को delete करें?</h2>
        <p>यह action undo नहीं हो सकती। जरूरत हो तो पहले data export करें।</p>
        <div class="button-row">
          <button id="cancel-dialog" type="button">Cancel</button>
          <button id="confirm-delete" class="danger" type="button">
            Delete
          </button>
        </div>
      </div>
    </dialog>

    <script>
      const openButton = document.querySelector("#open-dialog");
      const dialog = document.querySelector("#confirm-dialog");
      const title = document.querySelector("#dialog-title");
      const cancelButton = document.querySelector("#cancel-dialog");
      const confirmButton = document.querySelector("#confirm-delete");

      openButton.addEventListener("click", () => {
        dialog.showModal();
        title.focus();
      });

      cancelButton.addEventListener("click", () => dialog.close("cancel"));

      confirmButton.addEventListener("click", () => {
        console.log("delete project");
        dialog.close("confirm");
      });

      dialog.addEventListener("click", (event) => {
        if (event.target === dialog) {
          dialog.close("backdrop");
        }
      });

      dialog.addEventListener("close", () => {
        openButton.focus();
        console.log(`closed by: ${dialog.returnValue || "unknown"}`);
      });
    </script>
  </body>
</html>

Modal खोलने के लिए showModal() use करें। सिर्फ open attribute लगाने से background interactive रह सकता है।

Reusable React component

import * as React from "react";
import "./modal-dialog.css";

type ModalDialogProps = {
  open: boolean;
  title: string;
  description?: string;
  closeOnBackdrop?: boolean;
  onClose: () => void;
  children: React.ReactNode;
  footer: React.ReactNode;
};

const focusableSelector = [
  "a[href]",
  "button:not([disabled])",
  "input:not([disabled])",
  "select:not([disabled])",
  "textarea:not([disabled])",
  "[tabindex]:not([tabindex='-1'])",
].join(",");

export function ModalDialog({
  open,
  title,
  description,
  closeOnBackdrop = true,
  onClose,
  children,
  footer,
}: ModalDialogProps) {
  const dialogRef = React.useRef<HTMLDialogElement>(null);
  const titleRef = React.useRef<HTMLHeadingElement>(null);
  const openerRef = React.useRef<HTMLElement | null>(null);
  const titleId = React.useId();
  const descriptionId = React.useId();

  React.useEffect(() => {
    const dialog = dialogRef.current;
    if (!dialog) return;

    if (open && !dialog.open) {
      openerRef.current =
        document.activeElement instanceof HTMLElement
          ? document.activeElement
          : null;
      dialog.showModal();

      window.requestAnimationFrame(() => {
        const preferred = dialog.querySelector<HTMLElement>("[data-autofocus]");
        const firstFocusable = dialog.querySelector<HTMLElement>(
          focusableSelector,
        );
        (preferred ?? firstFocusable ?? titleRef.current)?.focus();
      });
    }

    if (!open && dialog.open) {
      dialog.close();
    }
  }, [open]);

  React.useEffect(() => {
    const dialog = dialogRef.current;
    if (!dialog) return;

    function handleClose() {
      onClose();
      openerRef.current?.focus();
    }

    function handleClick(event: MouseEvent) {
      if (event.target === dialog && closeOnBackdrop) {
        onClose();
      }
    }

    dialog.addEventListener("close", handleClose);
    dialog.addEventListener("click", handleClick);

    return () => {
      dialog.removeEventListener("close", handleClose);
      dialog.removeEventListener("click", handleClick);
    };
  }, [closeOnBackdrop, onClose]);

  return (
    <dialog
      ref={dialogRef}
      className="app-modal"
      aria-labelledby={titleId}
      aria-describedby={description ? descriptionId : undefined}
    >
      <div className="app-modal__body">
        <div className="app-modal__header">
          <h2 id={titleId} ref={titleRef} tabIndex={-1}>
            {title}
          </h2>
          <button
            type="button"
            className="app-modal__icon"
            aria-label="Dialog close करें"
            onClick={onClose}
          >
            x
          </button>
        </div>

        {description ? (
          <p id={descriptionId} className="app-modal__description">
            {description}
          </p>
        ) : null}

        <div className="app-modal__content">{children}</div>
        <div className="app-modal__footer">{footer}</div>
      </div>
    </dialog>
  );
}
.app-modal {
  width: min(calc(100vw - 32px), 520px);
  max-height: calc(100vh - 32px);
  border: 0;
  border-radius: 8px;
  padding: 0;
  color: #0f172a;
  box-shadow: 0 24px 80px rgb(15 23 42 / 0.3);
}

.app-modal::backdrop {
  background: rgb(15 23 42 / 0.58);
}

.app-modal__body {
  display: grid;
  gap: 16px;
  padding: 24px;
}

.app-modal__header,
.app-modal__footer {
  display: flex;
  gap: 12px;
}

.app-modal__header {
  align-items: flex-start;
  justify-content: space-between;
}

.app-modal__footer {
  flex-wrap: wrap;
  justify-content: flex-end;
}

.app-modal__icon {
  width: 36px;
  height: 36px;
  border: 0;
  border-radius: 999px;
  background: #e2e8f0;
  cursor: pointer;
}

:focus-visible {
  outline: 3px solid #f59e0b;
  outline-offset: 3px;
}

@media (max-width: 480px) {
  .app-modal__footer {
    flex-direction: column-reverse;
  }

  .app-modal__footer button {
    width: 100%;
  }
}

तीन real use cases

Use caseModal क्यों सही हैClaude Code से क्या कहलवाएं
Delete, cancel, role changeAction undo करना मुश्किल हैDanger copy, double-submit guard, audit log
Invite, billing, short settingsContext छोड़े बिना task पूरा होता हैValidation, pending state, focus after success
Command palette या quick searchNavigation के बिना तेज actionArrow keys, aria-activedescendant, empty state

Dangerous action में backdrop click से close करना हमेशा सही नहीं होता। Short form में error आने पर modal खुला रखें और error readable बनाएं।

Promise-based confirmation

import * as React from "react";
import { createRoot } from "react-dom/client";
import { ModalDialog } from "./ModalDialog";

type ConfirmDialogOptions = {
  title: string;
  message: string;
  confirmLabel?: string;
  cancelLabel?: string;
  danger?: boolean;
};

export function confirmDialog(
  options: ConfirmDialogOptions,
): Promise<boolean> {
  return new Promise((resolve) => {
    const container = document.createElement("div");
    document.body.appendChild(container);
    const root = createRoot(container);

    function finish(result: boolean) {
      root.unmount();
      container.remove();
      resolve(result);
    }

    function ConfirmHost() {
      return (
        <ModalDialog
          open
          title={options.title}
          description={options.message}
          closeOnBackdrop={false}
          onClose={() => finish(false)}
          footer={
            <>
              <button type="button" onClick={() => finish(false)}>
                {options.cancelLabel ?? "Cancel"}
              </button>
              <button
                type="button"
                data-autofocus
                className={options.danger ? "danger" : "primary"}
                onClick={() => finish(true)}
              >
                {options.confirmLabel ?? "Confirm"}
              </button>
            </>
          }
        >
          <p>Continue करने से पहले details check करें।</p>
        </ModalDialog>
      );
    }

    root.render(<ConfirmHost />);
  });
}

Common pitfalls

पहला pitfall है mouse-only close control। वास्तविक button use करें और icon-only button को accessible name दें।

दूसरा pitfall है title हटाना। Dialog को accessible name चाहिए, इसलिए aria-labelledby use करें।

तीसरा pitfall है outline: none बिना replacement। :focus-visible से focus दिखाएं।

चौथा pitfall है modal के ऊपर modal। इससे focus return और Escape unclear हो जाते हैं। Clear copy या undo बेहतर हो सकता है।

पांचवां pitfall है mobile overflow। max-height, overflow: auto और 320px manual check जरूरी है।

Playwright smoke test

import { expect, test } from "@playwright/test";

test("modal opens, closes, and returns focus", async ({ page }) => {
  await page.goto("/settings");

  const trigger = page.getByRole("button", { name: "Project delete करें" });
  await trigger.click();

  const dialog = page.getByRole("dialog", {
    name: "इस project को delete करें?",
  });
  await expect(dialog).toBeVisible();

  await page.keyboard.press("Tab");
  await expect(page.locator(":focus")).toBeVisible();

  await page.keyboard.press("Escape");
  await expect(dialog).toBeHidden();
  await expect(trigger).toBeFocused();
});

Manual QA में mouse न इस्तेमाल करें। Tab, Shift+Tab, Enter, Space, Escape से चलाएं, फिर NVDA या VoiceOver से title और buttons पढ़वाएं, और narrow mobile viewport देखें।

CTA और monetization

Modal अक्सर revenue path के पास होता है: checkout confirmation, consultation form, email capture, training request. इसलिए उसे aggressive ad जैसा न बनाएं। Clear, short और respectful रखें।

Teams के लिए Claude Code rollout, CLAUDE.md, accessible UI review और React workflow cleanup में Claude Code training and consultation मदद कर सकता है। Individual builders products और free cheatsheet से prompts, reviews और tests standardize कर सकते हैं।

Result

Masa ने small React settings screen में इस pattern को आजमाया। सबसे ज्यादा सुधार animation से नहीं, बल्कि acceptance criteria से आया: focus opener पर लौटे, dangerous action backdrop click से close न हो, और 320px width पर footer buttons usable रहें। इन तीन शर्तों से Claude Code की output review करना काफी आसान हुआ।

#Claude Code #modal #dialog #React #accessibility
मुफ़्त

मुफ़्त PDF: Claude Code cheatsheet

Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.

हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.

Masa

लेखक के बारे में

Masa

Claude Code workflow और team adoption पर काम करने वाला engineer.