웹 접근성 모범 사례

간단한 요약

나쁜 ARIA보다는 ARIA를 사용하지 않는 것이 더 좋다는량을 검토한 후 aria-*, role, 및 tabindex를 사용하기 전에 다음 권장 사항을 검토하세요. 웹 접근성이 내장된 의미있는 HTML을 사용하고 이상적으로 스크린 리더 및 브라우저의 관련 조합으로 테스트하세요.

WebAIM의 상위 백만 홈페이지 접근성 분석에서 “ARIA가 높은 검출 가능한 오류와 연관이 있는” 것으로 나타났습니다. ARIA의 잘못된 사용이 증가된 오류의 큰 원인일 가능성이 높으므로, 의심스러울 때는 aria-*, role, 및 tabindex 대신 의미 있는 HTML을 사용하는 것이 좋습니다.

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

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

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

macOS용 브라우저별 키보드 탐색 기능을 활성화하는 방법에 대해 자세히 알아보려면 a11yproject를 참조하세요.

빠른 체크리스트

좋은 문서 개요 제공

스크린 리더 사용자가 콘텐츠를 탐색하는데 사용하는 주요 메커니즘은 제목입니다. 따라서 페이지의 제목 구조는 좋은 목차처럼 이해하기 쉬워야 합니다. 다음을 확인해야 합니다:

  • 페이지에는 하나의 h1 요소만 있습니다.
  • 제목 수준이 건너뛰어지지 않습니다.
  • 제목 수준이 올바르게 중첩됩니다.

스크린 리더용 접근 가능한 이름 제공

접근 가능한 이름을 제공하기 위해 모든:

  • 입력에는 연관된 label이 있습니다(#examples-of-providing-accessible-names).
  • 버튼과 링크에는 보이는 텍스트 또는, 보이는 텍스트가 없는 경우 aria-label이 있습니다(예: 내용이 없는 아이콘 버튼의 경우).
  • 이미지에는 alt 속성이 있습니다.
  • fieldset는 첫 번째 자식으로 legend를 갖습니다.
  • figure는 첫 번째 자식으로 figcaption를 갖습니다.
  • table는 첫 번째 자식으로 caption을 갖습니다.

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

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

접근 가능한 이름 제공의 예

다음 소목은 접근 가능한 이름을 갖는 HTML 요소를 렌더링하는 마크업 예제를 포함합니다.

GlFormGroup를 사용할 때:

  • label 속성만 전달하면 label 값이 포함된 legend를 렌더링하는 fieldset가 렌더링됩니다.
  • labellabel-for 속성을 모두 전달하면 label이 동일한 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>

<!-- 숨은 범례가 있는 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>

<!-- 숨은 범례가 있는 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>

키보드 전용 사용 지원

키보드 사용자는 페이지 상에서 자신이 어디에 있는지 이해하기 위해 포커스 아웃라인에 의존합니다. 따라서 다음 사항을 확인해야 합니다.

  • 키보드 포커스를 받을 수 있어야 합니다.
  • 시각적으로 포커스 상태여야 합니다.

기본적으로 이러한 동작을 제공하는 a(GlLink) 및 button(GlButton)과 같은 의미 있는 HTML을 사용하세요.

다음 사항을 염두에 두세요.

  • 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"이상의 사용을 피하세요.(tabindex="1" 이상 사용을 항상 피하세요).

아이콘

아이콘은 다음과 같이 세 가지 유형으로 나뉠 수 있습니다.

  • 정보를 전달하지 않는 장식용 아이콘
  • 의미를 전달하는 아이콘
  • 클릭할 수 있는 아이콘

정보를 전달하지 않는 장식용 아이콘

아이콘이 UI에서 제거되어도 사용자에게 정보가 손실되지 않을 때 장식용으로 간주됩니다. GitLab 내의 대다수 아이콘은 장식적이므로 GlIcon은 암시적으로 스크린 리더로부터 아이콘을 숨깁니다. 따라서 장식용 GlIconaria-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 준수를 보장하기 위해 적절한 연구와 테스트가 수행되어야 합니다.