접근성 모범 사례
GitLab v19.4요약
잘못된 ARIA 보다는 ARIA를 쓰지 않는 편이 낫습니다. 상위 100만 개 홈페이지에 대한 WebAIM의 접근성 분석에서는 "ARIA가 탐지 가능한 오류의 증가와 상관관계가 있다"는 결과가 나왔습니다. macOS는 기본적으로 tab 키를 Text boxes and lists only로 제한합니다.
빠른 요약#
잘못된 ARIA 보다는 ARIA를 쓰지 않는 편이 낫습니다.
따라서 aria-*, role, tabindex를 사용하기 전에 다음 권장 사항을 확인합니다.
접근성 시맨틱이 내장된 시맨틱 HTML을 사용하고, 가능하면
스크린 리더와 브라우저의 주요 조합으로 테스트합니다.
상위 100만 개 홈페이지에 대한 WebAIM의 접근성 분석에서는
"ARIA가 탐지 가능한 오류의 증가와 상관관계가 있다"는 결과가 나왔습니다.
오류 증가의 큰 원인은 ARIA의 오용일 가능성이 높으므로,
확신이 없다면 aria-*, role, tabindex를 쓰지 말고 시맨틱 HTML을 사용합니다.
macOS에서 키보드 탐색 활성화#
macOS는 기본적으로 tab 키를 Text boxes and lists only로 제한합니다. 전체 키보드 탐색을 활성화하는 방법은 다음과 같습니다.
- System Preferences를 엽니다.
- Keyboard를 선택합니다.
- Shortcuts 탭을 엽니다.
- Use keyboard navigation to move focus between controls 설정을 활성화합니다.
브라우저별 키보드 탐색 활성화에 관한 자세한 내용은 a11yproject에서 확인할 수 있습니다.
빠른 체크리스트#
- text, textarea, select, checkbox, radio, file, toggle 입력에 접근 가능한 이름이 있습니다.
- 버튼, 링크, 이미지에 설명적인 접근 가능한 이름이 있습니다.
- 아이콘
- 장식용이 아닌 아이콘에
aria-label이 있습니다. - 클릭 가능한 아이콘은 버튼입니다. 즉
<gl-icon />이 아니라<gl-button icon="close" />를 사용합니다. - 아이콘만 있는 버튼에
aria-label이 있습니다.
- 장식용이 아닌 아이콘에
- 인터랙티브 요소를 Tab 키로 접근할 수 있고 포커스 상태가 눈에 보입니다.
- 툴팁이 있는 요소에 Tab 키로 포커스를 줄 수 있습니다.
- 불필요한
role,tabindex,aria-*속성이 없는지 확인합니다. div나span요소를p,button,time처럼 더 시맨틱한 HTML 요소로 바꿀 수 있는지 확인합니다.
좋은 문서 구조 제공#
제목은 스크린 리더 사용자가 콘텐츠를 탐색할 때 쓰는 주요 수단입니다. 따라서 페이지의 제목 구조는 잘 짜인 목차처럼 이해할 수 있어야 합니다. 다음을 지킵니다.
- 페이지에
h1요소는 하나만 둡니다. - 제목 수준을 건너뛰지 않습니다.
- 제목 수준을 올바르게 중첩합니다.
스크린 리더를 위한 접근 가능한 이름 제공#
접근 가능한 이름이 있는 마크업을 만들려면 다음을 지킵니다.
- 입력에는 연결된
label이 있어야 합니다. - 버튼과 링크에는 눈에 보이는 텍스트가 있어야 하며, 콘텐츠가 없는 아이콘 버튼처럼 보이는 텍스트가 없으면
aria-label이 있어야 합니다. - 이미지에는
alt속성이 있어야 합니다. - 차트에는 긴 설명과 짧은 설명이 모두 있어야 합니다.
fieldset의 첫 자식은legend여야 합니다.figure의 첫 자식은figcaption이어야 합니다.table의 첫 자식은caption이어야 합니다.
alt 속성은 약 150자를 넘지 않아야 합니다. 길이에 관한 공식 지침은 없지만, 일부 스크린 리더는 alt 속성 안의 긴 문자열을 읽지 않습니다.
접근 가능한 이름은 여러 방식으로 제공할 수 있으며 접근 가능한 이름 계산으로 결정됩니다. 우선순위를 단순화하면 다음 순서입니다.
aria-labelledbyaria-labelalt,legend,figcaption,captiontitle.
접근 가능한 이름 제공 예시#
다음 하위 섹션에는 접근 가능한 이름이 있는 HTML 요소를 렌더링하는 마크업 예시가 있습니다.
GlFormGroup을 사용할 때는 다음을 유의합니다.
labelprop만 전달하면label값을 담은legend가 있는fieldset이 렌더링됩니다.label과label-forprop을 함께 전달하면 같은label-forID를 가진 폼 입력을 가리키는label이 렌더링됩니다.
접근 가능한 이름이 있는 폼 입력#
체크박스와 라디오 입력 그룹은 legend가 있는 fieldset으로 묶습니다.
legend는 체크박스와 라디오 입력 그룹에 레이블을 부여합니다.
label, 자식 텍스트, 자식 요소가 화면에 보이지 않아야 한다면
gl-sr-only 클래스명으로 스크린 리더를 제외한 나머지에서 요소를 숨깁니다.
파일 입력 예시는 다음과 같습니다.
<!-- File input with a label -->
<label for="attach-file">{{ __('Attach a file') }}</label>
<input id="attach-file" type="file" />
<!-- File input with a hidden label -->
<label for="attach-file" class="gl-sr-only">{{ __('Attach a file') }}</label>
<input id="attach-file" type="file" />
접근 가능한 이름이 있는 이미지#
이미지 예시는 다음과 같습니다.
<img :src="imagePath" :alt="__('A description of the image')" />
<!-- SVGs implicitly have a graphics role so if it is semantically an image we should apply `role="img"` -->
<svg role="img" :alt="__('A description of the image')" />
<!-- A decorative image, hidden from screen readers -->
<img :src="imagePath" :alt="" />
설명적인 접근 가능한 이름이 있는 버튼과 링크#
버튼과 링크의 접근 가능한 이름은 그 자체만으로 이해할 수 있을 만큼 구체적이어야 합니다.
<!-- bad -->
<gl-button @click="handleClick">{{ __('Submit') }}</gl-button>
<gl-link :href="url">{{ __('page') }}</gl-link>
<!-- good -->
<gl-button @click="handleClick">{{ __('Submit review') }}</gl-button>
<gl-link :href="url">{{ __("GitLab's accessibility page") }}</gl-link>
Role#
일반적으로 role 사용은 피합니다.
대신 role 이 암묵적으로 부여되는 시맨틱 HTML 요소를 사용합니다.
| 잘못된 방법 | 올바른 방법 |
|---|---|
<div role="button"> |
<button> |
<div role="img"> |
<img> |
<div role="link"> |
<a> |
<div role="header"> |
<h1> to <h6> |
<div role="textbox"> |
<input> or <textarea> |
<div role="article"> |
<article> |
<div role="list"> |
<ol> or <ul> |
<div role="listitem"> |
<li> |
<div role="table"> |
<table> |
<div role="rowgroup"> |
<thead>, <tbody>, or <tfoot> |
<div role="row"> |
<tr> |
<div role="columnheader"> |
<th> |
<div role="cell"> |
<td> |
키보드 전용 사용 지원#
키보드 사용자는 포커스 윤곽선으로 페이지에서 자신의 위치를 파악합니다. 따라서 인터랙티브 요소라면 다음을 보장해야 합니다.
- 키보드 포커스를 받을 수 있습니다.
- 포커스 상태가 눈에 보입니다.
이런 동작을 기본으로 제공하는 a(GlLink), button(GlButton) 같은 시맨틱 HTML을 사용합니다.
다음을 유의합니다.
- Tab과 Shift-Tab은 정적 콘텐츠가 아니라 인터랙티브 요소 사이에서만 이동해야 합니다.
:hover스타일을 추가할 때는 대부분의 경우:focus스타일도 함께 추가해 마우스 사용자와 키보드 사용자 모두에게 스타일이 적용되도록 합니다.- 인터랙티브 요소의
outline을 제거한다면box-shadow등으로 포커스 상태를 시각적으로 유지합니다.
자세한 내용은 Pajamas 키보드 전용 페이지를 참고합니다.
tabindex#
tabindex는 되도록 사용하지 않습니다. 이유는 다음과 같습니다.
button(GlButton) 같은 시맨틱 HTML을 사용하면tabindex="0"이 암묵적으로 적용됩니다.- 탭 순서는 화면에서 읽는 순서와 같아야 하는데, 양의
tabindex는 이를 방해합니다.
tabindex="0"으로 요소를 인터랙티브하게 만들지 않기#
div와 span 태그 대신 인터랙티브 요소를 사용합니다.
예를 들면 다음과 같습니다.
마크업이 시맨틱하게 완성되면 CSS로 원하는 시각적 상태를 만듭니다.
<!-- bad -->
<div role="button" tabindex="0" @click="expand">Expand</div>
<!-- good -->
<gl-button class="gl-p-0!" category="tertiary" @click="expand">Expand</gl-button>
인터랙티브 요소에 tabindex="0" 사용 금지#
인터랙티브 요소는 이미 탭으로 접근할 수 있으므로 tabindex를 추가하는 것은 불필요합니다.
<!-- bad -->
<gl-link href="help" tabindex="0">Help</gl-link>
<gl-button tabindex="0">Submit</gl-button>
<!-- good -->
<gl-link href="help">Help</gl-link>
<gl-button>Submit</gl-button>
스크린 리더가 읽을 요소에 tabindex="0" 사용 금지#
스크린 리더는 탭으로 접근할 수 없는 텍스트도 읽습니다.
tabindex="0"을 쓰는 것은 불필요하며, 스크린 리더 사용자가 해당 요소와
상호작용할 수 있다고 기대하게 만들어 문제를 일으킵니다.
<!-- bad -->
<p tabindex="0" :aria-label="message">{{ message }}</p>
<!-- good -->
<p>{{ message }}</p>
양의 tabindex 사용 금지#
tabindex="1" 이상은
항상 피합니다.
아이콘#
아이콘은 세 가지 유형으로 나눌 수 있습니다.
- 장식용 아이콘
- 의미를 전달하는 아이콘
- 클릭 가능한 아이콘
장식용 아이콘#
UI에서 제거해도 사용자가 잃는 정보가 없다면 그 아이콘은 장식용입니다.
GitLab의 아이콘은 대부분 장식용이므로, GlIcon은 렌더링한 아이콘을 스크린 리더에서 자동으로 숨깁니다.
따라서 GlIcon에 aria-hidden="true"를 추가할 필요가 없으며, 이는 중복입니다.
<!-- unnecessary: gl-icon hides icons from screen readers by default -->
<gl-icon name="rocket" aria-hidden="true" />
<!-- good -->
<gl-icon name="rocket" />
정보를 전달하는 아이콘#
UI에서 제거했을 때 사용자가 잃는 정보가 있다면 그 아이콘은 정보를 전달하는 아이콘입니다.
예를 들어 옆에 "Confidential" 텍스트가 없는 상태로 이슈가 비밀임을 나타내는 confidential 아이콘이 여기에 해당합니다.
정보를 전달하는 아이콘은 스크린 리더 사용자에게도 그 정보가 전달되도록 접근 가능한 이름이 있어야 합니다.
<!-- bad -->
<gl-icon name="eye-slash" />
<!-- good -->
<gl-icon name="eye-slash" :aria-label="__('Confidential issue')" />
클릭 가능한 아이콘#
클릭 가능한 아이콘은 의미상 버튼이므로, 접근 가능한 이름과 함께 버튼으로 렌더링해야 합니다.
<!-- bad -->
<gl-icon name="close" :aria-label="__('Close')" @click="handleClick" />
<!-- good -->
<gl-button icon="close" category="tertiary" :aria-label="__('Close')" @click="handleClick" />
요소 숨기기#
필요한 경우 다음 표를 참고해 사용자에게서 요소를 숨깁니다.
| 시각 장애가 없는 사용자에게 숨기기 | 스크린 리더에게 숨기기 | 시각 장애가 없는 사용자와 스크린 리더 모두에게 숨기기 |
|---|---|---|
.gl-sr-only |
aria-hidden="true" |
display: none, visibility: hidden, 또는 hidden 속성 |
스크린 리더에서 장식용 이미지 숨기기#
스크린 리더 사용자에게 불필요한 정보를 줄이려면 alt=""로 장식용 이미지를 숨깁니다.
인라인 SVG처럼 img 요소가 아닌 이미지는 role="img"와 alt=""를 함께 추가해 숨길 수 있습니다.
gl-icon 컴포넌트는 아이콘을 스크린 리더에서 자동으로 숨기므로, gl-icon을 사용할 때는
aria-hidden="true"가 필요하지 않습니다.
<!-- good - decorative images hidden from screen readers -->
<img src="decorative.jpg" alt="">
<svg role="img" alt="" />
<gl-icon name="work-item-epic" />
ARIA 사용 시점#
시맨틱 HTML에는 접근성이 이미 반영돼 있으므로 ARIA가 필요하지 않습니다.
다만 시맨틱 HTML로 대응할 수 없는 UI 패턴도 있습니다. 일반적인 예로는 대화 상자(모달)와 탭이 있습니다. GitLab에서는 담당자 드롭다운과 레이블 드롭다운이 여기에 해당합니다. 이런 위젯을 만들 때는 스크린 리더가 이해할 수 있도록 ARIA가 필요합니다. WCAG 준수를 위해 충분한 조사와 테스트를 수행해야 합니다.