본문으로 건너뛰기

접근성

본 문서는 접근성 책임의 분담과, 직접 구현할 때 지켜야 하는 최소 기준을 정의합니다.

1. 책임 분담

접근성 구현을 전부 직접 하지 않습니다. 대부분은 이미 검증된 구현이 담당하며, 우리가 책임지는 범위는 그보다 좁습니다.

담당 범위
Spartan brain · Angular CDK 포커스 트랩, 포커스 복귀, 키보드 조작, ARIA 속성 연결, 오버레이 접근성
HTML 시맨틱 요소 역할, 기본 키보드 동작, 스크린리더 인식
우리 시맨틱 선택, 접근 가능한 이름, 색 대비, 상태 전달, 직접 만든 상호작용

우선순위는 다음과 같습니다.

  1. 시맨틱 HTML로 해결합니다. <button>은 클릭·엔터·스페이스·포커스를 모두 갖고 있습니다.
  2. 시맨틱으로 표현할 수 없으면 brain 프리미티브를 사용합니다.
  3. 둘 다 불가능한 경우에만 직접 구현하며, 4절의 기준을 적용합니다.

2단계의 적용 범위는 생각보다 넓습니다. 접근성 결함이 가장 자주 나오는 날짜 그리드도 brain이 담당합니다. 방향키·Home·End·PageUp·PageDown 이동, 로빙 탭인덱스, role="grid"와 셀 단위 ARIA가 이미 구현되어 있으므로 4절의 직접 구현 대상이 아닙니다. 남는 몫은 라벨의 한국어화이며 provideBrnCalendarI18n()으로 지정합니다. 확인 결과의 원본은 개발 환경 7절입니다.

주의

brain 디렉티브를 제거하면 접근성이 함께 사라집니다

helm 컴포넌트의 마크업은 자유롭게 수정할 수 있지만, brain 디렉티브 연결은 유지해야 합니다. 마크업을 정리하는 과정에서 디렉티브를 떼어내면 포커스 관리와 ARIA 연결이 소실되며, 이는 화면상으로 드러나지 않아 발견이 늦습니다. 상세는 디자인 시스템과 토큰 2절에 있습니다.

2. 시맨틱

목적 사용 금지
동작 실행 <button> <div (click)>
화면 이동 <a [routerLink]> <button (click)="router.navigate()">
목록 <ul>, <ol> <div> 반복
표 데이터 <table> + <th scope> <div> 그리드
폼 그룹 <fieldset> + <legend> 시각적 제목만

<div (click)>은 키보드로 도달할 수 없고 스크린리더가 동작 요소로 인식하지 않습니다. tabindexrole과 키 핸들러를 붙여 흉내 내는 것보다 <button>을 쓰고 스타일을 제거하는 편이 짧고 정확합니다.

동작과 이동을 구분합니다. 이동은 링크이며, 사용자가 새 탭으로 열거나 주소를 복사할 수 있어야 합니다.

3. 접근 가능한 이름

모든 상호작용 요소는 이름을 가져야 합니다.

상황 방법
텍스트가 있는 버튼 텍스트가 곧 이름입니다
아이콘만 있는 버튼 aria-label을 부여합니다
입력 필드 <label for>로 연결합니다. placeholder는 이름이 아닙니다
장식용 아이콘 aria-hidden="true"를 부여합니다
이미지 의미가 있으면 alt, 장식이면 alt=""

placeholder를 레이블 대신 쓰는 것을 금지합니다. 입력을 시작하면 사라져 무엇을 입력하는 칸인지 알 수 없게 되며, 스크린리더 지원도 일관되지 않습니다.

이름은 화면에 보이는 텍스트와 일치시킵니다. 음성 제어 사용자는 보이는 텍스트를 말해 조작합니다.

4. 직접 구현하는 상호작용

시맨틱과 brain으로 해결되지 않아 직접 만드는 경우 다음을 필수로 갖춥니다.

항목 기준
키보드 도달 Tab으로 도달하고 Shift+Tab으로 되돌아옵니다
키보드 조작 Enter 또는 Space로 실행합니다. 목록형은 방향키로 이동합니다
Escape 팝오버·다이얼로그는 Escape로 닫힙니다
포커스 복귀 오버레이를 닫으면 열었던 요소로 포커스가 돌아옵니다
역할과 상태 role, aria-expanded, aria-selected 등을 상태와 동기화합니다
포커스 가시성 포커스 표시를 제거하지 않습니다

outline: none만 적용하고 대체 표시를 두지 않는 것을 금지합니다. :focus-visible로 키보드 포커스에만 표시를 주는 방식을 사용합니다.

5. 크기와 확대

문서 루트의 font-size 를 뷰포트 폭에 연동합니다. 1920px 에서 100%, 2560px 에서 125% 이며 상한과 하한을 둡니다.

html { font-size: clamp(1rem, 0.25rem + 0.625vw, 1.25rem); }

단위는 rem 또는 % 를 사용하고 px 를 쓰지 않습니다. 접근성을 위해 브라우저 기본 글꼴을 키워 둔 사용자가 있으며, px 로 고정하면 그 설정이 무시됩니다.

주의

뷰포트 연동은 브라우저 확대와 상충합니다

브라우저 확대는 CSS 픽셀의 물리적 크기를 키우는 동시에 뷰포트의 CSS 픽셀 폭을 줄입니다. 따라서 vw 항이 함께 줄어들어 확대 배율만큼 텍스트가 커지지 않습니다. 2560px 화면에서 200% 확대 시 실제 증가폭은 약 160% 입니다. clamp 의 하한을 rem 으로 둔 것이 이 손실을 줄이는 장치이며, 완전히 없애지는 못합니다. 확대 의존도가 높은 화면을 만들 때는 고정 배율을 재검토합니다.

6. 색과 대비

항목 기준
본문 텍스트 대비 4.5:1 이상
큰 텍스트 대비 3:1 이상
UI 요소 경계 대비 3:1 이상
색만으로 의미 전달 금지. 아이콘·텍스트·형태를 함께 사용합니다

대비 기준은 라이트 모드와 다크 모드 양쪽에서 충족해야 합니다. 토큰을 추가할 때 두 모드의 값을 함께 정의하는 이유입니다.

색만으로 상태를 전달하면 색각 이상 사용자가 구분할 수 없습니다. 오류를 빨간색으로만 표시하지 않고 아이콘과 문구를 함께 둡니다.

7. 상태 전달

화면 변화를 시각 외의 방법으로도 알립니다.

변화 전달 방법
조회 중 aria-busy="true" 또는 로딩 텍스트
조회 완료로 목록이 갱신됨 aria-live="polite" 영역에 결과 요약
오류 발생 aria-live="assertive" 또는 role="alert"
입력 오류 aria-invalidaria-describedby로 메시지 연결
화면 전환 라우팅 후 포커스를 주요 제목으로 이동

단일 페이지 애플리케이션에서 화면을 전환하면 스크린리더에는 아무 일도 일어나지 않은 것처럼 보입니다. 라우팅 후 포커스 이동이나 알림이 없으면 사용자가 전환 사실을 알 수 없습니다.

8. 터치 환경

항목 기준
최소 터치 타겟 44 × 44 CSS 픽셀
인접 타겟 간격 8 CSS 픽셀 이상
호버 전용 기능 금지

호버로만 접근 가능한 기능은 터치 사용자에게 존재하지 않는 것과 같습니다. 툴팁에 필수 정보를 담지 않는 규칙도 여기서 나옵니다. 상세는 적응형 UI에 있습니다.

9. 동작과 애니메이션

prefers-reduced-motion을 존중합니다. 전정기관 장애가 있는 사용자에게 큰 움직임은 불편을 유발합니다.

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

자동 재생되는 캐러셀이나 5초 이상 지속되는 자동 변화에는 정지 수단을 제공합니다.

10. 검증

수단 대상 시점
angular-eslint 템플릿 규칙 레이블 연결, 유효하지 않은 ARIA, 클릭 가능 요소의 키보드 이벤트 린트
키보드 완주 마우스 없이 주요 흐름을 끝까지 수행 화면 완성 시 수동
대비 검사 라이트·다크 양쪽 토큰 변경 시
스크린리더 확인 주요 흐름 릴리스 전 수동

자동 검사는 위반의 일부만 잡습니다. 접근 가능한 이름이 부적절하거나 포커스 순서가 논리적이지 않은 것은 도구가 판정하지 못하므로 수동 확인이 필요합니다.

11. 금지 사항

금지 사유
<div (click)>으로 동작 구현 키보드로 도달할 수 없습니다
placeholder를 레이블 대신 사용 입력 시작 시 사라집니다
대체 없이 포커스 표시 제거 키보드 사용자가 현재 위치를 알 수 없습니다
색만으로 상태 전달 색각 이상 사용자가 구분할 수 없습니다
호버 전용 필수 기능 터치 사용자가 접근할 수 없습니다
brain 디렉티브 제거 포커스 관리와 ARIA 연결이 소실됩니다
라우팅 후 포커스·알림 없음 스크린리더 사용자가 전환을 인지하지 못합니다
© 2026 dev.goraebap