- 간략 요약
- macOS에서 키보드 탐색 활성화
- 간략 체크리스트
- 좋은 문서 개요 제공
- 스크린 리더를 위한 접근 가능한 이름 제공
- 역할
- 키보드 전용 사용 지원
-
tabindex
- 아이콘
- 툴팁
- 요소 숨기기
- ARIA를 사용할 때
접근성 최적 관행
간략 요약
나쁜 ARIA보다 ARIA가 없는 것이 더 낫다 를 기억하고, aria-*
, role
, 및 tabindex
를 사용하기 전에 다음 권장 사항을 검토하세요.
접근성 의미가 내장된 의미론적 HTML을 사용하고, 가능하면 관련 스크린 리더 및 브라우저 조합으로 테스트하세요.
WebAIM의 백만 개 홈 페이지에 대한 접근성 분석에서 “ARIA는 더 높은 감지 오류와 상관 관계가 있다”는 것을 발견했습니다.
ARIA의 오용이 오류 증가의 주요 원인일 가능성이 높으므로, 의심스러울 경우 aria-*
, role
, 및 tabindex
를 사용하지 말고 의미론적 HTML을 유지하세요.
macOS에서 키보드 탐색 활성화
기본적으로 macOS는 tab 키를 텍스트 상자 및 목록만으로 제한합니다. 전체 키보드 탐색을 활성화하려면:
- 시스템 환경설정을 엽니다.
- 키보드를 선택합니다.
- 단축키 탭을 엽니다.
- 키보드 탐색을 사용하여 컨트롤 간 초점을 이동 설정을 활성화합니다.
브라우저별 키보드 탐색을 활성화하는 것에 대해 더 읽고 싶다면 a11yproject를 참조하세요.
간략 체크리스트
- 아이콘
-
장식적이지 않은 아이콘이
aria-label
을 가지고 있습니다. -
클릭 가능한 아이콘은 버튼이며, 즉
<gl-button icon="close" />
를 사용하고<gl-icon />
을 사용하지 않습니다. - 아이콘만 있는 버튼이
aria-label
을 가지고 있습니다.
-
장식적이지 않은 아이콘이
-
인터랙티브 요소는 Tab 키로 접근 가능하며, 가시적인 포커스 상태를 가지며.
-
툴팁이 있는 요소는 Tab 키를 사용하여 포커스할 수 있습니다.
-
불필요한
role
,tabindex
또는aria-*
속성이 있습니까? -
div
또는span
요소를p
,button
또는time
과 같은 더 의미론적인 HTML 요소로 대체할 수 있습니까?
좋은 문서 개요 제공
헤딩은 스크린 리더 사용자가 컨텐츠를 탐색하는 데 사용하는 기본 메커니즘입니다.
따라서 페이지의 제목 구조는 좋은 목차처럼 의미가 있어야 합니다.
다음을 확인해야 합니다:
- 페이지에
h1
요소가 하나만 있어야 합니다. - 제목 수준이 건너뛰어지지 않아야 합니다.
- 제목 수준이 올바르게 중첩되어야 합니다.
스크린 리더를 위한 접근 가능한 이름 제공
접근 가능한 이름이 있는 마크업을 제공하려면 모든 다음 항목을 확인하세요:
-
입력(input)이 연결된
label
을 가지고 있어야 합니다. -
버튼과 링크는 가시적인 텍스트를 가지거나, 내용이 없는 아이콘 버튼과 같이 가시적인 텍스트가 없을 때는
aria-label
을 가져야 합니다. -
이미지는
alt
특성이 있어야 합니다. -
fieldset
은 첫 번째 자식으로legend
를 가져야 합니다. -
figure
는 첫 번째 자식으로figcaption
을 가져야 합니다. -
table
은 첫 번째 자식으로caption
을 가져야 합니다.
체크박스와 라디오 입력의 그룹은 fieldset
으로 함께 그룹화되어야 하며, 그 안에 legend
가 있어야 합니다.
legend
는 체크박스 및 라디오 입력 그룹에 레이블을 제공합니다.
label
, 자식 텍스트 또는 자식 요소가 시각적으로 필요하지 않은 경우, .gl-sr-only
를 사용하여 스크린 리더를 제외한 모든 것에서 요소를 숨깁니다.
접근 가능한 이름 제공의 예
다음 하위 섹션에서는 접근 가능한 이름을 가진 HTML 요소를 렌더링하는 마크업 예제를 포함하고 있습니다.
GlFormGroup를 사용할 때 주의하세요:
-
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
예제:
<!-- 레이블이 있는 textarea -->
<gl-form-group :label="__('이슈 설명')" label-for="issue-description">
<gl-form-textarea id="issue-description" v-model="description" />
</gl-form-group>
<!-- 숨겨진 레이블이 있는 textarea -->
<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>
다수 체크박스:
<!-- 필드 세트 내에 그룹화된 여러 개의 레이블이 있는 체크박스 -->
<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>
<!-- 숨겨진 레전드가 있는 필드 세트 내에 그룹화된 여러 개의 레이블이 있는 체크박스 -->
<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>
접근 가능한 이름을 가진 라디오 입력
단일 라디오 입력:
<!-- 레이블이 있는 단일 라디오 -->
<gl-form-radio v-model="status" value="opened">
{{ __('열림') }}
</gl-form-radio>
<!-- 숨겨진 레이블이 있는 단일 라디오 -->
<gl-form-radio v-model="status" value="opened">
<span class="gl-sr-only">{{ __('열림') }}</span>
</gl-form-radio>
다수 라디오 입력:
<!-- 필드 세트 내에 그룹화된 여러 개의 레이블이 있는 라디오 입력 -->
<gl-form-group :label="__('이슈 상태')">
<gl-form-radio value="opened">{{ __('열림') }}</gl-form-radio>
<gl-form-radio value="closed">{{ __('닫힘') }}</gl-form-radio>
</gl-form-group>
<!-- 또는 -->
<gl-form-group :label="__('이슈 상태')">
<gl-form-radio-group v-model="selected" :options="options" />
</gl-form-group>
<!-- 숨겨진 레전드가 있는 필드 세트 내에 그룹화된 여러 개의 레이블이 있는 라디오 입력 -->
<gl-form-group :label="__('이슈 상태')" label-sr-only>
<gl-form-radio value="opened">{{ __('열림') }}</gl-form-radio>
<gl-form-radio value="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="__('Key')" :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> 에서 <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
와 같은 다른 방식으로 시각적 포커스 상태를 유지하도록 하세요.
자세한 내용은 Pajamas Keyboard-only page를 참조하세요.
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"
또는 그 이상의 사용을 피하세요.
아이콘
아이콘은 세 가지 유형으로 나눌 수 있습니다:
- 장식적인 아이콘
- 의미를 전달하는 아이콘
- 클릭 가능한 아이콘
장식적인 아이콘
아이콘이 UI에서 제거될 때 사용자에게 정보 손실이 없는 경우 장식적입니다.
GitLab 내의 대부분 아이콘이 장식적이기 때문에, GlIcon
은 자동으로 그 렌더링된 아이콘을 화면 읽기 기기에서 숨깁니다.
따라서 GlIcon
에 aria-hidden="true"
를 추가할 필요가 없으며, 이는 중복됩니다.
<!-- 불필요 — gl-icon은 기본적으로 화면 읽기 기기에서 아이콘을 숨깁니다 -->
<gl-icon name="rocket" aria-hidden="true" />
<!-- 좋음 -->
<gl-icon name="rocket" />
의미를 전달하는 아이콘
아이콘은 UI에서 제거될 때 사용자에게 정보 손실이 있는 경우 의미를 전달합니다.
예를 들어, 기밀 아이콘은 이슈가 기밀임을 전달하며, “Confidential”이라는 텍스트가 옆에 없습니다.
정보를 전달하는 아이콘은 화면 읽기 기기 사용자에게 정보가 전달될 수 있도록 접근 가능한 이름을 가져야 합니다.
<!-- 나쁨 -->
<gl-icon name="eye-slash" />
<!-- 좋음 -->
<gl-icon name="eye-slash" :aria-label="__('Confidential issue')" />
클릭 가능한 아이콘
클릭 가능한 아이콘은 의미론적으로 버튼이므로 버튼으로 렌더링되어야 하며, 접근 가능한 이름을 가져야 합니다.
<!-- 나쁨 -->
<gl-icon name="close" :aria-label="__('Close')" @click="handleClick" />
<!-- 좋음 -->
<gl-button icon="close" category="tertiary" :aria-label="__('Close')" @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="__('Warning')"
/>
요소 숨기기
적절할 경우 사용자로부터 요소를 숨기기 위해 다음 표를 사용하세요.
시각 사용자로부터 숨기기 | 스크린 리더 사용자로부터 숨기기 | 시각 및 스크린 리더 사용자 모두로부터 숨기기 |
---|---|---|
.gl-sr-only |
aria-hidden="true" |
display: none , visibility: hidden , 또는 hidden 속성 |
스크린 리더로부터 장식 이미지를 숨기기
스크린 리더 사용자에게 소음을 줄이기 위해, 장식 이미지를 alt=""
를 사용하여 숨깁니다.
이미지가 img
요소가 아닌 경우, 예를 들어 인라인 SVG와 같은 경우, role="img"
및 alt=""
를 추가하여 숨길 수 있습니다.
gl-icon
구성 요소는 아이콘을 스크린 리더로부터 자동으로 숨기므로, aria-hidden="true"
는 gl-icon
을 사용할 때 필요하지 않습니다.
<!-- 잘함 - 장식 이미지가 스크린 리더로부터 숨겨짐 -->
<img src="decorative.jpg" alt="">
<svg role="img" alt="" />
<gl-icon name="epic" />
ARIA를 사용할 때
의미 있는 HTML을 사용할 때는 ARIA가 필요하지 않으며, 이미 접근성을 포함하고 있습니다.
하지만 의미 있는 HTML의 동등물이 없는 UI 패턴이 일부 있습니다. 이러한 패턴의 일반적인 예는 대화상자(모달)와 탭입니다.
GitLab-specific 예로는 담당자 및 레이블 드롭다운이 있습니다. 이러한 위젯을 만들기 위해 스크린 리더가 이해할 수 있도록 ARIA가 필요합니다.
WCAG 준수를 보장하기 위해 적절한 연구와 테스트가 수행되어야 합니다.