- 빠른 요약
- macOS에서 키보드 탐색 기능 활성화
- 빠른 체크리스트
- 문서 개요 제공
- 스크린 리더용 접근 가능한 이름 제공
- 역할
- 키보드 전용 사용 지원
-
tabindex
- 툴팁
- 요소 숨김
- ARIA 사용 시기
웹 접근성 최상의 실천사례
빠른 요약
나쁜 ARIA보다는 ARIA가 없는 것이 더 나은 것을 고려하여, aria-*
, role
, 및 tabindex
를 사용하기 전에 다음 권장 사항을 검토하세요.
웹 접근성을 지원하는 의미 있는 HTML을 사용하고, 이상적으로는 스크린 리더 및 브라우저의 관련 조합으로 테스트하세요.
WebAIM의 상위 백만 홈페이지에 대한 접근성 분석에서 “ARIA가 감지 가능한 오류와 상관 관계가 있다”고 발견되었습니다.
ARIA의 오용이 증가된 오류의 큰 원인일 가능성이 높으므로, 의심스러울 경우 aria-*
, role
, 및 tabindex
를 사용하지 말고 의미 있는 HTML을 사용하세요.
macOS에서 키보드 탐색 기능 활성화
기본적으로 macOS에서는 tab 키가 텍스트 상자 및 디렉터리만으로 제한됩니다. 전체 키보드 탐색을 활성화하려면:
- 시스템 환경설정을 엽니다.
- 키보드를 선택합니다.
- 단축키 탭을 엽니다.
- 설정에서 컨트롤 사이의 포커스를 이동하는 데 키보드 탐색 사용을 활성화합니다.
여기에서 브라우저별 키보드 탐색 기능을 활성화하는 방법에 대해 더 읽을 수 있습니다.
빠른 체크리스트
- 텍스트, 선택, 체크박스, 라디오, 파일, 및 토글 입력에 접근 가능한 이름이 있습니다.
- 버튼, 링크, 및 이미지에 설명적인 접근 가능한 이름이 있습니다.
- 아이콘
-
장식용이 아닌 아이콘에
aria-label
이 있습니다. -
클릭 가능한 아이콘은
<gl-button icon="close" />
처럼 버튼으로,<gl-icon />
처럼 사용되지 않습니다. - 아이콘만 있는 버튼에는
aria-label
이 있습니다.
-
장식용이 아닌 아이콘에
- 상호작용 요소는 Tab 키로 접근할 수 있으며 시각적 포커스 상태가 있습니다.
- 툴팁이 있을 경우, Tab 키를 사용하여 포커스를 맞출 수 있습니다.
- 불필요한
role
,tabindex
, 또는aria-*
속성이 있는지 확인하세요. - 어떤
div
또는span
요소가p
,button
, 또는time
과 같은 보다 의미 있는 HTML 요소로 바뀔 수 있는지 확인하세요.
문서 개요 제공
스크린 리더 사용자가 콘텐츠를 탐색하는 데 사용하는 주요 메커니즘은 제목입니다. 따라서 페이지의 제목 구조는 좋은 차례로 콘텐츠를 탐색할 수 있도록 만들어져야 합니다. 다음을 보장해야 합니다:
- 페이지에는
h1
요소가 하나만 있습니다. - 제목 수준이 건너뛰지 않습니다.
- 제목 수준이 올바르게 중첩되어 있습니다.
스크린 리더용 접근 가능한 이름 제공
접근 가능한 이름으로 표시된 마크업을 제공하려면 모든 다음 사항이 충족되어야 합니다:
- 입력에는 연관된
label
이 있습니다. - 버튼 및 링크에는 가시 텍스트 또는 가시 텍스트가 없을 때는
aria-label
이 있습니다(예: 내용이 없는 아이콘 버튼의 경우). - 이미지에는
alt
속성이 있습니다. -
fieldset
에는 첫 번째 자식으로legend
가 있습니다. -
figure
에는 첫 번째 자식으로figcaption
가 있습니다. -
table
에는 첫 번째 자식으로caption
이 있습니다.
체크박스 및 라디오 입력의 그룹은 fieldset
내에 묶여 있어야 하며, legend
를 가지고 있어야 합니다.
legend
는 체크박스 및 라디오 입력 그룹에 라벨을 제공합니다.
만약 label
, 자식 텍스트, 또는 자식 요소가 시각적으로 표시되지 않는 경우 .gl-sr-only
를 사용하여 화면 리더를 제외한 모든 것에서 해당 요소를 숨깁니다.
접근 가능한 이름을 제공하는 예제
다음 하위 섹션에는 접근 가능한 이름으로 HTML 요소를 렌더링하는 마크업의 예제가 포함되어 있습니다.
-
label
prop만 전달하면label
값을 포함하는legend
가 있는fieldset
이 렌더링됩니다. -
label
및label-for
prop을 모두 전달하면 동일한label-for
ID를 가진 폼 입력을 가리키는label
이 렌더링됩니다.
접근 가능한 이름을 가진 텍스트 입력
GlFormGroup
를 사용할 때, label
prop만으로는 입력에 접근 가능한 이름이 제공되지 않습니다.
입력에 접근 가능한 이름을 제공하려면 label-for
prop을 함께 제공해야 합니다.
텍스트 입력 예시:
<!-- 라벨이 있는 입력 -->
<gl-form-group :label="__('이슈 제목')" label-for="issue-title">
<gl-form-input id="issue-title" v-model="title" />
</gl-form-group>
<!-- 숨겨진 라벨이 있는 입력 -->
<gl-form-group :label="__('이슈 제목')" label-for="issue-title" label-sr-only>
<gl-form-input id="issue-title" v-model="title" />
</gl-form-group>
textarea
예시:
<!-- 라벨이 있는 텍스트 영역 -->
<gl-form-group :label="__('이슈 설명')" label-for="issue-description">
<gl-form-textarea id="issue-description" v-model="description" />
</gl-form-group>
<!-- 숨겨진 라벨이 있는 텍스트 영역 -->
<gl-form-group :label="__('이슈 설명')" label-for="issue-description" label-sr-only>
<gl-form-textarea id="issue-description" v-model="description" />
</gl-form-group>
또는 일반 label
요소를 사용할 수 있습니다:
<!-- `label`을 사용하여 라벨이 있는 입력 -->
<label for="issue-title">{{ __('이슈 제목') }}</label>
<gl-form-input id="issue-title" v-model="title" />
<!-- `label`을 사용하여 숨겨진 라벨이 있는 입력 -->
<label for="issue-title" class="gl-sr-only">{{ __('이슈 제목') }}</label>
<gl-form-input id="issue-title" v-model="title" />
접근 가능한 이름을 가진 선택 입력
선택 입력 예시:
<!-- 라벨이 있는 선택 입력 -->
<gl-form-group :label="__('이슈 상태')" label-for="issue-status">
<gl-form-select id="issue-status" v-model="status" :options="options" />
</gl-form-group>
<!-- 숨겨진 라벨이 있는 선택 입력 -->
<gl-form-group :label="__('이슈 상태')" label-for="issue-status" label-sr-only>
<gl-form-select id="issue-status" v-model="status" :options="options" />
</gl-form-group>
접근 가능한 이름을 가진 체크박스 입력
단일 체크박스:
<!-- 라벨이 있는 단일 체크박스 -->
<gl-form-checkbox v-model="status" value="task-complete">
{{ __('작업 완료') }}
</gl-form-checkbox>
<!-- 숨겨진 라벨이 있는 단일 체크박스 -->
<gl-form-checkbox v-model="status" value="task-complete">
<span class="gl-sr-only">{{ __('작업 완료') }}</span>
</gl-form-checkbox>
여러 체크박스:
<!-- fieldset으로 그룹화된 여러 라벨이 있는 체크박스 -->
<gl-form-group :label="__('작업 디렉터리')">
<gl-form-checkbox value="task-1">{{ __('작업 1') }}</gl-form-checkbox>
<gl-form-checkbox value="task-2">{{ __('작업 2') }}</gl-form-checkbox>
</gl-form-group>
<!-- 또는 -->
<gl-form-group :label="__('작업 디렉터리')">
<gl-form-checkbox-group v-model="selected" :options="options" />
</gl-form-group>
<!-- fieldset으로 그룹화된 여러 라벨이 있는 체크박스(숨겨진 legend와 함께) -->
<gl-form-group :label="__('작업 디렉터리')" label-sr-only>
<gl-form-checkbox value="task-1">{{ __('작업 1') }}</gl-form-checkbox>
<gl-form-checkbox value="task-2">{{ __('작업 2') }}</gl-form-checkbox>
</gl-form-group>
<!-- 또는 -->
<gl-form-group :label="__('작업 디렉터리')" label-sr-only>
<gl-form-checkbox-group v-model="selected" :options="options" />
</gl-form-group>
#### 접근 가능한 이름을 가진 라디오 입력란
단일 라디오 입력란:
```html
<!-- 라벨이 있는 단일 라디오 -->
<gl-form-radio v-model="status" value="opened">
{{ __('Opened') }}
</gl-form-radio>
<!-- 숨겨진 라벨이 있는 단일 라디오 -->
<gl-form-radio v-model="status" value="opened">
<span class="gl-sr-only">{{ __('Opened') }}</span>
</gl-form-radio>
다중 라디오 입력란:
<!-- fieldset 내에 그룹화된 여러 라벨이 있는 라디오 입력란 -->
<gl-form-group :label="__('이슈 상태')">
<gl-form-radio value="opened">{{ __('Opened') }}</gl-form-radio>
<gl-form-radio value="closed">{{ __('Closed') }}</gl-form-radio>
</gl-form-group>
<!-- 또는 -->
<gl-form-group :label="__('이슈 상태')">
<gl-form-radio-group v-model="selected" :options="options" />
</gl-form-group>
<!-- 숨겨진 legend가 있는 fieldset 내에 그룹화된 여러 라벨이 있는 라디오 입력란 -->
<gl-form-group :label="__('이슈 상태')" label-sr-only>
<gl-form-radio value="opened">{{ __('Opened') }}</gl-form-radio>
<gl-form-radio value="closed">{{ __('Closed') }}</gl-form-radio>
</gl-form-group>
<!-- 또는 -->
<gl-form-group :label="__('이슈 상태')" label-sr-only>
<gl-form-radio-group v-model="selected" :options="options" />
</gl-form-group>
접근 가능한 이름을 가진 파일 입력란
파일 입력 예시:
<!-- 라벨이 있는 파일 입력란 -->
<label for="attach-file">{{ __('파일 첨부') }}</label>
<input id="attach-file" type="file" />
<!-- 숨겨진 라벨이 있는 파일 입력란 -->
<label for="attach-file" class="gl-sr-only">{{ __('파일 첨부') }}</label>
<input id="attach-file" type="file" />
GlToggle 구성요소에 접근 가능한 이름이 있는 것
GlToggle
예시:
<!-- 라벨이 있는 GlToggle -->
<gl-toggle v-model="notifications" :label="__('알림')" />
<!-- 숨겨진 라벨이 있는 GlToggle -->
<gl-toggle v-model="notifications" :label="__('알림')" label-position="hidden" />
GlFormCombobox 구성요소에 접근 가능한 이름이 있는 것
GlFormCombobox
예시:
<!-- 라벨이 있는 GlFormCombobox -->
<gl-form-combobox :label-text="__('키')" :token-list="$options.tokenList" />
접근 가능한 이름이 있는 이미지
이미지 예시:
<img :src="imagePath" :alt="__('이미지 설명')" />
<!-- SVG는 시멘틱하게 이미지로 구성되어 있으므로 `role="img"`를 적용해야 합니다 -->
<svg role="img" :alt="__('이미지 설명')" />
<!-- 장식용 이미지, 스크린 리더에 표시되지 않음 -->
<img :src="imagePath" :alt="" />
설명적인 접근 이름이 있는 버튼 및 링크
버튼과 링크는 독립적으로 이해할 수 있을 정도로 설명적인 접근 이름을 가져야 합니다.
<!-- 나쁜 예시 -->
<gl-button @click="handleClick">{{ __('제출') }}</gl-button>
<gl-link :href="url">{{ __('페이지') }}</gl-link>
<!-- 좋은 예시 -->
<gl-button @click="handleClick">{{ __('리뷰 제출') }}</gl-button>
<gl-link :href="url">{{ __("GitLab의 접근성 페이지") }}</gl-link>
버튼과 같은 스타일이 적용된 링크
링크는 GlButton
을 사용하여 버튼과 같은 스타일을 적용할 수 있습니다.
<gl-button :href="url">{{ __('버튼처럼 스타일이 적용된 링크') }}</gl-button>
역할
일반적으로 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> 또는 <textarea>
|
<div role="article">
| <article>
|
<div role="list">
|
<ol> 또는 <ul>
|
<div role="listitem">
| <li>
|
<div role="table">
| <table>
|
<div role="rowgroup">
|
<thead> , <tbody> , 또는 <tfoot>
|
<div role="row">
| <tr>
|
<div role="columnheader">
| <th>
|
<div role="cell">
| <td>
|
키보드 전용 사용 지원
키보드 사용자는 페이지 상의 위치를 이해하기 위해 포커스 아웃라인에 의존합니다. 따라서 상호작용 가능한 요소는 다음이 보장되어야 합니다:
- 키보드 포커스를 받을 수 있어야 합니다.
- 시각적으로 포커스 상태가 보여져야 합니다.
기본적으로 이러한 동작을 제공하는 의미론적 HTML인 a
(GlLink
) 및 button
(GlButton
)을 사용하세요.
기억하세요:
- Tab 및 Shift-Tab은 상호작용 가능한 요소 간에만 이동해야 하며 정적 콘텐츠 사이에선 이동하면 안 됩니다.
-
:hover
스타일을 추가할 때 대부분의 경우:focus
스타일도 추가하여 마우스와 키보드 사용자에게 스타일이 적용되도록 합니다. - 상호작용 요소의
outline
을 제거하는 경우box-shadow
와 같은 다른 방법으로 시각적 포커스 상태를 유지해야 합니다.
자세한 내용은 파자마 키보드 전용 페이지를 참조하세요.
tabindex
tabindex
사용보다는 사용하지 않는 것을 선호하세요.
-
button
(GlButton
)과 같은 의미론적인 HTML을 사용하면 기본적으로tabindex="0"
을 제공합니다. - 탭 순서는 시각적으로 읽히는 순서와 일치해야 하며, 양수의
tabindex
값은 이러한 일치를 방해합니다.
tabindex="0"
을 사용하여 요소를 상호작용 가능하게 만들지 마세요
div
및 span
태그 대신 상호작용 가능한 요소를 사용하세요.
예를 들어:
- 요소를 클릭할 수 있어야 한다면
button
(GlButton
)을 사용하세요. - 텍스트를 편집할 수 있어야 한다면
input
또는textarea
을 사용하세요.
마크업이 의미론적으로 완성된 후 CSS를 사용하여 원하는 시각적 상태로 업데이트하세요.
<!-- 나쁜 예시 -->
<div role="button" tabindex="0" @click="expand">확장</div>
<!-- 좋은 예시 -->
<gl-button class="gl-p-0!" category="tertiary" @click="expand">확장</gl-button>
상호작용 요소에 tabindex="0"
을 사용하지 마세요
상호작용 요소는 이미 탭으로 접근할 수 있으므로 tabindex
를 추가하는 것은 중복되어 있습니다.
<!-- 나쁜 예시 -->
<gl-link href="help" tabindex="0">도움말</gl-link>
<gl-button tabindex="0">제출</gl-button>
<!-- 좋은 예시 -->
<gl-link href="help">도움말</gl-link>
<gl-button>제출</gl-button>
스크린 리더가 읽을 요소에 tabindex="0"
을 사용하지 마세요
스크린 리더는 탭으로 접근이 가능하지 않은 텍스트를 읽을 수 있습니다.
tabindex="0"
을 사용하는 것은 불필요하며 문제를 일으킬 수 있습니다.
왜냐하면 스크린 리더 사용자들은 상호작용할 수 있기를 기대하기 때문입니다.
<!-- 나쁜 예시 -->
<p tabindex="0" :aria-label="message">{{ message }}</p>
<!-- 좋은 예시 -->
<p>{{ message }}</p>
양수의 tabindex
를 사용하지 마세요
항상 tabindex="1
” 이상을 사용하는 것을 피하세요.
(https://webaim.org/techniques/keyboard/tabindex#overview)
장식용 아이콘들
아이콘은 UI에서 제거되었을 때 사용자에게 정보가 손실되지 않는 경우에 장식용입니다.
GitLab 내 대다수의 아이콘은 장식용이므로 GlIcon
은 자동으로 스크린 리더에서 렌더링된 아이콘을 숨깁니다.
따라서 GlIcon
에 aria-hidden="true"
를 추가할 필요가 없습니다. 이것은 중복된 작업입니다.
<!-- 불필요함 — gl-icon은 스크린 리더에서 아이콘을 기본적으로 숨김 -->
<gl-icon name="rocket" aria-hidden="true" />
<!-- 괜찮음 -->
<gl-icon name="rocket" />
정보 전달용 아이콘들
아이콘이 UI에서 제거되면 사용자에게 정보가 손실되는 경우, 해당 아이콘은 정보를 전달합니다.
예를 들어, 기밀 아이콘은 해당 이슈가 기밀임을 나타내며 “기밀”이라는 텍스트가 옆에 없는 경우입니다.
정보를 전달하는 아이콘은 스크린 리더 사용자에게도 정보가 전달되도록 접근 가능한 이름을 가져야 합니다.
<!-- 나쁨 -->
<gl-icon name="eye-slash" />
<!-- 좋음 -->
<gl-icon name="eye-slash" :aria-label="__('기밀 이슈')" />
클릭 가능한 아이콘들
클릭 가능한 아이콘들은 의미론적으로 버튼이므로 접근 가능한 이름과 함께 버튼으로 렌더링되어야 합니다.
<!-- 나쁨 -->
<gl-icon name="close" :aria-label="__('닫기')" @click="handleClick" />
<!-- 좋음 -->
<gl-button icon="close" category="tertiary" :aria-label="__('닫기')" @click="handleClick" />
툴팁
툴팁을 추가할 때, 키보드 사용자가 툴팁을 볼 수 있도록 툴팁이 포커스를 받을 수 있어야 합니다.
아이콘과 같은 정적인 요소인 경우, 이미 포커스를 받을 수 있는 버튼으로 감쌀 수 있으므로 아이콘에 tabindex=0
을 추가할 필요가 없습니다.
다음 코드 조각은 툴팁이 있는 아이콘의 좋은 예시입니다.
- 이미 버튼이기 때문에 자동으로 포커스를 받을 수 있습니다.
- 텍스트가 없는 버튼이므로 접근 가능한 이름이
aria-label
로 제공됩니다. -
gl-hover-bg-transparent!
클래스를 사용하여 버튼이 호버될 때 회색으로 변하지 않도록 할 수 있습니다. - 필요한 경우 버튼의 여백을 제거하기 위해
gl-p-0!
클래스를 사용할 수 있습니다.
<gl-button
v-gl-tooltip
class="gl-hover-bg-transparent! gl-p-0!"
icon="warning"
category="tertiary"
:title="tooltipText"
:aria-label="__('경고')"
/>
요소 숨김
다음 테이블을 사용하여 적절한 경우 사용자에게 요소를 숨깁니다.
시각 장애가 없는 사용자에게 숨김 | 스크린 리더에서 숨김 | 시각 장애가 있는 사용자와 스크린 리더에서 숨김 |
---|---|---|
.gl-sr-only
| aria-hidden="true"
|
display: none , visibility: hidden , 또는 hidden 속성
|
시각 장애가 없는 사용자로부터 장식용 이미지 숨김
스크린 리더 사용자를 위해 잡음을 줄이기 위해 장식용 이미지는 alt=""
를 사용하여 숨깁니다.
img
요소가 아닌 경우, 인라인 SVG와 같은 이미지는 role="img"
와 alt=""
을 추가하여 숨길 수 있습니다.
gl-icon
컴포넌트는 자동으로 스크린 리더에서 아이콘을 숨기므로 aria-hidden="true"
는 사용하지 않아도 됩니다.
<!-- 좋음 - 스크린 리더에서 장식용 이미지 숨김 -->
<img src="decorative.jpg" alt="">
<svg role="img" alt="" />
<gl-icon name="epic" />
ARIA 사용 시기
의미론적 HTML을 사용하는 경우 ARIA가 필요하지 않습니다. 왜냐하면 이미 접근성이 통합되어 있기 때문입니다.
그러나 의미론적 HTML과 동일한 UI 패턴이 없는 경우가 있습니다. 이에 대한 일반적인 예로는 대화 상자(모달)와 탭이 있습니다. GitLab 특정 예시로는 담당자와 라벨 드롭다운이 있습니다. 이러한 위젯을 구축하는 데는 스크린 리더 사용자가 이해할 수 있도록 ARIA가 필요합니다. WCAG 규정을 준수하기 위해 적절한 조사와 테스트가 수행되어야 합니다.