Styling Forms with CSS

Style text inputs, textareas, selects, checkboxes and radio buttons, add focus and error states, and lay out forms that work on phones.

Lesson 27 of 49Intermediate15 min read

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

CSS
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: inherit makes form controls use the page font instead of a small system font.
  • width: 100% with box-sizing: border-box makes fields fill their column without overflowing.
  • resize: vertical lets 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

SelectorMatches
:focus-visibleThe field the user is typing in, when focus should show
:user-invalidAn invalid field, but only after the user has interacted with it
:invalidAny invalid field, including empty required ones on page load
:disabledFields that can't be used
:placeholder-shownFields that are still showing their placeholder
:checkedTicked 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:

CSS
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.

Select menus

A <select> can be styled by removing its default arrow with appearance: none and adding your own with a background image. Leave room for the arrow with padding-right. The dropdown list itself is drawn by the operating system, so you can't fully style its options with traditional CSS. Newer browsers support a customisable select with appearance: base-select, which unlocks full styling where it's available.

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 and width: 100% with border-box.
  • Show a clear :focus-visible style on every field.
  • Use :user-invalid so error styles appear only after the user interacts.
  • accent-color recolours 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.

Example 1

A clean contact form

Type in the fields and press Tab to see the focus ring.

.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; }
ResultEdit the code, then press Run or Ctrl+Enter
Line-by-line explanation
  1. Line 2Labels sit on their own line above each field.
  2. Lines 5 to 616px text and full-width fields work well on phones.
  3. Line 10Fields can grow taller, but never wider.
  4. Line 11A soft blue ring shows which field is active.
  5. Line 15The checkbox turns green with accent-color.
Example 2

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 { 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; }
ResultEdit the code, then press Run or Ctrl+Enter
Line-by-line explanation
  1. Lines 5 to 8A red border and tint, but only after the user has touched the field.
  2. Lines 9 to 10The hint below changes colour and adds a message.
  3. Line 12Valid fields get a green border.
Example 3

A responsive two-column 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;
}
ResultEdit the code, then press Run or Ctrl+Enter
Line-by-line explanation
  1. Line 3Two columns when there's room, one column on phones, with no media query.
  2. Line 7The address field spans the full width.
  3. Lines 15 to 17The select loses its default arrow and gets a custom one.

Common CSS Forms Mistakes

Placeholder instead of label

Don't
/* <input placeholder="Email"> with no label */
::placeholder { color: #ccc; }
Do
/* <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

Don't
input:invalid { border-color: red; } /* every empty required field is red at once */
Do
input:user-invalid { border-color: red; }

Inputs overflowing their container

Don't
input { width: 100%; padding: 12px; } /* 100% plus padding is too wide */
Do
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.

Fill in the blankseasy

Make the checkbox green with one property.

input[type="checkbox"] {
  : #16a34a;
}
Multiple choiceeasy

Why add font: inherit to inputs?

Multiple choicemedium

Which selector shows errors only after the user has interacted?

Multiple choicemedium

Why use at least 16px text in inputs?

Fix the bugmedium

The input should fill its 300px form without overflowing, use the form's font, and only allow the textarea to resize vertically. Fix it.

.f { width: 300px; font-family: Georgia, serif; }
.in { width: 100%; padding: 12px; border: 2px solid #ccc; }
.ta { resize: both; }
ResultEdit the code, then press Run or Ctrl+Enter
Checks
  • Input fits the form
  • Input uses the form font
  • Textarea resizes vertically only

CSS Forms Quiz

5 questions to check what you learned.

  1. 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
  2. What does appearance: none do to a select?

    • Hides it
    • Disables it
    • Removes the browser's default look, including the arrow
    • Removes its options
  3. Which pseudo-class matches a ticked checkbox?

    • :selected
    • :checked
    • :active
    • :on
  4. 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
  5. 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

  1. Make .field inputs width: 100%, box-sizing: border-box, padding: 10px and font-size: 16px
  2. Give them a 2px solid border of rgb(209, 213, 219) and border-radius: 8px
  3. On :focus-visible, change the border colour to rgb(37, 99, 235)
  4. Give :user-invalid inputs a border colour of rgb(220, 38, 38)
  5. Make the checkbox accent-color: rgb(22, 163, 74)
ResultEdit the code, then press Run or Ctrl+Enter
Checks
  • Fields fill the form
  • 16px text and 10px padding
  • Grey rounded border
  • Focus and error states
  • Green checkbox