Forms are where visitors do things: search, check out, book a table, send a message. Browser default form styles are functional but plain, and they vary between Chrome, Safari and Firefox. A little CSS makes forms look consistent, feel friendlier, and, most importantly, become easier to complete without mistakes.
Good form styling isn't just decoration. Clear labels, roomy inputs, visible focus rings and obvious error messages all reduce the number of people who give up halfway. On phones, the right font size stops the browser zooming in on every field, and full-width inputs are easier to tap.
In this lesson you'll style text inputs and textareas, give focus a clear look, customise select menus, recolour checkboxes and radio buttons with accent-color, show validation errors only after the user has typed, and lay out forms that switch from one column on phones to two on larger screens. You built the HTML for forms in the HTML forms lesson; now you'll make them look good.
Styling text inputs and textareas
input, textarea, select {
font: inherit;
padding: 10px 12px;
border: 1px solid #d1d5db;
border-radius: 8px;
width: 100%;
box-sizing: border-box;
}
textarea { min-height: 120px; resize: vertical; }font: inheritmakes form controls use the page font instead of a small system font.width: 100%withbox-sizing: border-boxmakes fields fill their column without overflowing.resize: verticallets people make a textarea taller, but not wider than the layout.- Style placeholders with
::placeholder, but never use placeholder text instead of a label.
Focus and validation states
| Selector | Matches |
|---|---|
:focus-visible | The field the user is typing in, when focus should show |
:user-invalid | An invalid field, but only after the user has interacted with it |
:invalid | Any invalid field, including empty required ones on page load |
:disabled | Fields that can't be used |
:placeholder-shown | Fields that are still showing their placeholder |
:checked | Ticked checkboxes and selected radios |
Prefer :user-invalid over :invalid for error styles. With :invalid, a required field turns red the moment the page loads, before the person has typed anything.
Checkboxes, radios and range sliders
The quickest way to brand these controls is accent-color:
input[type="checkbox"], input[type="radio"], input[type="range"] { accent-color: #16a34a; }One line recolours the tick, the dot and the slider in every modern browser, while keeping the built-in accessibility. For fully custom designs, appearance: none removes the browser's look so you can draw your own, but then you must style every state yourself, including :checked, :focus-visible and :disabled.
CSS Forms Syntax
Form styling
label { display: block; font-weight: 600; margin-bottom: 4px; }
input, select, textarea { font: inherit; padding: 10px; width: 100%; box-sizing: border-box; }
input:focus-visible { outline: 3px solid #93c5fd; border-color: #2563eb; }
input:user-invalid { border-color: #dc2626; }
::placeholder { color: #9ca3af; }
input[type="checkbox"] { accent-color: #16a34a; }
textarea { resize: vertical; }CSS Forms Summary
- Give inputs
font: inherit, padding, a border, a radius andwidth: 100%withborder-box. - Show a clear
:focus-visiblestyle on every field. - Use
:user-invalidso error styles appear only after the user interacts. accent-colorrecolours checkboxes, radios and sliders in one line.- Keep input text at 16px or more on phones to avoid zooming.
CSS Forms Examples
Each example runs in the page. Edit the code and press Run to see what changes.
A clean contact form
Type in the fields and press Tab to see the focus ring.
<form class="form">
<label for="n">Name</label>
<input id="n" type="text" placeholder="Sam Lee">
<label for="m">Message</label>
<textarea id="m" placeholder="How can we help?"></textarea>
<label class="check"><input type="checkbox" checked> Email me a copy</label>
<button>Send message</button>
</form>.form { max-width: 380px; font-family: sans-serif; }
label { display: block; font-weight: 600; margin: 12px 0 4px; }
input[type="text"], textarea {
font: inherit; font-size: 16px;
width: 100%; box-sizing: border-box;
padding: 10px 12px;
border: 1px solid #d1d5db; border-radius: 8px;
}
textarea { min-height: 110px; resize: vertical; }
input:focus-visible, textarea:focus-visible { outline: 3px solid #bfdbfe; border-color: #2563eb; }
::placeholder { color: #9ca3af; }
.check { display: flex; align-items: center; gap: 8px; font-weight: 400; }
.check input { accent-color: #16a34a; width: 18px; height: 18px; }
button { margin-top: 14px; padding: 10px 18px; border: 0; border-radius: 8px; background: #16a34a; color: white; font: 600 16px sans-serif; cursor: pointer; }Line-by-line explanation
- Line 2Labels sit on their own line above each field.
- Lines 5 to 616px text and full-width fields work well on phones.
- Line 10Fields can grow taller, but never wider.
- Line 11A soft blue ring shows which field is active.
- Line 15The checkbox turns green with
accent-color.
Error states that wait for the user
Type "abc" in the email field, then press Tab. It only turns red after you've interacted.
<form class="form" novalidate>
<label for="e">Email</label>
<input id="e" type="email" required>
<p class="hint">We'll send your receipt here.</p>
</form>.form { max-width: 340px; font-family: sans-serif; }
label { display: block; font-weight: 600; margin-bottom: 4px; }
input { font: inherit; width: 100%; box-sizing: border-box; padding: 10px; border: 2px solid #d1d5db; border-radius: 8px; }
input:user-invalid {
border-color: #dc2626;
background: #fef2f2;
}
input:user-invalid + .hint { color: #dc2626; }
input:user-invalid + .hint::before { content: "Please enter a valid email. "; font-weight: 600; }
input:user-valid { border-color: #16a34a; }
.hint { font-size: 14px; color: #6b7280; margin: 6px 0 0; }Line-by-line explanation
- Lines 5 to 8A red border and tint, but only after the user has touched the field.
- Lines 9 to 10The hint below changes colour and adds a message.
- Line 12Valid fields get a green border.
A responsive two-column form
<form class="grid">
<div><label for="f">First name</label><input id="f"></div>
<div><label for="l">Last name</label><input id="l"></div>
<div class="full"><label for="a">Address</label><input id="a"></div>
<div><label for="c">Country</label>
<select id="c"><option>United Kingdom</option><option>Ireland</option></select></div>
<div><label for="p">Postcode</label><input id="p"></div>
</form>.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px 16px;
font-family: sans-serif;
}
.full { grid-column: 1 / -1; }
label { display: block; font-size: 14px; font-weight: 600; margin-bottom: 4px; }
input, select {
font: inherit; font-size: 16px; width: 100%; box-sizing: border-box;
padding: 10px; border: 1px solid #d1d5db; border-radius: 8px; background: white;
}
select {
appearance: none;
background: white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23374151' stroke-width='2'/%3E%3C/svg%3E") no-repeat right 12px center;
padding-right: 36px;
}Line-by-line explanation
- Line 3Two columns when there's room, one column on phones, with no media query.
- Line 7The address field spans the full width.
- Lines 15 to 17The select loses its default arrow and gets a custom one.
Common CSS Forms Mistakes
Placeholder instead of label
/* <input placeholder="Email"> with no label */
::placeholder { color: #ccc; }/* <label for="em">Email</label><input id="em"> */
label { display: block; }Placeholders disappear when typing starts and are often too faint to read.
Red errors on page load
input:invalid { border-color: red; } /* every empty required field is red at once */input:user-invalid { border-color: red; }Inputs overflowing their container
input { width: 100%; padding: 12px; } /* 100% plus padding is too wide */input { width: 100%; padding: 12px; box-sizing: border-box; }CSS Forms in Real Projects
Forms in production
Payment providers such as Stripe spend a lot of time on form details: large fields, inline validation after typing, and helpful error text right under the field. Most design systems style inputs with a 1px border, a focus ring in the brand colour and a red error state, and they set accent-color so checkboxes match the brand. Checkout forms that use auto-fit grids work on phones without extra code.
CSS Forms Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Make the checkbox green with one property.
input[type="checkbox"] {
: #16a34a;
}
accent-color recolours checkboxes, radios and sliders.Why add font: inherit to inputs?
Which selector shows errors only after the user has interacted?
Why use at least 16px text in inputs?
The input should fill its 300px form without overflowing, use the form's font, and only allow the textarea to resize vertically. Fix it.
<form class="f"><input class="in"><textarea class="ta"></textarea></form>.f { width: 300px; font-family: Georgia, serif; }
.in { width: 100%; padding: 12px; border: 2px solid #ccc; }
.ta { resize: both; }- Input fits the form
- Input uses the form font
- Textarea resizes vertically only
CSS Forms Quiz
5 questions to check what you learned.
What does resize: vertical do on a textarea?
- Lets the user drag it taller but not wider
- Hides it
- Makes it scroll sideways
- Fixes its height
What does appearance: none do to a select?
- Hides it
- Disables it
- Removes the browser's default look, including the arrow
- Removes its options
Which pseudo-class matches a ticked checkbox?
- :selected
- :checked
- :active
- :on
Why shouldn't a placeholder replace a label?
- Placeholders are slow
- Labels are required for CSS
- Placeholders don't work on phones
- Placeholders disappear when typing starts and are often hard to read
Which grid value gives two columns on wide screens and one on phones without a media query?
- repeat(2, 1fr)
- auto auto
- repeat(auto-fit, minmax(200px, 1fr))
- 50% 50%
CSS Forms Challenge
Style a booking form
- Make
.fieldinputswidth: 100%,box-sizing: border-box,padding: 10pxandfont-size: 16px - Give them a
2px solidborder ofrgb(209, 213, 219)andborder-radius: 8px - On
:focus-visible, change the border colour torgb(37, 99, 235) - Give
:user-invalidinputs a border colour ofrgb(220, 38, 38) - Make the checkbox
accent-color: rgb(22, 163, 74)
<form style="width: 320px">
<input class="field" id="name" required>
<input class="field" id="mail" type="email" required>
<label><input id="agree" type="checkbox"> Remind me by email</label>
</form>- Fields fill the form
- 16px text and 10px padding
- Grey rounded border
- Focus and error states
- Green checkbox