Form adalah komponen penting di hampir setiap website, mulai dari form login, registrasi, sampai form kontak. Sayangnya, tampilan form HTML bawaan browser seringkali terlihat membosankan dan tidak konsisten antar browser: tinggi input berbeda, panah select beda bentuk, dan efek fokusnya kadang nyaris tak terlihat. Dengan CSS, kamu bisa mengubah form menjadi terlihat profesional dan nyaman digunakan tanpa library tambahan.
Artikel ini membahas cara membuat form yang menarik dari awal: reset style, struktur HTML yang semantik, styling input dan tombol, ikon di dalam input, custom select, checkbox dan radio, floating label, pesan validasi (dengan CSS murni dan sedikit JavaScript), layout responsif dua kolom, dark mode, sampai kesalahan umum yang sering membuat form terasa "murahan". Semua contoh kode bisa langsung kamu salin dan jalankan di satu file HTML. Kalau kamu masih baru dengan selector, box model, dan properti dasar, baca dulu CSS dasar untuk membuat tampilan website menarik supaya bagian di bawah lebih mudah diikuti.
Prinsip Form yang Menarik (Bukan Sekadar Cantik)
Sebelum menulis CSS, pahami dulu apa yang membuat form terasa "enak". Form yang bagus bukan yang paling banyak animasinya, tapi yang membuat pengguna cepat paham harus mengisi apa, tahu field mana yang sedang aktif, dan langsung mengerti kalau ada isian yang salah. Ada empat hal yang perlu kamu jaga:
- Hierarki visual yang jelas. Label tebal dan dekat dengan input, jarak antar grup konsisten, tombol utama paling menonjol.
- Keadaan (state) yang terlihat. Normal, hover, fokus, error, sukses, dan disabled harus bisa dibedakan, dan jangan hanya mengandalkan warna.
- Ukuran yang ramah sentuhan. Input dan tombol cukup tinggi untuk jari, terutama di HP.
- Aksesibel. Setiap input punya label, fokus keyboard terlihat, dan pesan error bisa dibaca screen reader.
Semua teknik di bawah sebenarnya hanya cara menerapkan empat prinsip ini.
Reset Style Bawaan Browser
Langkah pertama adalah me-reset tampilan bawaan elemen form agar konsisten di semua browser. Secara default, elemen form tidak mewarisi font dari body, jadi teks di dalam input sering terlihat beda sendiri. Dua baris font-family: inherit dan font-size: inherit di bawah memperbaiki masalah itu, sedangkan box-sizing: border-box membuat width: 100% tidak meluber karena padding dan border.
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
input,
textarea,
select,
button {
font-family: inherit;
font-size: inherit;
}
Struktur HTML Form
Gunakan struktur yang semantik dan aksesibel. Setiap <label> terhubung ke input lewat atribut for yang sama dengan id input, sehingga mengklik label akan memfokuskan input dan screen reader tahu nama field tersebut. Atribut novalidate pada form mematikan bubble validasi bawaan browser, berguna kalau kamu ingin menampilkan pesan error sendiri (dibahas di bagian validasi). Kalau kamu ingin form ini benar-benar mengirim data ke server, tambahkan action dan method; perbedaan keduanya dijelaskan di artikel perbedaan GET dan POST di PHP.
<form class="form-keren" novalidate>
<h2>Hubungi Kami</h2>
<div class="form-grup">
<label for="nama">Nama Lengkap</label>
<input type="text" id="nama" name="nama" placeholder="Masukkan nama kamu">
</div>
<div class="form-grup">
<label for="email">Alamat Email</label>
<input type="email" id="email" name="email" placeholder="nama@email.com">
</div>
<div class="form-grup">
<label for="topik">Topik</label>
<select id="topik" name="topik">
<option value="">Pilih topik...</option>
<option value="umum">Pertanyaan Umum</option>
<option value="teknis">Masalah Teknis</option>
<option value="kerjasama">Kerjasama</option>
</select>
</div>
<div class="form-grup">
<label for="pesan">Pesan</label>
<textarea id="pesan" name="pesan" rows="5"
placeholder="Tulis pesan kamu di sini..."></textarea>
</div>
<button type="submit" class="tombol-kirim">Kirim Pesan</button>
</form>
Pilih juga type input yang tepat: email, tel, number, url, atau password. Selain mengaktifkan validasi bawaan, tipe yang tepat membuat keyboard HP menyesuaikan diri, misalnya muncul tombol @ untuk email atau keypad angka untuk tel. Tambahkan juga atribut autocomplete (contoh autocomplete="email" atau autocomplete="name") agar browser bisa mengisi otomatis dengan benar.
CSS Styling Utama Form
Berikut CSS inti yang membuat form terlihat rapi: kartu putih dengan bayangan halus, label tebal di atas input, input dengan latar abu-abu muda yang berubah putih saat fokus, dan transisi singkat supaya perubahan terasa halus. Perhatikan bahwa outline: none hanya aman karena kita menggantinya dengan box-shadow yang jelas saat fokus. Jangan pernah menghapus outline tanpa pengganti.
.form-keren {
max-width: 520px;
margin: 40px auto;
padding: 40px;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
}
.form-keren h2 {
margin-bottom: 28px;
font-size: 1.5rem;
color: #1e293b;
font-weight: 700;
}
.form-grup {
margin-bottom: 20px;
}
.form-grup label {
display: block;
margin-bottom: 6px;
font-weight: 600;
font-size: 0.9rem;
color: #374151;
}
/* Styling input, textarea, dan select */
.form-grup input,
.form-grup textarea,
.form-grup select {
width: 100%;
padding: 10px 14px;
border: 1.5px solid #d1d5db;
border-radius: 8px;
font-size: 1rem;
color: #1e293b;
background: #f9fafb;
transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
outline: none;
}
/* Efek hover */
.form-grup input:hover,
.form-grup textarea:hover,
.form-grup select:hover {
border-color: #9ca3af;
}
/* Efek fokus */
.form-grup input:focus,
.form-grup textarea:focus,
.form-grup select:focus {
border-color: #3b82f6;
background-color: #ffffff;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
/* Placeholder */
.form-grup input::placeholder,
.form-grup textarea::placeholder {
color: #9ca3af;
font-size: 0.9rem;
}
.form-grup textarea {
resize: vertical;
min-height: 120px;
}
/* Field yang dinonaktifkan */
.form-grup input:disabled,
.form-grup textarea:disabled,
.form-grup select:disabled {
background: #f3f4f6;
color: #9ca3af;
cursor: not-allowed;
}
Ukuran font-size: 1rem (16px pada setelan default) di input bukan kebetulan. Safari di iPhone otomatis memperbesar halaman saat pengguna mengetuk input yang ukuran hurufnya di bawah 16px, dan efek zoom ini sering membuat layout terasa "loncat". Jadi pertahankan minimal 16px untuk teks di dalam input.
Styling Tombol Submit
Tombol submit adalah aksi utama, jadi buat paling menonjol: warna solid, lebar penuh di form sempit, dan umpan balik saat di-hover, ditekan, atau difokuskan lewat keyboard. Pseudo-class :focus-visible hanya aktif saat fokus datang dari keyboard (Tab), sehingga ring fokus tidak muncul mengganggu ketika tombol diklik dengan mouse.
.tombol-kirim {
width: 100%;
padding: 12px;
background: #3b82f6;
color: white;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background 0.2s, transform 0.1s;
margin-top: 8px;
}
.tombol-kirim:hover {
background: #2563eb;
}
.tombol-kirim:active {
transform: scale(0.98);
}
.tombol-kirim:focus-visible {
outline: 3px solid #93c5fd;
outline-offset: 2px;
}
.tombol-kirim:disabled {
background: #93c5fd;
cursor: not-allowed;
}
Menambahkan Ikon pada Input
Ikon di dalam input membuat form terlihat lebih profesional. Gunakan teknik positioning: pembungkus diberi position: relative, ikon diberi position: absolute dan diposisikan tepat di tengah secara vertikal, lalu input diberi padding-left lebih besar agar teks tidak menimpa ikon. Pada versi ini ikon memakai SVG inline, bukan karakter emoji, supaya tampilannya sama di semua sistem operasi dan warnanya bisa diatur lewat currentColor. Properti pointer-events: none membuat klik pada ikon tetap "tembus" ke input.
<div class="form-grup input-dengan-ikon">
<label for="cari">Pencarian</label>
<div class="input-wrapper">
<svg class="ikon-input" width="18" height="18" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="11" cy="11" r="7"></circle>
<line x1="16.5" y1="16.5" x2="21" y2="21"></line>
</svg>
<input type="search" id="cari" placeholder="Cari sesuatu...">
</div>
</div>
<style>
.input-wrapper {
position: relative;
}
.ikon-input {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #9ca3af;
pointer-events: none;
}
.input-dengan-ikon input {
padding-left: 40px;
}
</style>
Custom Select dengan Panah Sendiri
Elemen select adalah yang paling "keras kepala" karena panahnya digambar oleh sistem operasi. Cara paling aman dan didukung semua browser modern adalah menghapus tampilan bawaan dengan appearance: none, lalu menggambar panah sendiri sebagai background-image berupa SVG. Daftar opsi yang muncul saat dropdown dibuka tetap memakai tampilan bawaan, dan itu normal.
.form-grup select {
appearance: none;
padding-right: 40px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%236b7280' stroke-width='2'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 14px center;
cursor: pointer;
}
Karena latar input berubah saat fokus (dari #f9fafb ke #ffffff), pastikan aturan fokus memakai background-color, bukan shorthand background. Shorthand akan me-reset background-image sehingga panah select hilang ketika diklik. Ini salah satu bug kecil yang paling sering terjadi, dan itulah alasan aturan :focus di bagian CSS utama sudah memakai background-color: #ffffff;.
Checkbox dan Radio yang Menarik
Untuk checkbox dan radio, cara paling ringan adalah properti accent-color. Satu baris ini mengubah warna centang dan titik radio sesuai warna brand tanpa merusak aksesibilitas, karena elemen aslinya tetap dipakai.
<div class="form-grup">
<label class="checkbox-label">
<input type="checkbox" name="setuju">
<span>Saya setuju dengan syarat dan ketentuan</span>
</label>
</div>
<style>
.checkbox-label {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
font-size: 0.9rem;
}
.checkbox-label input[type="checkbox"] {
width: 18px;
height: 18px;
accent-color: #3b82f6;
cursor: pointer;
}
</style>
Untuk pilihan radio, bungkus dengan <fieldset> dan <legend>. Keduanya memberi tahu screen reader bahwa beberapa radio itu satu kelompok pertanyaan. Contoh berikut sekaligus membuat radio tampil sebagai "kartu pilihan" yang berubah warna saat dipilih, memakai selector :has() yang kini didukung browser modern.
<fieldset class="pilihan-paket">
<legend>Pilih paket</legend>
<label class="kartu-radio">
<input type="radio" name="paket" value="basic" checked>
<span>Basic</span>
</label>
<label class="kartu-radio">
<input type="radio" name="paket" value="pro">
<span>Pro</span>
</label>
</fieldset>
<style>
.pilihan-paket {
border: none;
display: flex;
gap: 12px;
margin-bottom: 20px;
}
.pilihan-paket legend {
font-weight: 600;
font-size: 0.9rem;
margin-bottom: 8px;
color: #374151;
}
.kartu-radio {
flex: 1;
display: flex;
align-items: center;
gap: 8px;
padding: 12px 14px;
border: 1.5px solid #d1d5db;
border-radius: 8px;
cursor: pointer;
}
.kartu-radio input {
accent-color: #3b82f6;
}
.kartu-radio:has(input:checked) {
border-color: #3b82f6;
background: #eff6ff;
}
</style>
Floating Label dengan CSS Murni
Floating label adalah gaya di mana label tampil di dalam input seperti placeholder, lalu "naik" ke atas saat input difokuskan atau sudah terisi. Triknya memakai pseudo-class :placeholder-shown: selama placeholder masih terlihat, berarti input kosong. Karena itu input wajib punya atribut placeholder, walaupun isinya hanya satu spasi. Urutan HTML juga penting: input harus ditulis sebelum label agar selector saudara + bisa bekerja.
<div class="floating">
<input type="email" id="email2" name="email" placeholder=" " autocomplete="email">
<label for="email2">Alamat Email</label>
</div>
<style>
.floating {
position: relative;
margin-bottom: 20px;
}
.floating input {
width: 100%;
padding: 20px 14px 8px;
border: 1.5px solid #d1d5db;
border-radius: 8px;
font-size: 1rem;
background: #f9fafb;
outline: none;
}
.floating label {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
color: #6b7280;
pointer-events: none;
transition: top 0.15s, font-size 0.15s, color 0.15s;
}
.floating input:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
/* Label naik saat fokus ATAU saat input sudah terisi */
.floating input:focus + label,
.floating input:not(:placeholder-shown) + label {
top: 10px;
font-size: 0.75rem;
color: #3b82f6;
}
</style>
Floating label menghemat ruang vertikal, tapi jangan dipakai untuk form yang sangat panjang. Untuk form pendaftaran dengan banyak field, label statis di atas input biasanya lebih mudah dipindai mata.
Menampilkan Pesan Validasi
Tambahkan pesan validasi yang informatif dan estetis. Pola di bawah memakai class .error dan .sukses pada .form-grup; class ini nanti dipasang oleh JavaScript. Elemen .pesan-error disembunyikan secara default dan baru tampil saat grup berstatus error.
.form-grup .pesan-error {
display: none;
color: #ef4444;
font-size: 0.82rem;
margin-top: 4px;
}
.form-grup.error input,
.form-grup.error textarea {
border-color: #ef4444;
background: #fff5f5;
}
.form-grup.error .pesan-error {
display: block;
}
.form-grup.sukses input {
border-color: #10b981;
background: #f0fdf4;
}
Di HTML, tambahkan elemen pesan di bawah setiap input dan hubungkan dengan aria-describedby supaya screen reader ikut membacakan pesan error:
Berlangganan gratis. Artikel & tutorial coding terbaru langsung ke email kamu. Tanpa spam.
<div class="form-grup">
<label for="email">Alamat Email</label>
<input type="email" id="email" name="email" required
aria-describedby="email-error">
<p class="pesan-error" id="email-error">Masukkan alamat email yang valid.</p>
</div>
Validasi dengan CSS murni: :user-invalid
Kalau kamu tidak ingin memakai JavaScript, CSS punya pseudo-class :user-invalid dan :user-valid. Bedanya dengan :invalid biasa: :invalid langsung aktif begitu halaman dimuat, sehingga field required yang masih kosong sudah berwarna merah sebelum disentuh. Itu pengalaman yang buruk. Sementara :user-invalid baru aktif setelah pengguna berinteraksi dengan field tersebut. Pseudo-class ini sudah didukung versi terbaru Chrome, Edge, Firefox, dan Safari; di browser lama yang belum mendukung, aturannya sekadar diabaikan.
.form-grup input:user-invalid {
border-color: #ef4444;
background: #fff5f5;
}
.form-grup input:user-valid {
border-color: #10b981;
}
Validasi dengan JavaScript (Constraint Validation API)
Untuk pesan error yang bisa kamu atur sendiri, gabungkan atribut HTML5 (required, type="email", minlength) dengan Constraint Validation API bawaan browser. Kamu tidak perlu menulis regex email sendiri; cukup baca properti validity pada input. Script ini memasang class .error atau .sukses yang sudah kita style di atas. Jika kamu masih asing dengan event dan DOM, pelajari dulu JavaScript dasar untuk pemula.
<script>
const form = document.querySelector('.form-keren');
function cekField(input) {
const grup = input.closest('.form-grup');
if (!grup) return true;
const valid = input.checkValidity();
grup.classList.toggle('error', !valid);
grup.classList.toggle('sukses', valid);
input.setAttribute('aria-invalid', String(!valid));
return valid;
}
// Cek saat pengguna meninggalkan field
form.querySelectorAll('input, textarea, select').forEach((el) => {
el.addEventListener('blur', () => cekField(el));
});
form.addEventListener('submit', (e) => {
const fields = [...form.querySelectorAll('input, textarea, select')];
const semuaValid = fields.map(cekField).every(Boolean);
if (!semuaValid) {
e.preventDefault();
form.querySelector('.form-grup.error input, .form-grup.error textarea, .form-grup.error select')?.focus();
}
});
</script>
Catatan penting: validasi di browser hanya untuk kenyamanan pengguna. Siapa pun bisa melewatinya lewat DevTools atau mengirim request langsung, jadi data tetap wajib divalidasi ulang di server.
Layout Form Responsif Dua Kolom
Form yang panjang terasa lebih ringkas jika field pendek (nama depan dan belakang, kota dan kode pos) disusun berdampingan. Gunakan CSS Grid dengan media query: satu kolom di HP, dua kolom di layar yang lebih lebar.
<div class="baris-form">
<div class="form-grup">
<label for="depan">Nama Depan</label>
<input type="text" id="depan" name="depan" autocomplete="given-name">
</div>
<div class="form-grup">
<label for="belakang">Nama Belakang</label>
<input type="text" id="belakang" name="belakang" autocomplete="family-name">
</div>
</div>
<style>
.baris-form {
display: grid;
grid-template-columns: 1fr;
gap: 0 16px;
}
@media (min-width: 600px) {
.baris-form {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 480px) {
.form-keren {
padding: 24px 16px;
margin: 16px;
}
}
</style>
Dark Mode dan Variabel CSS
Daripada menulis warna berulang-ulang, simpan warna di CSS custom properties. Setelah itu, mendukung dark mode cukup dengan mengganti nilai variabel di dalam media query prefers-color-scheme. Properti color-scheme memberi tahu browser agar elemen bawaan seperti scrollbar dan daftar opsi select ikut menyesuaikan tema.
:root {
color-scheme: light dark;
--form-bg: #ffffff;
--input-bg: #f9fafb;
--input-border: #d1d5db;
--teks: #1e293b;
--aksen: #3b82f6;
}
@media (prefers-color-scheme: dark) {
:root {
--form-bg: #1e293b;
--input-bg: #0f172a;
--input-border: #334155;
--teks: #e2e8f0;
--aksen: #60a5fa;
}
}
.form-keren { background: var(--form-bg); color: var(--teks); }
.form-grup input,
.form-grup textarea,
.form-grup select {
background-color: var(--input-bg);
border-color: var(--input-border);
color: var(--teks);
}
.form-grup input:focus { border-color: var(--aksen); }
Agar konsisten, ganti juga warna judul dan label pada CSS utama dengan var(--teks). Kalau tidak, teksnya tetap gelap di atas latar gelap.
Kesalahan Umum Saat Styling Form
| Kesalahan | Akibatnya | Solusi |
|---|---|---|
outline: none tanpa pengganti | Pengguna keyboard tidak tahu field mana yang aktif | Ganti dengan box-shadow atau :focus-visible yang jelas |
| Placeholder dipakai sebagai label | Petunjuk hilang saat mulai mengetik | Selalu pakai <label>, placeholder hanya contoh isian |
| Font input di bawah 16px | Safari iOS melakukan zoom otomatis | Gunakan minimal font-size: 1rem |
| Error hanya ditandai warna merah | Sulit dikenali pengguna buta warna | Tambahkan teks pesan error, hubungkan dengan aria-describedby |
background shorthand di :focus | Panah custom select hilang saat fokus | Gunakan background-color |
Memakai :invalid langsung | Field kosong sudah merah sebelum disentuh | Pakai :user-invalid atau class dari JavaScript |
Lupa box-sizing: border-box | Input dengan width: 100% meluber keluar kartu | Pasang reset di awal stylesheet |
Satu kasus yang juga sering muncul: latar input berubah kuning atau biru muda saat browser mengisi otomatis (autofill) di Chrome. Warna itu dipasang browser dan sulit ditimpa dengan background biasa. Trik yang umum dipakai adalah bayangan inset besar yang menutupi latar tersebut:
.form-grup input:-webkit-autofill {
-webkit-box-shadow: 0 0 0 1000px #ffffff inset;
-webkit-text-fill-color: #1e293b;
}
Tips Membuat Form yang Baik
- Selalu gunakan
<label>yang terhubung dengan input melalui atributfordanid. - Berikan efek fokus yang jelas agar pengguna tahu field mana yang sedang aktif.
- Gunakan placeholder sebagai contoh nilai, bukan sebagai pengganti label.
- Pastikan tombol submit memiliki ukuran yang cukup besar dan mudah diklik di layar sentuh.
- Validasi form di sisi klien (HTML5 atau JavaScript) sebelum mengirim ke server, lalu validasi ulang di server.
- Minta data sesedikit mungkin. Setiap field tambahan adalah alasan baru bagi pengguna untuk berhenti mengisi.
- Tandai field opsional (misalnya dengan teks "opsional") daripada menandai semua field wajib dengan bintang.
- Hormati pengguna yang sensitif terhadap gerakan dengan membungkus animasi di dalam
@media (prefers-reduced-motion: no-preference). - Tulis teks tombol yang spesifik, seperti "Kirim Pesan" atau "Buat Akun", bukan sekadar "Submit".
Kesimpulan
Dengan CSS yang tepat, form HTML yang biasa bisa berubah menjadi komponen yang menarik dan nyaman digunakan. Kuncinya adalah memperhatikan detail seperti efek fokus, warna border, tipografi, dan feedback visual untuk validasi. Mulailah dari reset dan struktur HTML yang semantik, lalu tambahkan lapisan demi lapisan: styling input dan tombol, custom select, checkbox dan radio, validasi, layout responsif, dan dark mode. Terapkan teknik-teknik di atas pada form login, registrasi, atau kontak di proyek kamu untuk meningkatkan pengalaman pengguna secara signifikan.
FAQ
Apakah perlu framework seperti Bootstrap atau Tailwind untuk membuat form yang bagus?
Tidak wajib. Semua contoh di artikel ini memakai CSS murni dan berjalan di browser modern. Framework membantu kalau kamu butuh konsistensi di banyak halaman dengan cepat, tapi memahami CSS dasarnya tetap penting agar kamu bisa mengubah tampilan sesuai kebutuhan dan memperbaiki bug saat terjadi.
Kenapa panah select tetap muncul padahal sudah diberi background-image?
Kemungkinan besar properti appearance: none belum dipasang, sehingga panah bawaan sistem tetap tampil dan bertumpuk dengan panah buatanmu. Pastikan juga aturan :focus tidak memakai shorthand background yang menghapus gambar panah.
Apa beda :focus dan :focus-visible?
:focus aktif setiap kali elemen mendapat fokus, baik dari klik mouse maupun tombol Tab. :focus-visible hanya aktif ketika browser menilai indikator fokus perlu ditampilkan, umumnya saat navigasi keyboard. Untuk input teks, browser biasanya tetap menganggap fokus perlu terlihat meskipun diklik, sedangkan untuk tombol, ring fokus tidak muncul saat diklik mouse.
Apakah validasi HTML5 dan JavaScript sudah cukup untuk keamanan?
Tidak. Validasi di sisi klien hanya membantu pengguna mengisi dengan benar. Data yang sampai ke server bisa dimanipulasi, jadi backend (misalnya PHP atau Laravel) tetap harus memvalidasi ulang dan membersihkan semua input sebelum disimpan.
Bagaimana cara mengubah warna checkbox tanpa membuat checkbox palsu?
Gunakan properti accent-color pada input checkbox atau radio, misalnya accent-color: #3b82f6;. Cara ini didukung semua browser utama, tetap bisa diakses keyboard dan screen reader, dan jauh lebih sederhana daripada menyembunyikan checkbox asli lalu menggambar kotak baru dengan pseudo-element.