In short
Styling forms and buttons with CSS makes them clear and friendly: style inputs and labels, add focus styles, color checkboxes, show errors and build a full signup form. Why are form elements hard to style?
Styling forms and buttons with CSS makes them clear and friendly: style inputs and labels, add focus styles, color checkboxes, show errors and build a full signup form.
Why are form elements hard to style?
Inputs, checkboxes and buttons come with the browser's own look, and it is different in Chrome, Firefox and Safari. They also don't inherit your page font by default. The good news: a small set of rules fixes most of this. Let's start with a reset that many developers use.
input,
button,
select,
textarea {
font: inherit;
color: inherit;
}
font: inherit makes form fields use the same font and size as the rest of your page. Without it, inputs often show a small system font that looks out of place.
How to style inputs and labels
A label is the text that tells the user what to type. Always connect it to its input with for and id, so clicking the label puts the cursor in the field. Showing labels above inputs, as blocks, is easy to read on any screen.
<label for="email">Email address</label>
<input id="email" type="email" placeholder="you@example.com">
label {
display: block;
font-weight: 600;
margin-bottom: 6px;
}
input[type="email"],
input[type="text"],
input[type="password"] {
display: block;
width: 100%;
padding: 10px 12px;
border: 1px solid #9ca3af;
border-radius: 6px;
background: white;
}
input:focus-visible {
outline: 3px solid #93c5fd;
outline-offset: 1px;
border-color: #2563eb;
}
The label sits on its own line in bold, and the input fills the full width with soft rounded corners. When you click or tab into the field, a light blue ring appears so the user knows where they are typing.
Common mistake: Removing the focus outline with
outline: noneand adding nothing instead. Keyboard users then can't see which field is active. Always replace the outline with a clear style of your own.
Checkboxes and radios: accent-color and appearance
Checkboxes and radio buttons used to be very hard to style. Today, one property changes their color: accent-color. It also works on range sliders and progress bars.
input[type="checkbox"],
input[type="radio"] {
accent-color: #16a34a;
width: 1.1rem;
height: 1.1rem;
}
Ticked boxes and selected radios are now green instead of the default blue. The browser still draws them, so they stay accessible and work with the keyboard.
If you need a completely custom look, appearance: none removes the browser's built-in drawing, and then you style the element yourself:
.custom-check {
appearance: none;
width: 20px;
height: 20px;
border: 2px solid #6b7280;
border-radius: 4px;
display: inline-grid;
place-content: center;
}
.custom-check:checked {
background: #16a34a;
border-color: #16a34a;
}
.custom-check:checked::before {
content: "✔";
color: white;
font-size: 14px;
}
With appearance: none the checkbox becomes an empty square that you control. When it is checked, it fills green and shows a white tick. It is still a real checkbox, so the keyboard and screen readers keep working.
Tip: Start with
accent-color. Only reach forappearance: nonewhen the design truly needs it, because you must then style every state yourself (checked, focus, disabled).
Showing errors with :invalid and :user-invalid
Browsers check fields for you when you use attributes like required or type="email". The :invalid pseudo-class matches fields that fail. The problem: an empty required field is invalid as soon as the page loads, so the user sees red errors before typing anything. :user-invalid waits until the user has changed the field or tried to submit, which feels much kinder.
input:user-invalid {
border-color: #dc2626;
background: #fef2f2;
}
Type "hello" in an email field and click away: it turns red. A fresh, empty form stays calm.
Styling buttons with hover, focus, active and disabled
A good button shows clearly when it can be clicked, when it is hovered, when it is pressed and when it can't be used.
.btn {
padding: 12px 20px;
border: none;
border-radius: 8px;
background: #2563eb;
color: white;
font-weight: 600;
cursor: pointer;
}
.btn:hover {
background: #1d4ed8;
}
.btn:active {
transform: translateY(1px);
}
.btn:focus-visible {
outline: 3px solid #f59e0b;
outline-offset: 3px;
}
.btn:disabled {
background: #9ca3af;
cursor: not-allowed;
}
The button is blue, darkens on hover, moves down one pixel when pressed (it feels "real"), shows an orange ring for keyboard users, and turns grey with a "not allowed" cursor when it has the disabled attribute.
Full example: a styled signup form
Let's put everything together into a complete form you can copy.
<form class="signup">
<h2>Create your account</h2>
<div class="field">
<label for="name">Full name</label>
<input id="name" type="text" required>
</div>
<div class="field">
<label for="mail">Email address</label>
<input id="mail" type="email" required placeholder="you@example.com">
</div>
<div class="field">
<label for="pass">Password</label>
<input id="pass" type="password" required minlength="8">
<small>At least 8 characters.</small>
</div>
<label class="check">
<input type="checkbox" required> I agree to the terms
</label>
<button class="btn" type="submit">Sign up</button>
</form>
.signup {
max-width: 400px;
margin: 40px auto;
padding: 24px;
border: 1px solid #e5e7eb;
border-radius: 12px;
font-family: system-ui, sans-serif;
}
.signup input,
.signup button {
font: inherit;
}
.field {
margin-bottom: 16px;
}
.field label {
display: block;
font-weight: 600;
margin-bottom: 6px;
}
.field input {
width: 100%;
padding: 10px 12px;
border: 1px solid #9ca3af;
border-radius: 6px;
box-sizing: border-box;
}
.field input:focus-visible {
outline: 3px solid #93c5fd;
border-color: #2563eb;
}
.field input:user-invalid {
border-color: #dc2626;
background: #fef2f2;
}
.field small {
color: #6b7280;
}
.check {
display: block;
margin-bottom: 20px;
}
.check input {
accent-color: #2563eb;
margin-right: 6px;
}
.btn {
width: 100%;
padding: 12px;
border: none;
border-radius: 8px;
background: #2563eb;
color: white;
font-weight: 600;
cursor: pointer;
}
.btn:hover {
background: #1d4ed8;
}
.btn:focus-visible {
outline: 3px solid #f59e0b;
outline-offset: 3px;
}
You'll see a centered card with three full-width fields, a small hint under the password, a blue checkbox and a wide blue button. Try submitting it empty: the browser stops you, and fields you have touched turn red. You can read more on MDN's styling web forms guide.
Practice exercise
Build a "Contact us" form with name, email, a <textarea> for the message, a "Subscribe" checkbox and a send button. Make the textarea at least 120px tall, color the checkbox with accent-color, show red borders with :user-invalid, and add a disabled style for the button.
Hint: add disabled to the button in HTML to test the disabled style, then remove it. Use min-height: 120px and resize: vertical on the textarea.
Key takeaways
- Use
font: inheritso form fields match your page font. - Always connect labels to inputs, and always show a visible focus style.
accent-colorcolors checkboxes and radios;appearance: nonegives full control.:user-invalidshows errors only after the user interacts.- Style buttons for hover,
:focus-visible,:activeand:disabled.
Frequently asked questions
How do I change the color of a checkbox in CSS?
Use accent-color, for example input[type="checkbox"] { accent-color: green; }. It works in all modern browsers and keeps the checkbox accessible.
What is the difference between :invalid and :user-invalid?
:invalid matches a field as soon as its value breaks a rule, even on page load. :user-invalid only matches after the user has interacted with the field or tried to submit the form.
Can I style the placeholder text?
Yes, with the ::placeholder pseudo-element, for example input::placeholder { color: #9ca3af; }. Keep the contrast high enough to read, and never use a placeholder instead of a label.