Sebuah komponen tidak hanya punya satu tampilan — tombol punya wajah saat diarahkan kursor, saat ditekan, saat menerima fokus keyboard, dan saat tidak dapat dipakai. Pada sistem desain, setiap wajah itu bukan improvisasi per komponen, melainkan kontrak visual: daftar state yang wajib ada, beserta perubahan gaya yang disepakati untuk masing-masing. Mekanisme pseudo-classnya sudah kita kuasai pada Chapter Hover dan Active serta Focus dan Focus-visible ; chapter ini menyusunnya menjadi kontrak sistem dan menerapkannya pada tombol serta input.

Kontrak State untuk Tombol dan Input #

Kontrak yang baik mencakup empat state interaksi untuk elemen yang dapat dipakai, dan kontrak itu berlaku sama untuk seluruh komponen sejenis: dua tombol apa pun di sistem harus merespons hover, fokus, tekan, dan kondisi mati dengan pola yang identik. Konsistensi inilah yang membuat pengguna berhenti perlu menebak perilaku:

StateSelectorPerubahan pada tombol
Istirahat.tombolLatar --biru-600, teks putih
hover.tombol:hoverLatar melangkah ke tingkatan lebih gelap
focus.tombol:focus-visibleOutline 2px biru langit dengan offset 2px
active.tombol:activeLatar paling gelap pada skala
disabled.tombol:disabledOpacity 0,5 dan kursor not-allowed

File yang dipakai demo menerapkan kontrak itu pada dua komponen:

.tombol {
  --tombol-latar: rgb(29, 78, 216);
  --tombol-teks: rgb(255, 255, 255);

  background-color: var(--tombol-latar);
  color: var(--tombol-teks);
  padding: 8px 16px;
  border: 0;
  border-radius: 8px;
  font-size: 14px;
  transition: background-color 0.15s ease;
}

.tombol:hover { background-color: rgb(30, 64, 175); }
.tombol:active { background-color: rgb(30, 58, 138); }
.tombol:focus-visible { outline: 2px solid rgb(2, 132, 199); outline-offset: 2px; }
.tombol:disabled { opacity: 0.5; cursor: not-allowed; }

.input {
  border: 1px solid rgb(203, 213, 225);
  border-radius: 8px;
  padding: 8px 12px;
  font-size: 14px;
}

.input:focus-visible {
  outline: 2px solid rgb(2, 132, 199);
  border-color: rgb(29, 78, 216);
}

Mari kita telaah keputusannya:

  1. State sebagai aturan terpisah, bukan penulisan ulang: .tombol mendeklarasikan gaya istirahat sekali; setiap state hanya menimpa properti yang berubah. Perubahan latar mengikuti urutan ramp dari Chapter Color Palettes — hover dan active adalah tingkatan yang lebih gelap dari token istirahat.
  2. :focus-visible, bukan :focus: outline hanya muncul saat fokus datang dari navigasi keyboard, sehingga pengguna mouse tidak melihat cincin yang tidak perlu; pengguna keyboard tetap mendapat petunjuk fokus sebagaimana dibahas pada Chapter Visible Focus .
  3. :disabled memakai opacity: nilai computed background-color pada tombol mati tidak berubah — peredupan terjadi pada tingkat penggambaran. Ini membuat state mati murah dan berlaku pada semua isi tombol sekaligus.

Perhatikan juga satu detail pada transisi: transition di .tombol membuat perpindahan antar-state bergerak halus, tetapi pergerakan itu bukan untuk semua orang. Pengguna yang mengaktifkan preferensi gerak minim perlu dihormati dengan membungkus transisi dalam @media (prefers-reduced-motion: no-preference) — pola lengkapnya kita bahas pada Chapter Reduced Motion .

Menguji Kontrak secara Otomatis #

Tidak semua state dapat diprobe secara otomatis: :hover dan :active menuntut interaksi pointer yang tidak bisa dilakukan harness, sehingga keduanya diverifikasi dengan mencoba manual di browser. Yang bisa diverifikasi otomatis adalah state yang berlaku statis: :disabled pada tombol yang beratribut disabled, dan :focus-visible pada input yang menerima fokus saat halaman dimuat. Demo chapter ini memprobe ketiganya — dua tombol dan satu input yang otomatis difokuskan oleh skrip kecil.

Hasil di Browser:

text
Tombol istirahat tampil berlatar rgb(29, 78, 216).
Tombol mati tercatat opacity 0.5 dengan kursor not-allowed,
sementara computed background-color-nya tetap rgb(29, 78, 216).
Input yang difokuskan saat halaman dimuat membawa outline-width 2px,
outline-color rgb(2, 132, 199), dan border-color rgb(29, 78, 216).

Nilai-nilai tersebut diverifikasi otomatis melalui penanda data-expected. Saat membangun komponen baru, salin kontraknya terlebih dahulu sebagai komentar, lalu isi implementasinya — pola state yang sama berlaku untuk elemen bentuk pada Chapter Checked dan Disabled serta Focus States .

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

contoh-kontrak-state.css

Unduh berkas
css
.tombol {
  --tombol-latar: rgb(29, 78, 216);
  --tombol-teks: rgb(255, 255, 255);

  background-color: var(--tombol-latar);
  color: var(--tombol-teks);
  padding: 8px 16px;
  border: 0;
  border-radius: 8px;
  font-size: 14px;
  transition: background-color 0.15s ease;
}

.tombol:hover { background-color: rgb(30, 64, 175); }
.tombol:active { background-color: rgb(30, 58, 138); }
.tombol:focus-visible { outline: 2px solid rgb(2, 132, 199); outline-offset: 2px; }
.tombol:disabled { opacity: 0.5; cursor: not-allowed; }

.input {
  border: 1px solid rgb(203, 213, 225);
  border-radius: 8px;
  padding: 8px 12px;
  font-size: 14px;
}

.input:focus-visible {
  outline: 2px solid rgb(2, 132, 199);
  border-color: rgb(29, 78, 216);
}