Aksesibilitas

Mengapa aksesibilitas?

Aksesibilitas web (juga dikenal dengan istilah a11y) adalah perancangan dan pembuatan situs web yang dapat digunakan oleh semua orang. Dukungan aksesibilitas diperlukan agar teknologi asistif—misalnya alat pembaca layar untuk pengguna tuna netra—dapat memahami dan menyampaikan isi halaman web.

React sepenuhnya mendukung pembuatan situs web yang aksesibel, pada umumnya melalui penggunaan teknik-teknik HTML standar.

Standar dan Pedoman

WCAG

Web Content Accessibility Guidelines (Panduan Aksesibilitas Konten Web) menyediakan panduan untuk membuat situs web yang aksesibel.

Daftar cek dari WCAG di bawah ini dapat memberikan gambaran umum:

WAI-ARIA

Dokumen Web Accessibility Initiative - Accessible Rich Internet Applications (Inisiatif Aksesibilitas Web - Aplikasi Internet Kaya yang Aksesibel) berisi teknik-teknik untuk membuat widget JavaScript yang sepenuhnya aksesibel.

JSX sepenuhnya mendukung semua atribut HTML aria-*. Berbeda dengan sebagian besar properti DOM dan atribut lain di React yang ditulis dengan camelCase (inisial kata selain kata pertama menggunakan huruf kapital), atribut aria-* menggunakan hyphen-case (seluruh kata ditulis dengan huruf kecil dan dipisahkan tanda “-“) karena atribut-atribut tersebut ditulis dalam HTML biasa:

<input
  type="text"
  aria-label={labelText}
  aria-required="true"
  onChange={onchangeHandler}
  value={inputValue}
  name="name"
/>

HTML Semantik

HTML yang semantik merupakan landasan aksesibilitas dalam suatu aplikasi web. Dengan menggunakan berbagai elemen HTML untuk memperkuat makna informasi di situs web kita, sering kali kita dapat memperoleh manfaat aksesibilitas secara cuma-cuma.

Kadang kita melanggar kaidah semantik HTML dengan menambahkan elemen <div> ke dalam JSX agar kode React berjalan dengan baik, khususnya saat berurusan dengan elemen-elemen daftar (<ol>, <ul>, dan <dl>) serta <table> HTML. Dalam kasus demikian, alih-alih <div>, gunakan Fragment React untuk mengelompokkan beberapa elemen.

Contohnya,

import React, { Fragment } from 'react';

function ListItem({ item }) {
  return (
    <Fragment>
      <dt>{item.term}</dt>
      <dd>{item.description}</dd>
    </Fragment>
  );
}

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        <ListItem item={item} key={item.id} />
      ))}
    </dl>
  );
}

Anda juga dapat memetakan sekumpulan item pada senarai (array) berisi fragmen, sama seperti elemen-elemen lainnya:

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // Fragment juga harus memiliki prop `key` untuk memetakan koleksi
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </Fragment>
      ))}
    </dl>
  );
}

Jika tag Fragment Anda tidak membutuhkan props dan jika peralatan Anda mendukung, Anda dapat menggunakan sintaks singkat:

function ListItem({ item }) {
  return (
    <>
      <dt>{item.term}</dt>
      <dd>{item.description}</dd>
    </>
  );
}

Untuk info selengkapnya, lihat dokumentasi Fragment.

Formulir yang Aksesibel

Pelabelan

Setiap elemen kendali formulir HTML, seperti <input> dan <textarea>, perlu diberi label yang memenuhi prinsip aksesibilitas. Kita perlu memberi label deskriptif yang juga dapat diakses oleh aplikasi pembaca layar (screen reader).

Sumber-sumber di bawah ini menunjukkan caranya:

Praktik-praktik standar HTML tersebut dapat langsung digunakan di React, namun harap diingat bahwa atribut for ditulis sebagai htmlFor di JSX:

<label htmlFor="namedInput">Name:</label>
<input id="namedInput" type="text" name="name"/>

Menyampaikan notifikasi kesalahan ke pengguna

Keadaan kesalahan (error) perlu dipahami semua pengguna. Tautan di bawah ini menunjukkan cara menyampaikan pesan kesalahan yang juga dapat diakses oleh pengguna aplikasi pembaca layar:

Kendali Fokus

Pastikan aplikasi web Anda dapat dioperasikan sepenuhnya hanya dengan menggunakan keyboard:

Fokus keyboard dan garis luar fokus

Fokus keyboard mengacu pada elemen saat ini di DOM yang dipilih untuk menerima masukan dari keyboard. Kita sering melihatnya dalam bentuk garis luar (outline) fokus seperti pada gambar di bawah ini:

Garis luar fokus berwarna biru di sekeliling tautan yang dipilih.

Jangan gunakan CSS untuk menghilangkan garis luar ini, misalnya dengan menggunakan outline: 0, kecuali Anda menggantinya dengan implementasi sejenis untuk garis luar fokus.

Mekanisme untuk melompat ke konten yang diinginkan

Sediakan mekanisme untuk memungkinkan pengguna “melompati” bagian navigasi di aplikasi Anda, karena ini dapat memudahkan dan mempercepat navigasi menggunakan keyboard.

Tautan melompati navigasi (Skiplinks atau Skip Navigation Links) adalah tautan navigasi tersembunyi yang hanya muncul di layar saat pengguna keyboard berinteraksi dengan halaman tersebut. Fitur ini sangat mudah untuk diimplementasikan dengan menggunakan tautan internal halaman dan modifikasi tampilan (styling):

Gunakan juga elemen dan peran landmark, seperti <main> dan <aside>, untuk menandai wilayah halaman yang berguna bagi pengguna teknologi asistif untuk melakukan navigasi cepat ke bagian-bagian tersebut.

Baca selengkapnya tentang kegunaan elemen-elemen tersebut untuk meningkatkan aksesibilitas di sini:

Mengelola fokus secara programatik

Aplikasi React kita memodifikasi DOM HTML secara terus menerus sepanjang runtime, yang kadang menyebabkan hilangnya fokus keyboard atau berpindahnya fokus ke elemen yang tak diinginkan. Untuk mengatasi masalah ini, kita perlu mengarahkan fokus keyboard secara programatik ke arah yang tepat. Misalnya, dengan mengembalikan fokus keyboard ke tombol yang membuka jendela modal setelah jendela modal tersebut ditutup.

Dokumentasi Web MDN mengangkat topik ini dan menjelaskan cara membuat widget JavaScript yang dapat dioperasikan dengan keyboard.

Untuk menetapkan fokus pada React, kita dapat menggunakan Ref ke elemen DOM.

Dengan kode di bawah ini, pertama-tama kita membuat ref ke elemen dalam JSX dari sebuah kelas komponen:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // Buat ref untuk menyimpan elemen DOM textInput
    this.textInput = React.createRef();
  }
  render() {
  // Gunakan callback `ref` untuk menyimpan referensi ke elemen 
  // DOM input teks dalam field instans (misalnya, this.textInput).
    return (
      <input
        type="text"
        ref={this.textInput}
      />
    );
  }
}

Lalu, kita dapat memfokuskan ref tersebut ke posisi lain di komponen kita saat diperlukan:

focus() {
  // Fokuskan pada input teks secara eksplisit menggunakan API DOM mentah
  // Catatan: kita mengakses “current” untuk mendapatkan simpul DOM
  this.textInput.current.focus();
}

Kadang komponen induk perlu menetapkan fokus pada sebuah elemen dalam komponen anak yang meneruskan ref induknya ke simpul DOM. Kita dapat melakukannya dengan mengekspos ref DOM ke komponen induk melalui prop khusus pada komponen anak yang meneruskan ref induk ke simpul DOM anak.

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.inputElement = React.createRef();
  }
  render() {
    return (
      <CustomTextInput inputRef={this.inputElement} />
    );
  }
}

// Sekarang Anda dapat menetapkan fokus saat diperlukan.
this.inputElement.current.focus();

Saat menggunakan HOC (Higher-Order Component) untuk membuat komponen baru dari komponen yang sudah ada, disarankan untuk melakukan ref forwarding ke komponen yang dibungkus dengan menggunakan fungsi forwardRef React. Jika HOC pihak ketiga tidak mengimplementasikan ref forwarding, pola di atas masih dapat digunakan sebagai fallback.

Salah satu contoh pengelolaan fokus yang baik adalah react-aria-modal. Ini adalah contoh yang relatif langka dari jendela modal yang sepenuhnya aksesibel. Selain menetapkan fokus awal pada tombol “batal” (mencegah pengguna keyboard tidak sengaja mengaktifkan opsi “sukses”) dan mengurung fokus keyboard di dalam modal, react-aria-modal juga mengembalikan fokus ke elemen awal yang memicu modal tersebut.

Catatan:

Ini adalah fitur aksesibilitas yang sangat penting, namun pertimbangkan matang-matang sebelum menggunakan teknik ini. Gunakanlah teknik ini untuk memperbaiki alur fokus keyboard yang mengalami gangguan, bukan untuk mencoba mengantisipasi cara pengguna memakai aplikasi.

Event mouse dan penunjuk

Pastikan bahwa semua fungsionalitas yang terekspos melalui event mouse atau penunjuk (pointer) juga dapat diakses hanya dengan menggunakan keyboard. Bergantung pada peranti penunjuk akan mengakibatkan kasus di mana pengguna keyboard tidak dapat menggunakan aplikasi Anda.

Sebagai gambaran, mari kita lihat contoh umum rusaknya aksesibilitas akibat event klik. Ini adalah pola “klik di luar”, di mana pengguna bisa menonaktifkan kotak popover yang terbuka dengan cara mengklik di luar elemen tersebut.

Tombol buka-tutup yang membuka kotak daftar popover yang diimplementasikan dengan pola klik di luar popover dan dioperasikan dengan mouse. Kotak popover berhasil ditutup.

Pola ini umumnya diimplementasikan dengan menempelkan event click pada obyek window yang menutup popover:

class OuterClickExample extends React.Component {
  constructor(props) {
    super(props);

    this.state = { isOpen: false };
    this.toggleContainer = React.createRef();

    this.onClickHandler = this.onClickHandler.bind(this);
    this.onClickOutsideHandler = this.onClickOutsideHandler.bind(this);
  }

  componentDidMount() {
    window.addEventListener('click', this.onClickOutsideHandler);
  }

  componentWillUnmount() {
    window.removeEventListener('click', this.onClickOutsideHandler);
  }

  onClickHandler() {
    this.setState(currentState => ({
      isOpen: !currentState.isOpen
    }));
  }

  onClickOutsideHandler(event) {
    if (this.state.isOpen && !this.toggleContainer.current.contains(event.target)) {
      this.setState({ isOpen: false });
    }
  }

  render() {
    return (
      <div ref={this.toggleContainer}>
        <button onClick={this.onClickHandler}>Pilih salah satu opsi</button>
        {this.state.isOpen ? (
          <ul>
            <li>Opsi 1</li>
            <li>Opsi 2</li>
            <li>Opsi 3</li>
          </ul>
        )}
      </div>
    );
  }
}

Kode ini mungkin berfungsi dengan baik untuk pengguna yang memakai peranti penunjuk, misalnya mouse. Tapi jika dioperasikan hanya dengan keyboard, fungsionalitasnya akan rusak saat pengguna menekan tombol tab ke elemen selanjutnya, karena objek window tidak pernah menerima event click. Hal ini dapat mengakibatkan fungsionalitas yang terhalang, yang menghambat pengguna untuk menggunakan aplikasi Anda.

Tombol buka-tutup yang membuka kotak daftar popover yang diimplementasikan dengan pola klik di luar popover dan dioperasikan dengan keyboard. Kotak popover tidak tertutup saat fokus berpindah (blur) dan menghalangi elemen lainnya di layar.

Fungsionalitas yang sama dapat dicapai dengan menggunakan event handler yang sesuai, misalnya onBlur dan onFocus:

class BlurExample extends React.Component {
  constructor(props) {
    super(props);

    this.state = { isOpen: false };
    this.timeOutId = null;

    this.onClickHandler = this.onClickHandler.bind(this);
    this.onBlurHandler = this.onBlurHandler.bind(this);
    this.onFocusHandler = this.onFocusHandler.bind(this);
  }

  onClickHandler() {
    this.setState(currentState => ({
      isOpen: !currentState.isOpen
    }));
  }

  // Kita menutup popover pada detik selanjutnya menggunakan setTimeout.
  // Ini perlu, karena kita harus memeriksa terlebih dahulu 
  // apakah ada anak lain dari elemen ini yang telah menerima 
  // fokus saat event blur diluncurkan sebelum event fokus yang baru.
  onBlurHandler() {
    this.timeOutId = setTimeout(() => {
      this.setState({
        isOpen: false
      });
    });
  }

  // Jika ada anak yang menerima fokus, jangan tutup popover.
  onFocusHandler() {
    clearTimeout(this.timeOutId);
  }

  render() {
    // React membantu kita dengan melakukan bubbling 
    // pada event blur dan fokus ke elemen induk.
    return (
      <div onBlur={this.onBlurHandler}
           onFocus={this.onFocusHandler}>
        <button onClick={this.onClickHandler}
                aria-haspopup="true"
                aria-expanded={this.state.isOpen}>
          Pilih salah satu opsi
        </button>
        {this.state.isOpen && (
          <ul>
            <li>Opsi 1</li>
            <li>Opsi 2</li>
            <li>Opsi 3</li>
          </ul>
        )}
      </div>
    );
  }
}

Kode ini mengekspos fungsionalitas pada peranti penunjuk maupun pengguna keyboard. Perhatikan adanya tambahan props aria-* untuk mendukung pengguna pembaca layar. Agar contoh ini ringkas, event keyboard untuk mengaktifkan interaksi arrow key pada opsi-opsi popover belum diimplementasikan.

Kotak daftar 'popover' yang menutup dengan benar baik untuk pengguna mouse maupun keyboard.

Ini salah satu contoh dari banyak kasus di mana bergantung hanya pada event mouse dan penunjuk akan merusak fungsionalitas untuk pengguna keyboard. Selalu lakukan pengujian dengan keyboard agar dapat segera melihat area bermasalah, yang kemudian dapat diperbaiki dengan menggunakan event handler yang sadar akan keyboard.

Widget yang Lebih Kompleks

Pengalaman pengguna (user experience) yang lebih kompleks tidak semestinya mengurangi aksesibilitas. Meskipun aksesibilitas paling mudah dicapai dengan menulis kode yang sedekat mungkin dengan HTML, bahkan widget paling rumit sekalipun dapat dibuat dengan aksesibel.

Di sini kita membutuhkan pengetahuan tentang Peran (Role) ARIA, juga State dan Properti ARIA, yang merupakan “kotak peralatan” berisi atribut-atribut HTML yang sepenuhnya didukung di JSX dan memungkinkan kita untuk menyusun komponen-komponen React yang sepenuhnya aksesibel dan sangat fungsional.

Masing-masing jenis widget memiliki pola desain yang spesifik; dan baik pengguna maupun perangkat (agent) sudah memiliki ekspekstasi tentang fungsi widget tersebut:

Hal-hal Lain untuk Dipertimbangkan

Mengatur bahasa

Beri keterangan bahasa manusia yang digunakan pada isi teks di halaman, karena perangkat lunak pembaca layar menggunakannya untuk memilih pengaturan suara yang tepat:

Mengatur judul dokumen

Atur <title> dokumen agar menggambarkan dengan tepat isi halaman yang sedang dibuka, untuk menmastikan pengguna tetap sadar tentang konteks halaman saat ini:

Kita dapat mengaturnya di React menggunakan komponen React Document Title.

Kontras warna

Pastikan seluruh teks yang dapat dibaca di situs web Anda memiliki kontras warna yang cukup, agar semudah mungkin untuk dibaca oleh pengguna dengan kemampuan penglihatan lemah (low vision):

Akan membosankan jika harus menghitung kombinasi warna secara manual untuk setiap kasus penggunaan di halaman website Anda, maka Anda dapat membuat perhitungan seluruh palet warna yang aksesibel dengan Colorable.

Uji kontras warna juga termasuk dalam alat aXe maupun WAVE yang disebut di bawah ini. Alat-alat tersebut akan melaporkan jika ada kesalahan kontras.

Jika Anda ingin mengembangkan kemampuan uji kontras, Anda dapat menggunakan alat-alat ini:

Alat-alat Pengembangan dan Pengujian

Ada sejumlah alat yang dapat kita gunakan untuk membantu pembuatan aplikasi web yang aksesibel.

Keyboard

Bentuk uji coba yang paling mudah sekaligus salah satu yang terpenting sejauh ini ialah menguji apakah seluruh situs web Anda dapat dijangkau dan digunakan hanya dengan menggunakan keyboard. Caranya adalah:

  1. Cabut mouse Anda.
  2. Gunakan tombol Tab dan Shift+Tab untuk menjelajah.
  3. Gunakan tombol Enter untuk mengaktifkan elemen.
  4. Bila dibutuhkan, gunakan tombol panah pada keyboard untuk berinteraksi dengan beberapa elemen, misalnya menu dan dropdown.

Bantuan pengembangan

Kita dapat memeriksa beberapa fitur aksesibilitas secara langsung dalam kode JSX kita. Sering kali, pemeriksaan intellisense untuk peran, state, dan properti ARIA sudah tersedia di IDE yang sadar akan JSX. Kita juga dapat mengakses peralatan berikut ini:

eslint-plugin-jsx-a11y

Plugin eslint-plugin-jsx-a11y untuk ESLint menyediakan umpan balik linting Abstract Syntax Tree (AST) tentang masalah-masalah aksesibilitas di JSX Anda. Banyak IDE memungkinkan Anda untuk mengintegrasikan hasil penemuan tersebut langsung ke jendela analisa kode dan kode sumber.

Create React App memiliki plugin ini dengan sebagian aturan aksesibilitas yang sudah aktif. Jika Anda ingin mengaktifkan lebih banyak lagi aturan aksesibiltas, Anda dapat membuat file .eslintrc di root projek Anda dengan isi seperti ini:

{
  "extends": ["react-app", "plugin:jsx-a11y/recommended"],
  "plugins": ["jsx-a11y"]
}

Menguji aksesibilitas di browser

Ada sejumlah alat yang dapat menjalankan audit aksesibilitas pada halaman-halaman web dari browser Anda. Gunakanlah bersama metode pemeriksaan aksesibilitas lainnya yang disebutkan di sini, karena alat-alat ini hanya menguji aksesibilitas teknis kode HTML Anda.

aXe, aXe-core and react-axe

Deque Systems menawarkan aXe-core untuk menguji aksesibilitas aplikasi Anda secara otomatis dari awal hingga akhir. Modul ini mencakup integrasi Selenium.

The Accessibility Engine atau aXe adalah ekstensi browser pemeriksa aksesibilitas yang dibuat berdasarkan aXe-core.

Anda juga dapat menggunakan modul react-axe untuk melaporkan temuan-temuan aksesibilitas ini secara langsung ke console saat melakukan pengembangan dan menelusuri kesalahan.

WebAIM WAVE

Web Accessibility Evaluation Tool adalah ekstensi browser lainnya untuk memeriksa aksesibilitas.

Pemeriksa aksesibilitas dan Pohon Aksesibilitas

Pohon Aksesibilitas (Accessibility Tree) adalah bagian dari pohon DOM yang berisi objek-objek aksesibel untuk setiap elemen DOM yang seharusnya terekspos ke teknologi asistif, misalnya pembaca layar.

Di beberapa browser, kita dapat dengan mudah melihat informasi aksesibilitas untuk masing-masing elemen di pohon aksesibilitas:

Pembaca layar

Menguji dengan pembaca layar harus menjadi bagian uji aksesibilitas Anda.

Harap perhatikan bahwa kombinasi browser / pembaca layar berpengaruh. Anda disarankan menguji aplikasi Anda pada browser yang paling sesuai dengan pembaca layar pilihan Anda.

Pembaca Layar yang Umum Digunakan

NVDA di Firefox

NonVisual Desktop Access atau NVDA adalah pembaca layar bersumber terbuka (open source) untuk Windows yang banyak digunakan.

Berikut ini panduan untuk menggunakan NVDA dengan baik:

VoiceOver di Safari

VoiceOver adalah pembaca layar yang terintegrasi dengan peranti Apple.

Berikut ini panduan cara mengaktifkan dan menggunakan VoiceOver:

JAWS di Internet Explorer

Job Access With Speech atau JAWS adalah pembaca layar untuk Windows yang banyak digunakan.

Berikut ini panduan untuk menggunakan JAWS dengan baik:

Pembaca Layar Lain

ChromeVox di Google Chrome

ChromeVox adalah pembaca layar yang terintegrasi dengan Chromebook, dan tersedia sebagai ekstensi browser Google Chrome.

Berikut ini panduan untuk menggunakan ChromeVox dengan baik: