From cfbb79954dc6c163cb2a4c3e0768bcef739e1e83 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 5 Jul 2026 02:09:38 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=EC=84=B1=20=ED=83=AD=20=EC=88=9C=EC=84=9C(tabIndex)=20?= =?UTF-8?q?=EC=95=88=ED=8B=B0=ED=8C=A8=ED=84=B4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 불필요한 포커스 트랩과 키보드 내비게이션 피로를 유발하는 `tabIndex={0}` 속성을 `TableNode`의 텍스트 잘림 요소와 기타 비상호작용 요소에서 제거했습니다. --- .Jules/palette.md | 3 ++ frontend/src/erd/TableNode.tsx | 37 +++++++++++++++---- frontend/src/erd/__tests__/TableNode.test.tsx | 6 +-- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index 32fb11be..3bc5e311 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -43,3 +43,6 @@ ## 2024-06-26 - [Abbreviation Comprehension in ERD Nodes] **Learning:** Users without deep database administration backgrounds may not immediately recognize domain-specific abbreviations like "PK" or "FK" rendered as minimalist badges inside dense ERD nodes. **Action:** Always provide `title` attributes on technical acronym badges (like Primary Key / Foreign Key) to ensure clarity and improve accessibility without cluttering the space-constrained node UI. +## 2026-07-04 - Focus Trap Anti-Pattern in Non-Interactive Badges +**Learning:** Adding `tabIndex={0}` to static, non-interactive text badges (like truncated text spans or abbreviation `abbr` tags) just to expose their native `title` or `aria-label` to keyboard users creates an accessibility anti-pattern. This significantly bloats the page's tab sequence, degrading keyboard navigation and frustrating screen reader users who expect interactive elements upon focus. +**Action:** Do not use `tabIndex={0}` as a shortcut for making non-interactive tooltips accessible. Truncated static text or badges should be paired with proper `aria-label` attributes without adding them to the focus order unless they are actionable. diff --git a/frontend/src/erd/TableNode.tsx b/frontend/src/erd/TableNode.tsx index f80d8488..b0eacdf3 100644 --- a/frontend/src/erd/TableNode.tsx +++ b/frontend/src/erd/TableNode.tsx @@ -54,7 +54,6 @@ function AccessibleTruncatedText({ className={className} title={title ?? accessibleText} aria-label={accessibleText} - tabIndex={0} > {children ?? text} @@ -116,10 +115,22 @@ function TableNode(props: NodeProps) { /> ) : null} {data.badges?.pk ? ( - PK + + PK + ) : null} {data.badges?.fk ? ( - FK + + FK + ) : null} @@ -153,12 +164,20 @@ function TableNode(props: NodeProps) { {c.data_type} {c.is_pk ? ( - + PK ) : null} {c.is_not_null ? ( - + NOT NULL ) : null} @@ -176,13 +195,16 @@ function TableNode(props: NodeProps) { className="tableNode__more" title="생략된 컬럼이 더 있습니다" aria-label="생략된 컬럼이 더 있습니다" - tabIndex={0} > … {data.columns.length - MAX_RENDERED_COLUMNS} more ) : null} {data.indexes?.length ? ( -
+
Indexes
{data.indexes.slice(0, 4).map((index) => { const columnsText = `(${index.columns.join(", ")})`; @@ -207,7 +229,6 @@ function TableNode(props: NodeProps) { className="tableNode__more" title="생략된 인덱스가 더 있습니다" aria-label="생략된 인덱스가 더 있습니다" - tabIndex={0} > … {data.indexes.length - 4} more indexes
diff --git a/frontend/src/erd/__tests__/TableNode.test.tsx b/frontend/src/erd/__tests__/TableNode.test.tsx index 8e8f65a9..2c217b8e 100644 --- a/frontend/src/erd/__tests__/TableNode.test.tsx +++ b/frontend/src/erd/__tests__/TableNode.test.tsx @@ -33,7 +33,7 @@ describe('TableNode', () => { expect(screen.getByText('Core')).toBeInTheDocument(); }); - it('exposes truncated metadata to keyboard and assistive technology users', () => { + it('exposes truncated metadata to assistive technology users', () => { const data = { title: 'public.users', comment: 'Stores application users with long operational notes', @@ -73,12 +73,12 @@ describe('TableNode', () => { ]) { const item = screen.getByLabelText(name); expect(item).toHaveAttribute('title', name); - expect(item).toHaveAttribute('tabindex', '0'); + expect(item).not.toHaveAttribute('tabindex', '0'); } const indexName = screen.getByLabelText('idx_users_email_unique_long_name'); expect(indexName).toHaveAttribute('title', 'Access method: btree'); - expect(indexName).toHaveAttribute('tabindex', '0'); + expect(indexName).not.toHaveAttribute('tabindex', '0'); const [notNullBadge] = screen.getAllByLabelText('필수 입력 (Not Null)'); expect(notNullBadge).toHaveAttribute('title', 'Not Null');