15: Forms, Inputs & Pseudo-Classes

The browser gives you 80% of validation for free. Use the right input types and pair every input with a label.

The form skeleton

<form>
  <label for="email">Email</label>
  <input id="email" type="email" required>

  <label for="pwd">Password</label>
  <input id="pwd" type="password" minlength="8" required>

  <textarea rows="3"></textarea>
  <button type="submit">Sign in</button>
</form>
Why label + for/id: Clicking the label focuses the input, meaning bigger hit area. Screen readers announce label text. Always pair them.

Input types: free validation and keyboards

typeWhat you get
emailValidates @, shows @ keyboard on mobile
passwordDots, show/hide toggle (browser)
numberNumeric keyboard, min/max/step
telPhone keyboard
urlValidates URL, .com key
searchClear button
checkbox / radioToggles / single-choice
selectDropdown
<input type="email" required>           <!-- validates @ -->
<input type="number" min="1" max="10">
<input type="checkbox" required>         <!-- must check -->
<select><option>Cat</option></select>
Gotcha: Don't use type="text" for everything. Doing this means you lose mobile keyboards and free validation. Use the specific type.

Pseudo-classes and pseudo-elements

  • item 1
  • item 2 with :nth-child(2)
  • item 3

::before adds marker

SelectorWhen
:hoverMouse over
:focusKeyboard/tab focused
:activeBeing pressed
:checkedCheckbox/radio ticked
:nth-child(n)nth child
::before / ::afterGenerated content (needs content: "")
:has()Parent that has… (e.g., label:has(:checked))
input:hover { border-color: #0b57d0; }
input:focus { outline: 2px solid #0b57d0; outline-offset: 2px; }
input:checked { accent-color: #0b57d0; }
li:nth-child(2) { background: #fff7cc; }
.tag::before { content: "#"; color: #888; }
label:has(input:checked) { background: #eef6ff; }
Outline versus border: Do not remove outline on :focus without replacing it. Keyboard users need it. Use outline (it does not affect layout) or style :focus-visible for keyboard only.

Focus, cursor, and polish

button { cursor: pointer; }
button:disabled { cursor: not-allowed; opacity: 0.6; }
input:focus-visible { outline: 2px solid #0b57d0; outline-offset: 2px; }

Live demo: usable form, keyboard only

Tab through this form. All focus states are visible, labels are clickable, and validation is native.

Try submitting empty. The browser blocks it with no JS.

Exercise: Style a login form keyboard only (20 min)
  1. Build the form above with correct label for/id and type="email" and password.
  2. Add :hover, :focus-visible, and :active styles. Make focus obvious.
  3. Style :checked via accent-color or label:has(:checked).
  4. Unplug mouse. Tab through, fill, and submit. Is every control reachable?
Prev14: Color & Backgrounds Next16: Transitions & Animations