Chapter 14: CSS Forms & Custom Form Controls
Forms are the primary vehicle for user input on the web. Unstyled browser native controls often feel inconsistent. Mastering custom input fields, modern accent-color, custom checkboxes and radio buttons, custom select menus, validation state feedback (:required, :valid, :invalid), floating labels, and building accessible form layouts elevates your UI components to production standards.
14.1 Customizing Text Inputs, Textareas & accent-color
Modern CSS allows subtle styling of text inputs while standardizing checkbox/radio colors across browsers using the single line accent-color property.
<!DOCTYPE html>
<html>
<head>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; }
h3 { margin-top: 0; color: #38bdf8; }
.form-box { background: #1e293b; border-radius: 12px; padding: 20px; max-width: 360px; }
.form-group { margin-bottom: 14px; }
label { display: block; font-size: 12px; color: #94a3b8; margin-bottom: 6px; }
/* Custom styled input */
input[type="text"], textarea {
width: 100%; padding: 10px 14px; background: #0f172a;
border: 1px solid #334155; border-radius: 8px; color: #fff; font-size: 14px;
transition: border-color 0.2s, box-shadow 0.2s;
}
input[type="text"]:focus, textarea:focus {
outline: none; border-color: #38bdf8;
box-shadow: 0 0 10px rgba(56,189,248,0.3);
}
/* accent-color standardizes native checkbox/radio tinting */
.accent-demo { accent-color: #38bdf8; }
</style>
</head>
<body>
<h3>Input Styling & accent-color Demo</h3>
<div class="form-box">
<div class="form-group">
<label>Styled Input Field:</label>
<input type="text" placeholder="Type here..." />
</div>
<div class="form-group" style="display:flex;gap:16px;align-items:center;">
<label style="margin:0;cursor:pointer;">
<input type="checkbox" class="accent-demo" checked /> accent-color Checkbox
</label>
<label style="margin:0;cursor:pointer;">
<input type="radio" class="accent-demo" checked /> accent-color Radio
</label>
</div>
</div>
</body>
</html>
14.2 Custom Radio Buttons, Checkboxes & Select Menus
By using appearance: none or hiding native elements with :checked + label selectors, you can create pixel-perfect custom form controls matching any design system.
<!DOCTYPE html>
<html>
<head>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; }
h3 { margin-top: 0; color: #818cf8; }
.control-group { margin-bottom: 16px; display: flex; flex-direction: column; gap: 10px; }
/* Hidden native radio/checkbox */
.custom-check input, .custom-radio input { display: none; }
/* Custom Checkbox UI */
.custom-check { display: flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; }
.check-box {
width: 20px; height: 20px; border: 2px solid #6366f1; border-radius: 6px;
display: flex; align-items: center; justify-content: center; transition: all 0.2s;
}
.custom-check input:checked + .check-box { background: #6366f1; }
.custom-check input:checked + .check-box::after { content: "✔"; color: #fff; font-size: 12px; font-weight: bold; }
/* Custom Radio UI */
.custom-radio { display: flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; }
.radio-circle {
width: 20px; height: 20px; border: 2px solid #10b981; border-radius: 50%;
display: flex; align-items: center; justify-content: center; transition: all 0.2s;
}
.radio-dot { width: 10px; height: 10px; background: #10b981; border-radius: 50%; opacity: 0; transform: scale(0); transition: all 0.2s; }
.custom-radio input:checked + .radio-circle .radio-dot { opacity: 1; transform: scale(1); }
/* Styled Select Dropdown */
select.custom-select {
appearance: none;
width: 100%; max-width: 280px; padding: 10px 36px 10px 14px;
background: #1e293b url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2394a3b8'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E") no-repeat right 12px center / 20px;
border: 1px solid #334155; border-radius: 8px; color: #fff; font-size: 14px; cursor: pointer;
}
</style>
</head>
<body>
<h3>Custom Checkbox, Radio & Select Dropdown</h3>
<div class="control-group">
<label class="custom-check">
<input type="checkbox" checked />
<div class="check-box"></div>
<span style="font-size:14px;">Custom Checkbox (:checked state)</span>
</label>
<label class="custom-radio">
<input type="radio" name="plan" checked />
<div class="radio-circle"><div class="radio-dot"></div></div>
<span style="font-size:14px;">Monthly Plan</span>
</label>
<label class="custom-radio">
<input type="radio" name="plan" />
<div class="radio-circle"><div class="radio-dot"></div></div>
<span style="font-size:14px;">Annual Plan (Save 20%)</span>
</label>
<select class="custom-select">
<option>Select Experience Level...</option>
<option>Beginner (0-1 yrs)</option>
<option>Intermediate (1-3 yrs)</option>
<option>Advanced (3+ yrs)</option>
</select>
</div>
</body>
</html>
14.3 Form Validation State Feedback (:required, :valid, :invalid)
CSS state pseudo-classes (:required, :valid, :invalid) display real-time feedback borders and status checkmarks as users fill out required form fields.
<!DOCTYPE html>
<html>
<head>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; }
h3 { margin-top: 0; color: #f59e0b; }
.field { margin-bottom: 14px; max-width: 320px; position: relative; }
label { display: block; font-size: 12px; color: #94a3b8; margin-bottom: 4px; }
input {
width: 100%; padding: 10px 14px; background: #1e293b;
border: 2px solid #334155; border-radius: 8px; color: #fff; font-size: 14px;
}
/* Green border for valid filled inputs */
input:focus:valid { border-color: #10b981; box-shadow: 0 0 8px rgba(16,185,129,0.3); }
/* Red border for invalid inputs */
input:focus:invalid { border-color: #ef4444; box-shadow: 0 0 8px rgba(239,68,68,0.3); }
/* Required asterisk via label::after */
label.required::after { content: " *"; color: #ef4444; font-weight: bold; }
</style>
</head>
<body>
<h3>Form Validation Feedback Demo</h3>
<p style="font-size:12px;color:#64748b;">Type an email address below to see live green/red border feedback:</p>
<form onsubmit="return false;">
<div class="field">
<label class="required">Email Address:</label>
<input type="email" placeholder="name@example.com" required />
</div>
<div class="field">
<label class="required">Username (min 4 chars):</label>
<input type="text" minlength="4" placeholder="alex123" required />
</div>
</form>
</body>
</html>
14.4 The Floating Label Pattern (:placeholder-shown & :focus-within)
The floating label design pattern moves the field label from inside the input box to floating above the input box as soon as the user starts typing, using :placeholder-shown or :focus-within.
<!DOCTYPE html>
<html>
<head>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; }
h3 { margin-top: 0; color: #10b981; }
/* Floating Label Container */
.float-group {
position: relative;
margin-bottom: 20px;
max-width: 320px;
}
.float-input {
width: 100%; padding: 16px 14px 6px;
background: #1e293b; border: 2px solid #334155;
border-radius: 8px; color: #fff; font-size: 14px;
transition: border-color 0.2s;
}
.float-label {
position: absolute; top: 12px; left: 14px;
color: #64748b; font-size: 14px; pointer-events: none;
transition: all 0.2s ease;
}
/* When input is focused OR has value (placeholder NOT shown) -> float label up! */
.float-input:focus + .float-label,
.float-input:not(:placeholder-shown) + .float-label {
top: 4px; left: 14px;
font-size: 10px; color: #38bdf8; font-weight: 700;
}
.float-input:focus { outline: none; border-color: #38bdf8; }
</style>
</head>
<body>
<h3>Pure CSS Floating Label Pattern</h3>
<div class="float-group">
<input type="text" class="float-input" id="name" placeholder=" " />
<label for="name" class="float-label">Full Name</label>
</div>
<div class="float-group">
<input type="email" class="float-input" id="email" placeholder=" " />
<label for="email" class="float-label">Email Address</label>
</div>
</body>
</html>
14.5 Mini-Project #1: Accessible Floating Label Contact Form
Putting everything together into a production-grade, accessible contact form featuring floating labels, custom checkboxes, custom radios, select dropdowns, and validation states:
<!DOCTYPE html>
<html>
<head>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; }
.form-card {
background: #1e293b; border: 1px solid #334155; border-radius: 16px;
padding: 28px; max-width: 440px; margin: 0 auto;
box-shadow: 0 12px 32px rgba(0,0,0,0.4);
}
.form-title { margin-top: 0; color: #38bdf8; font-size: 1.4rem; }
/* Floating input */
.float-group { position: relative; margin-bottom: 16px; }
.float-input, .float-textarea {
width: 100%; padding: 16px 14px 6px;
background: #0f172a; border: 2px solid #334155; border-radius: 8px;
color: #fff; font-size: 14px; transition: all 0.2s;
}
.float-textarea { height: 80px; resize: vertical; }
.float-label {
position: absolute; top: 12px; left: 14px;
color: #64748b; font-size: 14px; pointer-events: none; transition: all 0.2s;
}
.float-input:focus + .float-label, .float-input:not(:placeholder-shown) + .float-label,
.float-textarea:focus + .float-label, .float-textarea:not(:placeholder-shown) + .float-label {
top: 4px; left: 14px; font-size: 10px; color: #38bdf8; font-weight: 700;
}
.float-input:focus, .float-textarea:focus { outline: none; border-color: #38bdf8; }
/* Submit Button */
.btn-submit {
width: 100%; background: linear-gradient(135deg, #4f46e5, #06b6d4);
color: #fff; padding: 12px; border: none; border-radius: 8px;
font-weight: 700; font-size: 15px; cursor: pointer;
box-shadow: 0 4px 14px rgba(79,70,229,0.4); transition: transform 0.2s;
}
.btn-submit:hover { transform: translateY(-2px); }
</style>
</head>
<body>
<div class="form-card">
<h3 class="form-title">Contact Us</h3>
<form onsubmit="alert('Message Sent!'); return false;">
<div class="float-group">
<input type="text" class="float-input" id="c-name" placeholder=" " required />
<label for="c-name" class="float-label">Your Name</label>
</div>
<div class="float-group">
<input type="email" class="float-input" id="c-email" placeholder=" " required />
<label for="c-email" class="float-label">Email Address</label>
</div>
<div class="float-group">
<textarea class="float-textarea" id="c-msg" placeholder=" " required></textarea>
<label for="c-msg" class="float-label">Your Message</label>
</div>
<button type="submit" class="btn-submit">Send Message →</button>
</form>
</div>
</body>
</html>
14.6 Interactive Sandbox — Forms & Floating Labels
Test custom checkboxes, toggle switches, and floating labels live in the code playground below: