Designing and Implementing Modal Dialogs: Claude Code 활용 가이드
designing and implementing modal dialogs: Claude Code 활용. 실용적인 코드 예시를 포함합니다.
모달・다이얼로그の설계原則
모달は사용자の주의を特定の操作に集中させるUIパターンです。하지만、포커스관리やキーボード操作、スクリーンリーダー대응を怠ると、アクセシビリティの問題を引き起こします。Claude Code를 활용하면 WAI-ARIA準拠の모달を올바르게구현할 수 있습니다。
HTML dialog要素를 사용한구현
> HTML標準のdialog要素를 사용한모달컴포넌트を作って。
> アクセシビリティと애니메이션に대응して。
import { useRef, useEffect } from 'react';
interface DialogProps {
open: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
}
function Dialog({ open, onClose, title, children }: DialogProps) {
const dialogRef = useRef<HTMLDialogElement>(null);
useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;
if (open) {
dialog.showModal();
} else {
dialog.close();
}
}, [open]);
useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;
const handleClose = () => onClose();
dialog.addEventListener('close', handleClose);
const handleClick = (e: MouseEvent) => {
if (e.target === dialog) onClose();
};
dialog.addEventListener('click', handleClick);
return () => {
dialog.removeEventListener('close', handleClose);
dialog.removeEventListener('click', handleClick);
};
}, [onClose]);
return (
<dialog
ref={dialogRef}
aria-labelledby="dialog-title"
className="rounded-xl shadow-2xl p-0 backdrop:bg-black/50 backdrop:backdrop-blur-sm
max-w-lg w-full open:animate-fadeIn"
>
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<h2 id="dialog-title" className="text-xl font-bold">{title}</h2>
<button onClick={onClose} aria-label="Close"
className="rounded-full p-1 hover:bg-gray-100">✕</button>
</div>
{children}
</div>
</dialog>
);
}
확인다이얼로그
> 「Deleteしてよろしいですか?」와 같은확인다이얼로그をPromiseベースで使えるようにして。
import { createRoot } from 'react-dom/client';
interface ConfirmOptions {
title: string;
message: string;
confirmLabel?: string;
cancelLabel?: string;
variant?: 'danger' | 'default';
}
function confirm(options: ConfirmOptions): Promise<boolean> {
return new Promise((resolve) => {
const container = document.createElement('div');
document.body.appendChild(container);
const root = createRoot(container);
const handleClose = (result: boolean) => {
root.unmount();
container.remove();
resolve(result);
};
root.render(
<Dialog open={true} onClose={() => handleClose(false)} title={options.title}>
<p className="text-gray-600 mb-6">{options.message}</p>
<div className="flex justify-end gap-3">
<button onClick={() => handleClose(false)}
className="px-4 py-2 border rounded-lg hover:bg-gray-50">
{options.cancelLabel ?? 'キャンセル'}
</button>
<button onClick={() => handleClose(true)}
className={`px-4 py-2 rounded-lg text-white ${
options.variant === 'danger' ? 'bg-red-600 hover:bg-red-700' : 'bg-blue-600 hover:bg-blue-700'
}`}>
{options.confirmLabel ?? '確認'}
</button>
</div>
</Dialog>
);
});
}
// Usage example
async function handleDelete(id: string) {
const ok = await confirm({
title: 'Deleteの確認',
message: 'この項目をDeleteしてよろしいですか?この操作は取り消せません。',
confirmLabel: 'Deleteする',
variant: 'danger',
});
if (ok) await deleteItem(id);
}
コマンドパレット
function CommandPalette({ commands, onClose }: { commands: Command[]; onClose: () => void }) {
const [query, setQuery] = useState('');
const [activeIndex, setActiveIndex] = useState(0);
const filtered = commands.filter((c) =>
c.label.toLowerCase().includes(query.toLowerCase())
);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'ArrowDown') {
e.preventDefault();
setActiveIndex((prev) => Math.min(prev + 1, filtered.length - 1));
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setActiveIndex((prev) => Math.max(prev - 1, 0));
} else if (e.key === 'Enter' && filtered[activeIndex]) {
filtered[activeIndex].action();
onClose();
}
};
return (
<Dialog open={true} onClose={onClose} title="コマンドパレット">
<input
autoFocus
value={query}
onChange={(e) => { setQuery(e.target.value); setActiveIndex(0); }}
onKeyDown={handleKeyDown}
placeholder="コマンドを検索..."
className="w-full px-3 py-2 border rounded-lg mb-3"
role="combobox"
aria-expanded={true}
/>
<ul role="listbox" className="max-h-64 overflow-y-auto">
{filtered.map((cmd, i) => (
<li key={cmd.id} role="option" aria-selected={i === activeIndex}
onClick={() => { cmd.action(); onClose(); }}
className={`px-3 py-2 rounded cursor-pointer ${i === activeIndex ? 'bg-blue-100' : 'hover:bg-gray-50'}`}>
{cmd.label}
</li>
))}
</ul>
</Dialog>
);
}
정리
Claude Code를 활용하면 HTML dialog要素を활용したアクセシブルな모달から、확인다이얼로그、コマンドパレットまで효율적으로구축할 수 있습니다。토스트알림との使い分けは토스트알림구현を、アクセシビリティの基本はアクセシビリティ가이드를 참고하세요.
dialog要素의 사양은MDN Web Docs - dialog를 확인하세요.
#Claude Code
#modal
#ダイアログ
#React
#accessibility
Related Posts
Tips & Tricks
Tips & Tricks
Claude Code 생산성을 3배로 높이는 10가지 팁
Claude Code를 더 효과적으로 활용하는 10가지 실전 팁을 공개합니다. 프롬프트 전략부터 워크플로 단축키까지, 오늘부터 바로 적용해 보세요.
Tips & Tricks
Tips & Tricks
Canvas/WebGL Optimization: Claude Code 활용 가이드
canvas/webgl optimization: Claude Code 활용. 실용적인 팁과 코드 예시를 포함합니다.
Tips & Tricks
Tips & Tricks
Markdown Implementation: Claude Code 활용 가이드
markdown implementation: Claude Code 활용. 실용적인 팁과 코드 예시를 포함합니다.