Skip to main content

無障礙網站實作範例

無障礙 Icon Buttons

<button>Help!</button>
<button>Help!</button>

<!-- 語音無法辨識 icon -->
<button>
<i class="icon icon-help"></i>
</button>

<!-- 考量無障礙原則進行優化: 加上隱藏文字說明,並將 icon 設為輔助軟體不讀取 -->
<button>
<span class="visuallyhidden">Help!</span>
<i class="icon icon-help" aria-hidden="true"></i>
</button>
<div class="button">
<svg width="32" height="32" viewBox="0 0 32 32" class="icon">
<path
d="M14 24h4v-4h-4v4zM16 8c-3 0-6 3-6 6h4c0-1 1-2 2-2s2 1 2 2c0 2-4 2-4 4h4c2-0.688 4-2 4-5s-3-5-6-5zM16 0c-8.844 0-16 7.156-16 16s7.156 16 16 16 16-7.156 16-16-7.156-16-16-16zM16 28c-6.625 0-12-5.375-12-12s5.375-12 12-12 12 5.375 12 12-5.375 12-12 12z"
></path>
</svg>
</div>

<!-- 考量無障礙原則進行優化: div 加上 role 和 tabindex,svg 加上 title 和 aria-labelledby -->
<!-- tabindex 讓預設不可聚焦物件改變為可聚焦 -->
<div class="button" role="button" tabindex="0">
<svg
width="32"
height="32"
viewBox="0 0 32 32"
class="icon"
aria-labelledby="svgtitle"
>
<title id="svgtitle">Help!</title>
<path
d="M14 24h4v-4h-4v4zM16 8c-3 0-6 3-6 6h4c0-1 1-2 2-2s2 1 2 2c0 2-4 2-4 4h4c2-0.688 4-2 4-5s-3-5-6-5zM16 0c-8.844 0-16 7.156-16 16s7.156 16 16 16 16-7.156 16-16-7.156-16-16-16zM16 28c-6.625 0-12-5.375-12-12s5.375-12 12-12 12 5.375 12 12-5.375 12-12 12z"
></path>
</svg>
</div>

無障礙按鈕事件

當使用 div 為按鈕的父層時,要加上 role 和 tabindex,另外,考量鍵盤使用者,除了原本的 onClick 事件之外,也要加上 onKeydown 事件

<button aria-label="Help" onClick="doStuff()">
<i class="icon icon-help"></i>
</button>

<!-- 考量無障礙原則進行優化: div 加上 role 和 tabindex,以及 onKeydown 事件 -->

<div
class="button"
role="button"
tabindex="0"
aria-label="Menu"
onClick="doStuff()"
onKeydown="doStuff()"
>
<i class="icon icon-menu"></i>
</div>

無障礙表單

<form action="">
Your name
<input type="text" />

<p>Favorite animal</p>
<div>
<input type="radio" name="animals" />
Dog
</div>
<div>
<input type="radio" name="animals" />
Cat
</div>
<div>
<input type="radio" name="animals" />
Polar bear
</div>

<div class="label-wrap">Hometown</div>
<div class="input-wrap">
<input type="text" />
</div>
</form>
<form action="">
<!-- 所有 input 外層加上 label -->
<label>
Your name
<input type="text" />
</label>

<!-- 使用fieldset和legend -->
<fieldset>
<legend>Favorite animal</legend>
<div>
<label>
<input type="radio" name="animals" />
Dog
</label>
</div>
<div>
<label>
<input type="radio" name="animals" />
Cat
</label>
</div>
<div>
<label>
<input type="radio" name="animals" />
Polar bear
</label>
</div>
</fieldset>
<div class="label-wrap">
<!-- 加上label for -->
<label for="hometown"> Hometown </label>
</div>
<!-- 加上 id -->
<div class="input-wrap">
<input type="text" id="hometown" />
</div>
</form>

使用 aria-label

有時候 HTML 元素沒有很清楚的描述,例如:todo list 的 Add 按鈕,實際上功能是「新增 todo」,可以透過 aria-label

<button class="btn btn-outline" aria-label="add todo">+</button>

又或者是頁面中有許多的 Add 按鈕,點擊下去可以添加電影至願望清單中,這時候可以透過 aria-label 加上更多的文字描述

const MovieToolbarButton = ({ movieTitle, buttonText, clickHandler }) => {
return (
<button
className="btn btn-outline-light"
// 加上新增的細節描述
aria-label={`Add ${buttonText}`}
onClick={clickHandler}
>
{buttonText}
</button>
);
};

使用 aria-labelledby

當希望將多個標籤組合在一起又或者是為區塊(widget)新增標籤,可以使用 aria-labelledby

<p>No Movies in your Wish List! <Link to="/browse">Add some</Link>!</p>

上述 pLink 標籤是相關聯的,這時候可以調整成:

<div aria-labelledby="no-movies add-link">
<span id="no-movies">No Movies in your Wish List!</span><Link id="add-link" to="/browse">Add some!</Link>
</div>

使用 aria-describedby

類似於 aria-labelledby ,可用於提供重要的使用細節或指示。例如:表單中 input 的提示訊息和錯誤訊息,這時候移動到 input 時,錯誤訊息和提示訊息是不會被閱讀的。

const FormInput = ({
id,
type,
name,
label,
errorText,
helperText,
onChange,
isValid,
}) => {
const inputClasses = `form-control ${!isValid ? "is-invalid" : ""}`;

return (
<div className="form-group">
<label htmlFor={id}>{label}</label>
<input
id={id}
type={type}
name={name}
className={inputClasses}
onChange={onChange}
/>
{helperText && (
<small className="form-text text-muted helper-text">{helperText}</small>
)}
{errorText && <div className="invalid-feedback">{errorText}</div>}
</div>
);
};

這時候可以使用 aria-describedby,如此 input focus 時錯誤訊息和提示訊息會被閱讀出來。

const FormInput = ({
id,
type,
name,
label,
errorText,
helperText,
onChange,
isValid,
}) => {
const inputClasses = `form-control ${!isValid ? "is-invalid" : ""}`;
// 加上 helperId 和 errorId
const helperId = helperText ? `${id}-helper` : "";
const errorId = errorText && !isValid ? `${id}-error` : "";

return (
<div className="form-group">
<label htmlFor={id}>{label}</label>
<input
id={id}
type={type}
name={name}
className={inputClasses}
onChange={onChange}
aria-describedby={`${helperId} ${errorId}`}
/>
{helperText && (
<small id={helperId} className="form-text text-muted helper-text">
{helperText}
</small>
)}
{errorText && (
<div id={errorId} className="invalid-feedback">
{errorText}
</div>
)}
</div>
);
};

aria-live, aria-atomicaria-relevant

當頁面的內容發生動態變化時,對於視力正常的使用者來說是很明顯的,但對於視障者來說可能並不明顯,這時候可以透過以下屬性來調整

  • aria-live: 定義螢幕閱讀器讀取區域更新的優先順序,值包括:
    • off: 預設設定,不會讀取該區域的更新
    • polite: 一旦空閑,螢幕閱讀器將等待讀取該區域的更新
    • assertive: 螢幕閱讀器會中斷目前正在閱讀的內容,以讀取該區域的更新
  • aria-atomic: 螢幕閱讀器是否應始終將活動區為呈現為一個整體,即使只有部分區域發生變化
  • aria-relevant: 列出哪種變更類型會觸發(預設為 additions text)
    • additions:區域內插入任何節點
    • removals: 區域內刪除任何節點
    • text: 區域內文字的變更
    • all: 等於 additions removals text

頁面渲染後的 focus 設定

一開始進到頁面時,聚焦在該頁面的 h1 title

const Header = ({
title,
buttonText,
buttonLabel,
handleButtonClick,
doFocus,
}) => {
// 設定 Ref
const headerRef = useRef(null);

// 頁面渲染後,執行 focus ref
useEffect(() => {
if (doFocus && headerRef && headerRef.current) {
headerRef.current.focus();
}
}, [doFocus]);

return (
<header>
<nav className="navbar navbar-dark">
<span className="navbar-brand">
//加上ref和eslint disabled
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
<h1 ref={headerRef} tabIndex={0}>
{title}
</h1>
</span>
<button
className="btn btn btn-outline-light"
onClick={handleButtonClick}
aria-label={buttonLabel}
>
{buttonText}
</button>
</nav>
</header>
);
};

參考資料