Komponen Formulir yang Aksesibel
Beri label pada setiap input, hubungkan error dengan aria-describedby, dan sediakan penanda kolom wajib serta pesan error yang jelas dan deskriptif.
Komponen Formulir yang Aksesibel adalah pelajaran Tailwind CSS Academy gratis di CoddyKit. Ini adalah pelajaran 4 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar Tailwind CSS Academy, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Tailwind CSS Academy mencakup 4 pelajaran total.
Mengapa Formulir yang Aksesibel Itu Penting
Formulir adalah salah satu titik interaksi paling penting dalam aplikasi web — digunakan untuk login, pembayaran, pencarian, dan entri data. Formulir yang tidak aksesibel mengecualikan pengguna yang mengandalkan pembaca layar, navigasi keyboard, atau kontrol suara. Kegagalan yang umum meliputi kolom input tanpa label, kesalahan yang hanya muncul secara visual, penanda kolom wajib yang tidak dapat dideteksi pembaca layar, serta fokus yang tidak berpindah ke kesalahan setelah pengiriman. Tailwind menyediakan semua utilitas yang diperlukan untuk membangun formulir yang dapat digunakan semua orang.
Memberi Label pada Setiap Kolom Input
Setiap kolom input formulir harus memiliki label programatis — bukan hanya teks placeholder visual. Placeholder menghilang saat pengguna mengetik dan tidak selalu dibacakan dengan andal oleh semua pembaca layar. Gunakan elemen <label> dengan atribut for yang cocok dengan id kolom input. Ini membuat keterkaitan: mengeklik label akan memindahkan fokus ke kolom input, dan pembaca layar akan mengumumkan label saat kolom input mendapat fokus. Jangan pernah menghapus label yang terlihat hanya demi alasan desain — label adalah persyaratan aksesibilitas.
<!-- GOOD: visible label with for/id binding -->
<div class='flex flex-col gap-1'>
<label for='email' class='text-sm font-medium text-gray-700'>
Email address
</label>
<input
id='email'
type='email'
name='email'
placeholder='you@example.com'
class='rounded-lg border border-gray-300 px-3 py-2 text-sm
focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500'
/>
</div>
<!-- BAD: placeholder-only labeling -->
<input type='email' placeholder='Email address'
class='rounded-lg border border-gray-300 px-3 py-2' />
{/* Placeholder disappears on input — user forgets what the field is for */}Penanda Kolom Wajib
Kolom wajib perlu ditandai secara visual dan programatis. Tambahkan atribut HTML required (pembaca layar akan mengumumkannya) dan penanda visual — biasanya berupa tanda bintang — disertai keterangan yang menjelaskan arti tanda bintang tersebut. Gunakan aria-required='true' untuk elemen formulir khusus yang tidak mendukung atribut bawaan required. Jangan pernah hanya mengandalkan warna untuk menunjukkan status wajib.
<form>
{/* Explain the asterisk at the top of the form */}
<p class='text-sm text-gray-500 mb-6'>
Fields marked with
<span class='text-red-600 font-bold' aria-hidden='true'> *</span>
<span class='sr-only'>an asterisk</span>
are required.
</p>
<div class='flex flex-col gap-1'>
<label for='name' class='text-sm font-medium text-gray-700'>
Full name
<span class='text-red-600 ml-0.5' aria-hidden='true'>*</span>
</label>
<input
id='name'
type='text'
required
aria-required='true'
class='rounded-lg border border-gray-300 px-3 py-2'
/>
</div>
</form>Pesan Kesalahan dengan aria-describedby
Ketika sebuah kolom memiliki kesalahan validasi, pesan kesalahan harus dikaitkan secara programatis dengan kolom input. Gunakan aria-describedby pada kolom input untuk merujuk ke ID elemen pesan kesalahan. Pembaca layar akan mengumumkan pesan kesalahan setelah mengumumkan label kolom. Atur juga aria-invalid='true' pada kolom input saat terdapat kesalahan — ini memberi sinyal kepada teknologi asistif bahwa nilai kolom tersebut tidak valid.
function FormField({ id, label, error, ...inputProps }) {
const errorId = id + '-error';
return (
<div class='flex flex-col gap-1'>
<label for={id} class='text-sm font-medium text-gray-700'>
{label}
</label>
<input
id={id}
aria-invalid={error ? 'true' : undefined}
aria-describedby={error ? errorId : undefined}
class={cn(
'rounded-lg border px-3 py-2 text-sm',
'focus:outline-none focus-visible:ring-2',
error
? 'border-red-400 focus-visible:ring-red-500'
: 'border-gray-300 focus-visible:ring-blue-500'
)}
{...inputProps}
/>
{error && (
<p id={errorId} class='text-sm text-red-600 flex items-center gap-1'>
<ExclamationCircleIcon class='h-4 w-4 flex-shrink-0' aria-hidden='true' />
{error}
</p>
)}
</div>
);
}Pengelolaan Fokus Setelah Pengiriman Formulir
Saat formulir dikirim dan ditemukan kesalahan validasi, pindahkan fokus ke kesalahan pertama atau ke ringkasan kesalahan di bagian atas formulir. Jika tidak, pengguna yang menavigasi formulir dengan tombol Tab tidak akan mendapat petunjuk bahwa pengiriman gagal — pesan kesalahan mungkin muncul di bawah area yang terlihat atau di area yang sudah mereka lewati. Memindahkan fokus ke ringkasan kesalahan adalah pola yang paling andal karena berfungsi terlepas dari lokasi kemunculan kesalahan.
function ContactForm() {
const [errors, setErrors] = useState({});
const errorSummaryRef = useRef(null);
const handleSubmit = async (e) => {
e.preventDefault();
const validation = validateForm(formData);
if (Object.keys(validation).length > 0) {
setErrors(validation);
// Move focus to error summary after state update
setTimeout(() => errorSummaryRef.current?.focus(), 0);
return;
}
// ... submit
};
return (
<form onSubmit={handleSubmit}>
{Object.keys(errors).length > 0 && (
<div
ref={errorSummaryRef}
tabIndex={-1}
role='alert'
class='bg-red-50 border border-red-200 rounded-lg p-4 mb-6 focus:outline-none'
>
<h2 class='text-sm font-semibold text-red-800 mb-2'>
Please fix the following {Object.keys(errors).length} error(s):
</h2>
<ul class='list-disc list-inside text-sm text-red-700'>
{Object.entries(errors).map(([field, msg]) => (
<li key={field}><a href={'#' + field} class='underline'>{msg}</a></li>
))}
</ul>
</div>
)}
{/* form fields */}
</form>
);
}Grup Kotak Centang dan Tombol Radio yang Aksesibel
Kotak centang dan tombol radio sebaiknya dikelompokkan dalam <fieldset> dengan <legend> yang menjelaskan grup tersebut. Legend akan diumumkan oleh pembaca layar saat anggota grup menerima fokus, sehingga memberikan konteks penting. Setiap kotak centang atau tombol radio tetap memerlukan <label> sendiri. Tanpa pengelompokan fieldset/legend, pengguna pembaca layar akan mendengar label individual tetapi kehilangan konteks grupnya.
<fieldset class='border-0 p-0 m-0'>
<legend class='text-sm font-semibold text-gray-800 mb-3'>
Notification preferences
</legend>
<div class='flex flex-col gap-3'>
{['Email', 'SMS', 'Push'].map(option => (
<label key={option} class='flex items-center gap-3 cursor-pointer'>
<input
type='checkbox'
name='notifications'
value={option.toLowerCase()}
class='
h-4 w-4 rounded border-gray-300 text-blue-600
focus-visible:ring-2 focus-visible:ring-blue-500
'
/>
<span class='text-sm text-gray-700'>{option} notifications</span>
</label>
))}
</div>
</fieldset>Select dan Combobox yang Aksesibel
Elemen <select> bawaan sudah aksesibel sejak awal, tetapi terbatas dalam hal penataan gaya. Saat Anda memerlukan select dengan gaya khusus yang memuat gambar atau tata letak opsi yang kompleks, gunakan komponen Listbox atau Combobox milik Headless UI yang menerapkan pola listbox ARIA. Selalu sertakan <label> yang terlihat dan dikaitkan dengan select. Jangan pernah hanya menggunakan aria-label — label yang terlihat membantu semua pengguna, bukan hanya pengguna pembaca layar.
<!-- Native select: accessible, limited styling -->
<div class='flex flex-col gap-1'>
<label for='country' class='text-sm font-medium text-gray-700'>
Country
</label>
<select
id='country'
name='country'
class='
rounded-lg border border-gray-300 px-3 py-2 text-sm bg-white
focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500
'
>
<option value=''>Select a country</option>
<option value='us'>United States</option>
<option value='uk'>United Kingdom</option>
<option value='ca'>Canada</option>
</select>
</div>Kolom Kata Sandi dan Sakelar Visibilitas
Kolom kata sandi dengan sakelar tampil/sembunyi memerlukan penerapan aksesibilitas yang cermat. Tombol sakelar harus memiliki aria-label yang menjelaskan tindakan saat ini dengan jelas ('Tampilkan kata sandi' atau 'Sembunyikan kata sandi'). Saat sakelar diubah, gunakan aria-live untuk mengumumkan perubahan status kepada pengguna pembaca layar. Jenis kolom input kata sandi harus berubah antara password dan text — jangan gunakan pendekatan penyamaran khusus.
function PasswordInput({ id, label }) {
const [visible, setVisible] = useState(false);
const announcement = visible ? 'Password is now visible' : 'Password is now hidden';
const [liveText, setLiveText] = useState('');
const toggle = () => {
setVisible(!visible);
setLiveText(announcement);
};
return (
<div class='flex flex-col gap-1'>
<label for={id} class='text-sm font-medium text-gray-700'>{label}</label>
<div class='relative'>
<input
id={id}
type={visible ? 'text' : 'password'}
class='w-full rounded-lg border border-gray-300 px-3 py-2 pr-10 text-sm
focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500'
/>
<button
type='button'
onClick={toggle}
aria-label={visible ? 'Hide password' : 'Show password'}
class='absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-600'
>
{visible ? <EyeSlashIcon class='h-4 w-4' /> : <EyeIcon class='h-4 w-4' />}
</button>
</div>
<span class='sr-only' aria-live='polite'>{liveText}</span>
</div>
);
}Umpan Balik Validasi Formulir Sebaris
Berikan umpan balik validasi secara waktu nyata saat pengguna menyelesaikan kolom (bukan hanya saat pengiriman) agar kesalahan dapat diketahui lebih awal. Gunakan onBlur untuk memicu validasi saat kolom kehilangan fokus — bukan pada setiap penekanan tombol, karena hal itu mengganggu. Umpan balik harus jelas: tanda centang hijau atau teks keberhasilan untuk kolom yang valid, serta pesan kesalahan merah untuk kolom yang tidak valid. Selalu sampaikan kesalahannya dan cara memperbaikinya.
function ValidatedInput({ id, label, validate }) {
const [value, setValue] = useState('');
const [error, setError] = useState('');
const [touched, setTouched] = useState(false);
const isValid = touched && !error && value;
const handleBlur = () => {
setTouched(true);
const err = validate(value);
setError(err || '');
};
return (
<div class='flex flex-col gap-1'>
<label for={id} class='text-sm font-medium text-gray-700'>{label}</label>
<div class='relative'>
<input
id={id}
value={value}
onChange={e => setValue(e.target.value)}
onBlur={handleBlur}
aria-invalid={touched && error ? 'true' : undefined}
aria-describedby={error ? id + '-error' : undefined}
class={cn('w-full rounded-lg border px-3 py-2 pr-8 text-sm',
error && touched ? 'border-red-400' : isValid ? 'border-green-500' : 'border-gray-300'
)}
/>
{isValid && <CheckCircleIcon class='absolute right-2 top-1/2 -translate-y-1/2 h-4 w-4 text-green-500' />}
</div>
{error && touched && (
<p id={id + '-error'} class='text-sm text-red-600'>{error}</p>
)}
</div>
);
}Contoh Formulir Aksesibel Lengkap
Formulir yang sepenuhnya aksesibel menggabungkan: label yang dikaitkan, penanda kolom wajib dengan aria-required, pesan kesalahan dengan aria-describedby, ringkasan kesalahan dengan pengelolaan fokus, jenis kolom input yang sesuai, serta fieldset/legend untuk grup. Formulir harus dapat dioperasikan sepenuhnya melalui keyboard, dan semua informasi harus tersedia bagi pembaca layar. Tailwind menangani lapisan visual; semantik HTML dan ARIA menangani lapisan aksesibilitas.
<form onSubmit={handleSubmit} noValidate>
{/* Error summary */}
{hasErrors && (
<div ref={summaryRef} tabIndex={-1} role='alert'
class='bg-red-50 border border-red-200 rounded-lg p-4 mb-6'>
<h2 class='text-sm font-semibold text-red-800'>Fix these errors:</h2>
{/* error list with anchor links to fields */}
</div>
)}
{/* Name field */}
<div class='flex flex-col gap-1 mb-4'>
<label for='name' class='text-sm font-medium'>
Name <span aria-hidden='true' class='text-red-600'>*</span>
</label>
<input id='name' required aria-required='true'
aria-invalid={errors.name ? 'true' : undefined}
aria-describedby={errors.name ? 'name-error' : undefined}
class='rounded-lg border border-gray-300 px-3 py-2 focus-visible:ring-2 focus-visible:ring-blue-500' />
{errors.name && <p id='name-error' class='text-sm text-red-600'>{errors.name}</p>}
</div>
<button type='submit' class='w-full bg-blue-600 text-white rounded-lg py-2 font-medium hover:bg-blue-700'>Submit</button>
</form>Menguji Formulir yang Aksesibel
Uji formulir yang aksesibel dengan tiga pendekatan. Otomatis: jalankan axe-core pada formulir yang dirender. Keyboard: navigasikan setiap kolom dengan tombol Tab, kirim dengan kesalahan, pastikan fokus berpindah ke ringkasan kesalahan, perbaiki kesalahan, lalu kirim ulang. Pembaca layar: lakukan pengujian dengan VoiceOver (macOS/iOS) dan NVDA (Windows), sambil mendengarkan pengumuman label, pengumuman kesalahan, dan perubahan status. Setiap metode pengujian menemukan jenis masalah yang berbeda.
// Automated: test with jest-axe
import { render } from '@testing-library/react';
import { axe } from 'jest-axe';
import ContactForm from './ContactForm';
test('Contact form has no accessibility violations', async () => {
const { container } = render(<ContactForm />);
expect(await axe(container)).toHaveNoViolations();
});
test('Error messages are associated with inputs', async () => {
const { container, getByRole } = render(<ContactForm />);
// Submit empty form to trigger errors
fireEvent.click(getByRole('button', { name: /submit/i }));
// axe checks aria-describedby associations
expect(await axe(container)).toHaveNoViolations();
});Pemeriksaan Singkat
Uji pemahaman Anda tentang konsep Tailwind CSS Mastery dari pelajaran ini.
Ringkasan Pelajaran
Dalam pelajaran ini Anda telah mempelajari: setiap kolom input memerlukan label yang terlihat dengan keterkaitan for/id, aria-invalid dan aria-describedby menghubungkan pesan kesalahan dengan kolom inputnya secara programatis, dan pengelolaan fokus mengarahkan pengguna ke ringkasan kesalahan setelah pengiriman gagal. Selamat telah menyelesaikan modul Komponen Aksesibel — kini Anda memiliki perangkat lengkap untuk membangun antarmuka Tailwind yang inklusif.
Belajar HTML dengan tutor AI — gratis
Tulis dan jalankan kode asli di browser kamu, dapatkan bantuan instan dari tutor AI 24/7, dan lanjutkan di mana kamu tinggalkan di web atau aplikasi.
- Kursus
- 30
- Pelajaran
- 120
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Komponen Formulir yang Aksesibel” gratis?
Ya — teks lengkap “Komponen Formulir yang Aksesibel” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus Tailwind CSS Academy, upgrade ke CoddyKit PRO. Kursus Tailwind CSS Academy mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Komponen Formulir yang Aksesibel”?
Beri label pada setiap input, hubungkan error dengan aria-describedby, dan sediakan penanda kolom wajib serta pesan error yang jelas dan deskriptif. Kamu berlatih Tailwind CSS Academy dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai Tailwind CSS Academy?
Tidak diperlukan pengalaman sebelumnya. Tailwind CSS Academy di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 4 dari 4.
Berapa lama pelajaran “Komponen Formulir yang Aksesibel” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran Tailwind CSS Academy ini?
Ya. Setiap pelajaran Tailwind CSS Academy menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Kontras Warna dan Teks yang Mudah Dibaca
- Indikator Fokus dan Navigasi Papan Ketik
- Atribut ARIA dan Pembaca Layar
- Komponen Formulir yang Aksesibel