접근성
Spatial Navigation, 포커스 관리, WAI-ARIA 역할, 키보드 단축키를 설명합니다.
reopt design업데이트
Primitive 기반 접근성
opt-ui의 모든 Primitive는 @reopt-ai/opt-ui-primitives 기반으로 구현됩니다. 이 레이어는 WAI-ARIA 역할과 상태, roving tabindex 패턴, 포커스 트랩 같은 접근성 동작을 제공합니다.
개발자는 접근성 세부사항을 직접 구현할 필요 없이 Primitive를 조합하면 됩니다. 역할, 상태, 키보드 인터랙션은 Primitive 계층이 담당합니다.
입력 필드 보호
입력 필드 유형별로 방향키 동작이 다르게 보호됩니다:
| 입력 유형 | 보호 방향 | 이유 |
|---|---|---|
| text, email, url, tel, password | ← → | 커서 이동 |
| number | ↑ ↓ | 값 증감 |
| range | ← → | 값 조절 |
| textarea | 전체 | 커서 이동 (2D) |
| select | 전체 | 옵션 탐색 |
트랩 컨텍스트
dialog, menu, listbox, tree 등 내부에서는 Spatial Navigation이 비활성화됩니다. 이들 컨텍스트는 자체 키보드 탐색을 제공하므로, 충돌을 방지합니다.
export function getTrappedContextRole(el: HTMLElement): string | null {
let node: HTMLElement | null = el;
while (node) {
const role = node.getAttribute("role");
if (
role === "dialog" || role === "alertdialog" ||
role === "menu" || role === "listbox" || role === "tree"
) {
return role;
}
if (node.hasAttribute("data-dialog")) return "dialog";
if (node.hasAttribute("data-popover")) return "popover";
node = node.parentElement;
}
return null;
}RouteFocusManager
페이지 전환(Next.js 라우팅) 시 RouteFocusManager가 자동으로 h1 요소에 포커스를 이동합니다. 이를 통해 스크린 리더 사용자가 새 페이지의 제목을 즉시 인지할 수 있습니다.
WAI-ARIA 역할 맵
| 컴포넌트 | ARIA role | 키보드 조작 |
|---|---|---|
| Tabs | tablist, tab, tabpanel | ←→ 탭 전환, Home/End |
| Dialog | dialog | Esc 닫기, 포커스 트랩 |
| Menu | menubar, menu, menuitem | ←→ 메뉴 전환, ↑↓ 아이템 탐색 |
| Select | listbox, option | ↑↓ 옵션 탐색, Enter 선택 |
| Combobox | combobox, listbox | ↑↓ 후보 탐색, Enter 선택 |
| Toolbar | toolbar | ←→ 아이템 탐색 |
| Disclosure | button (trigger) | Enter/Space 토글 |
| CompositeZone | grid, row, gridcell | ↑↓←→ 2D 탐색 |
키보드 단축키
| 단축키 | 동작 |
|---|---|
| Cmd+K | 커맨드 팔레트 열기 |
| Ctrl+Shift+F | UI 디버거 토글 |
| ↑ ↓ ← → | Spatial Navigation (포커스 이동) |
| Tab | 다음 포커스 가능 요소로 이동 |
| Enter / Space | 버튼 활성화, Disclosure 토글 |
| Esc | 다이얼로그/메뉴/팝오버 닫기 |
정적 분석 (ESLint)
opt-ui는 ESLint 규칙으로 접근성과 컴포넌트 품질을 빌드 시점에 강제합니다. @reopt/eslint-config에 정의된 규칙은 세 범주로 나뉩니다.
접근성 (jsx-a11y)
| 규칙 | 심각도 | 효과 |
|---|---|---|
| alt-text | error | 이미지 alt 텍스트 누락 방지 |
| aria-props | error | 잘못된 aria-* 속성 감지 |
| aria-proptypes | error | 잘못된 aria-* 값 감지 |
| aria-role | error | 잘못된/추상 ARIA role 방지 |
| role-has-required-aria-props | error | role별 필수 aria prop 누락 |
| heading-has-content | error | 빈 heading 방지 |
| interactive-supports-focus | error | 인터랙티브 요소 포커스 필수 |
| tabindex-no-positive | error | 양수 tabIndex 금지 |
| click-events-have-key-events | warn | onClick에 키보드 핸들러 동반 |
| no-static-element-interactions | warn | div/span에 핸들러 시 role 필요 |
| anchor-has-content | warn | 빈 앵커 태그 방지 |
| label-has-associated-control | warn | label-input 연결 강제 |
React 컴포넌트 품질
| 규칙 | 심각도 | 효과 |
|---|---|---|
| button-has-type | error | button type 속성 필수 |
| jsx-key | error | 배열 렌더링 시 key 누락 |
| jsx-no-target-blank | error | target="_blank" 보안 |
| jsx-pascal-case | error | 컴포넌트명 PascalCase |
| void-dom-elements-no-children | error | void 요소에 children 금지 |
| self-closing-comp | warn | 빈 컴포넌트 자기 닫기 (auto-fix) |
| jsx-curly-brace-presence | warn | 불필요한 중괄호 제거 (auto-fix) |
| jsx-fragments | warn | Fragment 단축 문법 사용 (auto-fix) |
테스트 품질 (testing-library)
테스트 파일(**/__tests__/**, *.test.*)에만 적용됩니다.
| 규칙 | 심각도 | 효과 |
|---|---|---|
| prefer-screen-queries | warn | screen.getBy* 사용 권장 |
| no-wait-for-multiple-assertions | warn | waitFor 내 단일 assertion |
| prefer-user-event | warn | fireEvent 대신 userEvent 권장 |
| no-container | warn | container 직접 접근 지양 |
커스텀 규칙
no-restricted-imports로 @reopt-ai/opt-ui-primitives 직접 import를 금지합니다. 모든 프리미티브는 반드시 @reopt-ai/opt-ui를 통해 사용해야 합니다.
개발 도구
opt-ui는 접근성 개발을 위한 두 가지 도구를 제공합니다: