From ba9474a5080e7ef6acd6e4f20ed8a1cf4bac9837 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:19:12 +0000 Subject: [PATCH] feat(ux): Add aria-describedby to disabled export buttons Links disabled artifact export buttons in the ExportModal to their adjacent description text using `aria-describedby` so screen reader users understand why the buttons are disabled. Updates `ExportModal.test.tsx` to explicitly verify this accessibility improvement. --- .Jules/palette.md | 3 ++ .../components/modals/ExportModal.test.tsx | 22 ++++++++---- .../src/components/modals/ExportModal.tsx | 36 ++++++++++--------- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index 7577a1ca..29166052 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -56,3 +56,6 @@ ## 2024-07-14 - Native Keyboard Submission with Forms for Modals **Learning:** Modals designed with plain `
` elements as wrappers instead of `
` lack native keyboard submission support, forcing users to switch from keyboard to mouse to confirm actions like "Save". **Action:** When designing modals or popups containing inputs, always use a `` element to wrap the content, handle the `onSubmit` event (calling `e.preventDefault()`), and set the primary confirmation button to `type="submit"` to enable seamless Enter-key submission for keyboard users. +## 2026-07-20 - Disabled Button Accessibility with `aria-describedby` +**Learning:** Disabled buttons often rely on `title` attributes for explanations. However, many browsers do not expose tooltips for disabled controls, leaving screen reader users without context. Furthermore, dynamically mapping artifact descriptions to `aria-describedby` requires careful ID generation (e.g., stripping spaces) to ensure proper resolution. +**Action:** Always pair disabled buttons with visible helper text and link them using `aria-describedby` instead of relying on `title`. Ensure test suites (like `ExportModal.test.tsx`) are updated to explicitly verify the `aria-describedby` attribute and its target ID. diff --git a/frontend/src/components/modals/ExportModal.test.tsx b/frontend/src/components/modals/ExportModal.test.tsx index a5710075..a42bb5d0 100644 --- a/frontend/src/components/modals/ExportModal.test.tsx +++ b/frontend/src/components/modals/ExportModal.test.tsx @@ -155,13 +155,21 @@ describe('ExportModal', () => { ); expect(screen.getAllByText('먼저 테이블을 추가하세요')).toHaveLength(7); - expect(screen.getByRole('button', { name: 'SQL DDL 복사' })).toBeDisabled(); - expect(screen.getByRole('button', { name: 'SVG 이미지 내보내기' })).toBeDisabled(); - expect(screen.getByRole('button', { name: 'PlantUML 내보내기' })).toBeDisabled(); - expect(screen.getByRole('button', { name: 'Mermaid 내보내기' })).toBeDisabled(); - expect(screen.getByRole('button', { name: 'DBML 내보내기' })).toBeDisabled(); - expect(screen.getByRole('button', { name: '데이터 사전 CSV 내보내기' })).toBeDisabled(); - expect(screen.getByRole('button', { name: '데이터 사전 Markdown 내보내기' })).toBeDisabled(); + + const checkDisabledExportButton = (name: string, artifactLabel: string) => { + const button = screen.getByRole('button', { name }); + expect(button).toBeDisabled(); + const descId = `artifact-desc-${artifactLabel.replace(/\s+/g, '-')}`; + expect(button).toHaveAttribute('aria-describedby', descId); + }; + + checkDisabledExportButton('SQL DDL 복사', 'SQL DDL'); + checkDisabledExportButton('SVG 이미지 내보내기', 'SVG 이미지'); + checkDisabledExportButton('PlantUML 내보내기', 'PlantUML'); + checkDisabledExportButton('Mermaid 내보내기', 'Mermaid'); + checkDisabledExportButton('DBML 내보내기', 'DBML'); + checkDisabledExportButton('데이터 사전 CSV 내보내기', 'Data Dictionary CSV'); + checkDisabledExportButton('데이터 사전 Markdown 내보내기', 'Data Dictionary MD'); }); it('exposes access-control guidance for disabled button', () => { diff --git a/frontend/src/components/modals/ExportModal.tsx b/frontend/src/components/modals/ExportModal.tsx index 60abe891..c60c8c45 100644 --- a/frontend/src/components/modals/ExportModal.tsx +++ b/frontend/src/components/modals/ExportModal.tsx @@ -212,23 +212,27 @@ export function ExportModal({

- {artifacts.map((artifact) => ( -
-
- {artifact.label} - {artifact.description} + {artifacts.map((artifact) => { + const descId = `artifact-desc-${artifact.label.replace(/\s+/g, '-')}`; + return ( +
+
+ {artifact.label} + {artifact.description} +
+
- -
- ))} + ); + })}