This episode covers form styling and interactive controls: the basics of styling inputs and labels, clear focus states, valid and invalid indicators from pseudo-classes, modern form accessories like accent-color, and how to style select, checkbox, and range controls without JavaScript.

Forms are the most important interaction gateway on the web — login, signup, search, checkout. But native browser form controls look different on each system and often feel awkward. Form styling unifies their appearance with your design. More than cosmetics, form styling determines how easily people fill forms out. Clear focus states, comfortable label spacing, and visible error indicators are all part of usability and accessibility. Why does it matter? A confusing form makes users give up halfway. This episode equips you to style inputs, selects, checkboxes, and ranges in a modern way — with pure CSS, without patching hard-to-style native content.
Form controls can be styled like other elements, but consistency must be kept:
.kelompok { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
label { font-weight: 600; font-size: 0.9rem; }
input[type="text"], input[type="email"] { padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 1rem; font-family: inherit; background-color: white; color: #0f172a; }Set font-family: inherit on inputs so the text inside follows the page font, not the browser default. font-size: 1rem or larger also stops phones from zooming when the input is focused.
Visible focus is an accessibility requirement. Replace the plain border with a clear ring:
input[type="text"]:focus,
input[type="email"]:focus,
textarea:focus,
select:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
}
input:focus-visible {
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.3);
}Removing the default outline without a replacement is a common mistake. The ring (box-shadow) pattern around the border keeps the focus indicator while looking more modern than the default outline.
The :valid and :invalid pseudo-classes respond to the browser's built-in validation:
input:invalid {
border-color: #dc2626;
}
input:invalid:focus {
box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.2);
}
input:valid {
border-color: #16a34a;
}
input::placeholder {
color: #94a3b8;
opacity: 1;
}Controls with required, type, or pattern attributes automatically become valid or invalid. Combining :invalid with :focus gives feedback while the user is still typing, instead of turning red from the moment the page loads. ::placeholder styles the hint text, which is often too pale in browsers.
Checkboxes, radio buttons, and ranges are hard to style fully. accent-color colors native controls in one line:
.selesai { accent-color: #2563eb; }
.form-periksa { display: grid; gap: 12px; }
.periksa { display: flex; align-items: center; gap: 10px; }
.periksa input { width: 18px; height: 18px; }accent-color changes the check mark, radio fill, and range fill to match your palette without restyling the whole control. For larger sizes, enlarge the checkbox box and click area with padding.
<label class="periksa">
<input type="checkbox" name="setuju" checked>
<span>Saya menyetujui ketentuan layanan</span>
</label>Wrap the input inside a label so the text also becomes a click target. Controls that are easy to click speed up form completion significantly.
select can be given padding, border, and font to stay aligned:
select {
padding: 10px 36px 10px 12px;
border: 1px solid #cbd5e1;
border-radius: 8px;
font-size: 1rem;
font-family: inherit;
background-color: white;
color: #0f172a;
appearance: none;
}appearance: none removes the browser's built-in arrow. If you remove it, provide a replacement arrow — for example a CSS background icon — so users still know this control can be opened.
input[type="range"] {
width: 100%;
accent-color: #2563eb;
}
progress {
accent-color: #2563eb;
width: 100%;
}input[type="range"], progress, and meter all accept accent-color. For advanced needs like custom tracks, use the ::-webkit-slider-thumb and ::-moz-range-thumb pseudo-elements with per-browser prefixes.
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Form Kontak</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<form class="form" novalidate>
<div class="kelompok">
<label for="nama">Nama</label>
<input id="nama" type="text" name="nama" placeholder="Nama lengkap" required>
</div>
<div class="kelompok">
<label for="email">Email</label>
<input id="email" type="email" name="email" placeholder="nama@email.com" required>
</div>
<div class="kelompok">
<label for="pesan">Pesan</label>
<textarea id="pesan" name="pesan" rows="4" required></textarea>
</div>
<label class="periksa">
<input type="checkbox" name="setuju">
<span>Izinkan saya menerima info terbaru</span>
</label>
<button class="tombol" type="submit">Kirim</button>
</form>
</body>
</html>* { box-sizing: border-box; }
body { margin: 0; padding: 32px; font-family: system-ui, sans-serif; background-color: #f1f5f9; }
.form { max-width: 480px; margin-inline: auto; padding: 24px; background-color: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(15, 23, 42, 0.1); }
.kelompok { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
label { font-size: 0.9rem; font-weight: 600; }
input, textarea { padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 1rem; font-family: inherit; color: #0f172a; }
input:focus, textarea:focus { outline: none; border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2); }
.periksa { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
.periksa input { width: 18px; height: 18px; accent-color: #2563eb; }
.tombol { width: 100%; padding: 12px; border: none; border-radius: 8px; background-color: #2563eb; color: white; font-size: 1rem; cursor: pointer; transition: background-color 0.2s ease; }
.tombol:hover { background-color: #1d4ed8; }Run npx serve . and click into each input. The blue ring shows the focus position, and the accent-color control aligns the checkbox with the button. Test by pressing Tab to make sure the focus order makes sense.
Placeholders disappear when the user types and have low contrast. Always use a separate label element that stays visible.
Red alone isn't enough — color-blind users have trouble distinguishing it. Add error text and an icon alongside the color change.
Removing the default outline without a replacement ring leaves keyboard navigation without guidance. Make sure every control has a visible focus state.
Key takeaways:
:valid and :invalid give validation feedback without JavaScript.accent-color colors checkboxes, radios, and ranges in one line.appearance: none requires a replacement arrow on selects.