Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-05-27 - Duplicate Resume Modal Accessibility
**Learning:** Custom modals like `DuplicateResumeModal` require explicit ARIA attributes (`role="dialog"`, `aria-modal="true"`, `aria-labelledby`) and keyboard interactions (Escape key to close, backdrop click to close) to be fully accessible and provide a smooth user experience.
**Action:** When implementing custom modals, always include these standard accessibility and interaction patterns rather than just visual styling.
25 changes: 22 additions & 3 deletions resume-builder-ui/src/components/DuplicateResumeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ export function DuplicateResumeModal({
}
}, [resume]);

useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isOpen) {
onCancel();
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [isOpen, onCancel]);
Comment on lines +27 to +35
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To improve performance and comply with accessibility standards, we should optimize the keydown event listener and implement a focus trap. Currently, the global keydown listener is registered even when the modal is closed, which is inefficient. Additionally, according to our accessibility guidelines, all modals must implement a focus trap to ensure keyboard focus remains within the modal's focusable elements while it is open.

  useEffect(() => {
    if (!isOpen) return;
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        onCancel();
        return;
      }
      if (e.key === 'Tab') {
        const modal = document.querySelector('[role="dialog"]');
        if (!modal) return;
        const focusable = modal.querySelectorAll(
          'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])"
        );
        if (focusable.length === 0) return;
        const first = focusable[0] as HTMLElement;
        const last = focusable[focusable.length - 1] as HTMLElement;
        if (e.shiftKey) {
          if (document.activeElement === first) {
            last.focus();
            e.preventDefault();
          }
        } else {
          if (document.activeElement === last) {
            first.focus();
            e.preventDefault();
          }
        }
      }
    };
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [isOpen, onCancel]);
References
  1. For accessibility, all modals must implement a focus trap to ensure keyboard focus remains within the modal's focusable elements while it is open.


if (!isOpen || !resume) return null;

const handleSubmit = (e: React.FormEvent) => {
Expand All @@ -34,8 +44,17 @@ export function DuplicateResumeModal({
};

return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
<div className="bg-white rounded-lg shadow-xl max-w-md w-full mx-4">
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
onClick={onCancel}
>
<div
className="bg-white rounded-lg shadow-xl max-w-md w-full mx-4"
onClick={(e) => e.stopPropagation()}
role="dialog"
aria-modal="true"
aria-labelledby="duplicate-modal-title"
>
<div className="p-6">
<div className="flex items-center gap-3 mb-4">
<div className="flex-shrink-0">
Expand All @@ -54,7 +73,7 @@ export function DuplicateResumeModal({
</svg>
</div>
<div>
<h2 className="text-xl font-bold text-gray-900">Duplicate Resume</h2>
<h2 id="duplicate-modal-title" className="text-xl font-bold text-gray-900">Duplicate Resume</h2>
<p className="text-sm text-gray-500 mt-1">Create a copy with a new name</p>
</div>
</div>
Expand Down
Loading