접근성 최적 관행

간략 요약

나쁜 ARIA보다 ARIA가 없는 것이 더 낫다 를 기억하고, aria-*, role, 및 tabindex를 사용하기 전에 다음 권장 사항을 검토하세요.

접근성 의미가 내장된 의미론적 HTML을 사용하고, 가능하면 관련 스크린 리더 및 브라우저 조합으로 테스트하세요.

WebAIM의 백만 개 홈 페이지에 대한 접근성 분석에서 “ARIA는 더 높은 감지 오류와 상관 관계가 있다”는 것을 발견했습니다.

ARIA의 오용이 오류 증가의 주요 원인일 가능성이 높으므로, 의심스러울 경우 aria-*, role, 및 tabindex를 사용하지 말고 의미론적 HTML을 유지하세요.

macOS에서 키보드 탐색 활성화

기본적으로 macOS는 tab 키를 텍스트 상자 및 목록만으로 제한합니다. 전체 키보드 탐색을 활성화하려면:

  1. 시스템 환경설정을 엽니다.
  2. 키보드를 선택합니다.
  3. 단축키 탭을 엽니다.
  4. 키보드 탐색을 사용하여 컨트롤 간 초점을 이동 설정을 활성화합니다.

브라우저별 키보드 탐색을 활성화하는 것에 대해 더 읽고 싶다면 a11yproject를 참조하세요.

간략 체크리스트

  • 텍스트, 선택, 체크박스, 라디오, 파일, 및 전환 입력이 접근 가능한 이름을 가지고 있습니다.

  • 버튼, 링크, 및 이미지가 설명이 잘 된 접근 가능한 이름을 가지고 있습니다.

  • 아이콘
  • 인터랙티브 요소는 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이 렌더링됩니다.

  • labellabel-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)을 사용하세요.

다음 사항을 유의하세요:

  • TabShift-Tab는 정적 콘텐츠가 아닌 상호작용 요소 간에만 이동해야 합니다.
  • :hover 스타일을 추가할 때, 대부분의 경우 마우스 키보드 사용자 모두를 위해 :focus 스타일도 추가해야 합니다.
  • 상호작용 요소의 outline을 제거할 경우, box-shadow와 같은 다른 방식으로 시각적 포커스 상태를 유지하도록 하세요.

자세한 내용은 Pajamas Keyboard-only page를 참조하세요.

tabindex

tabindex를 사용하는 것보다 사용하지 않는 것을 선호합니다. 이유는 다음과 같습니다:

  • button(GlButton)과 같은 의미론적 HTML을 사용하면 암묵적으로 tabindex="0"이 제공됩니다.
  • 탭 순서는 시각적 읽기 순서와 일치해야 하며, 긍정적인 tabindex는 이를 방해합니다.

요소를 상호작용 가능하게 만들기 위해 tabindex="0" 사용을 피하세요

divspan 태그 대신 상호작용 가능한 요소를 사용하세요.

예를 들어:

  • 요소가 클릭 가능해야 하는 경우, 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은 자동으로 그 렌더링된 아이콘을 화면 읽기 기기에서 숨깁니다.

따라서 GlIconaria-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 준수를 보장하기 위해 적절한 연구와 테스트가 수행되어야 합니다.