접근성 최고의 실천 방법

빠른 요약

누락된 ARIA가 잘못된 ARIA보다 나을 때를 참고하여 aria-*, role, 및 tabindex를 사용하기 전에 다음 권장 사항을 검토하십시오. 액세스 가능한 의미론적 HTML을 사용하면 좋으며, 이상적으로 해당 스크린 리더 및 브라우저의 조합으로 테스트하세요.

웹AIM이 수백만 홈페이지에 대한 접근성 분석에서 “ARIA가 검출 가능한 오류와 상관관계가 있다”고 발견되었습니다. ARIA의 잘못된 사용이 증가된 오류의 큰 원인이라고 추측되므로, 의심스러울 때는 aria-*, role, 및 tabindex를 사용하지 말고 의미론적 HTML을 사용하세요.

macOS에서 키보드 탐색 기능 활성화

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

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

여기에서 브라우저별 키보드 탐색 기능을 활성화하는 방법을 자세히 읽을 수 있습니다.

빠른 체크리스트

  • 텍스트, 선택, 체크박스, 라디오, 파일, 및 토글 입력란에는 액세스 가능한 이름이 있습니다.
  • 버튼, 링크, 및 이미지에는 설명적인 액세스 가능한 이름이 있습니다.
  • 아이콘
    • 비장식적인 아이콘에는 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이 있습니다.

체크박스와 라디오 입력란의 그룹은 fieldsetlegend로 묶여야 합니다. legend는 체크박스 및 라디오 입력란 그룹에 레이블을 제공합니다.

label, 자식 텍스트 또는 자식 요소가 시각적으로 표시되지 않는 경우 .gl-sr-only를 사용하여 화면 판독기를 제외한 모든 것에서 해당 요소를 숨깁니다.

액세스 가능한 이름을 제공하는 예제

다음 소목은 HTML 요소를 액세스 가능한 이름으로 렌더링하는 마크업의 예제를 포함하고 있습니다.

GlFormGroup를 사용할 때:

  • label 속성만 전달하면 label 값이 포함된 legend가 있는 fieldset이 렌더링됩니다.
  • labellabel-for 속성을 모두 전달하면 동일한 label-for ID를 가진 양식 입력란을 가리키는 label이 렌더링됩니다.

접근 가능한 이름이 있는 텍스트 입력

GlFormGroup를 사용할 때 label 속성만으로는 입력란에 접근 가능한 이름을 제공할 수 없습니다. 입력란에 접근 가능한 이름을 제공하려면 label-for 속성도 제공해야 합니다.

텍스트 입력 예시:

<!-- 라벨이 있는 입력란 -->
<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>

<!-- 숨겨진 legend를 가진 fieldset 내에서 그룹화된 여러 라벨이 있는 체크박스 -->
<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>

여러 라디오 입력:

<!-- fieldset 내에서 그룹화된 여러 라벨이 있는 라디오 입력 -->
<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>

<!-- 숨겨진 legend를 가진 fieldset 내에서 그룹화된 여러 라벨이 있는 라디오 입력 -->
<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="__('키')" :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 키보드만 사용 페이지를 참조하세요.

tabindex

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="로켓" aria-hidden="true" />`

<!-- 좋은 예 -->
<gl-icon name="로켓" />`

정보를 전달하는 아이콘

아이콘이 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=""를 사용하여 장식용 이미지를 숨깁니다. 이미지가 인라인 SVG와 같은 img 요소가 아닌 경우에는 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 패턴이 있습니다. 이러한 UI 패턴의 일반적인 예는 대화상자 (모달)와 탭입니다. GitLab의 경우 수행자 및 레이블 드롭다운과 같은 다양한 예가 있습니다. 이러한 위젯을 구축하려면 화면 판독기 사용자가 이해할 수 있도록 ARIA가 필요합니다. WCAG에 대한 규정 준수를 보장하기 위해 적절한 연구와 테스트가 수행되어야 합니다.