建立可恢復的無障礙退款表單
專案情境
使用者要申請退款,表單包含原因、金額、附件和備註。送出失敗時不能清空內容,也不能只顯示一句「發生錯誤」。
1. 從 HTML 語意開始
<label for="amount">退款金額</label>
<input
id="amount"
name="amount"
inputmode="decimal"
aria-describedby="amount-help amount-error"
aria-invalid="true"
/>
<p id="amount-help">最高可申請 NT$10,000。</p>
<p id="amount-error" role="alert">請輸入不超過可退款金額的數字。</p>
label 建立欄位名稱,aria-describedby 串起說明與錯誤,aria-invalid 表示目前狀態。錯誤文字仍然要在畫面上可見。
2. 選擇驗證時機
使用者離開欄位時可以檢查格式;送出時檢查整份表單;輸入過程不要在每一個字元都顯示錯誤,否則使用者還沒完成就被打斷。驗證訊息要回答三件事:哪裡錯、為什麼錯、怎麼修正。
function validateAmount(value: string, maximum: number) {
const amount = Number(value);
if (!value) return '請輸入退款金額。';
if (!Number.isFinite(amount) || amount <= 0) return '請輸入大於 0 的數字。';
if (amount > maximum) return `退款金額不能超過 NT$${maximum.toLocaleString()}。`;
return null;
}
3. 處理送出狀態
async function submit(form) {
setState({ status: 'submitting' });
try {
await createRefund(form);
setState({ status: 'success', message: '退款申請已送出。' });
} catch {
setState({
status: 'error',
message: '送出失敗,資料已保留,請稍後重試。',
});
}
}
送出中停用按鈕並提供進度;失敗後恢復按鈕、保留輸入並把焦點移到錯誤摘要或第一個錯誤欄位。
4. 用鍵盤驗證
依序使用 Tab 進入所有欄位、Space 選擇選項、Enter 送出、Shift+Tab 返回。確認焦點順序符合閱讀順序,錯誤訊息出現時沒有把焦點送到不相關的位置。
驗收
- 每個欄位有 visible label、說明和必要時的錯誤訊息。
- 錯誤不只用紅色表達,讀屏器也能讀到。
- 送出失敗時資料和焦點都可恢復。
- 手機軟鍵盤不會遮住目前欄位或送出按鈕。
延伸練習
加入附件上傳的檔案格式、大小錯誤和上傳中狀態,並設計取消上傳與重新選檔的操作。