CSS Form-Related Pseudo-Classes

Introduction

CSS form-related pseudo-classes let styles respond to native control state without adding presentation classes for every interaction. A field can be focused, required, optional, valid, invalid, in range, read-only, disabled, checked, or showing its placeholder. The browser already tracks these conditions through HTML semantics and user input, and CSS can reflect them immediately.

State styling is only the presentation layer. A red border does not explain an error, a required asterisk does not make a field required, and browser validation does not secure data on the server. Robust forms combine semantic labels, native constraints, helpful messages, keyboard support, accessible focus, application logic, and server validation. Pseudo-classes make those foundations easier to see and use.

What Are Form-Related Pseudo-Classes?

Form-related pseudo-classes match controls according to state, capability, user interaction, or native constraint validation. Common examples include :focus, :focus-visible, :enabled, :disabled, :checked, :indeterminate, :required, :optional, :valid, :invalid, :in-range, :out-of-range, :read-only, :read-write, :placeholder-shown, and :autofill.

Not every pseudo-class applies to every form element. Checked is meaningful for checkboxes and radio buttons, range states require relevant constraints, and placeholder-shown requires a supported placeholder. Unsupported states simply do not match that element.

Basic Syntax

input:focus {
  border-color: #0b63a3;
}

input:required:invalid {
  border-color: #b42318;
}

input:disabled {
  background: #edf0f3;
}

A pseudo-class attaches to the control selector and adds a condition. Chaining requires all conditions to be true: required-invalid inputs must be both required and currently invalid. Comma-separated selectors provide alternatives. Keep the positive form-control scope clear so broad state selectors do not affect unrelated elements.

How Browsers Update Form State

Browsers derive form state from HTML attributes, DOM properties, entered values, focus, user actions, and constraint-validation rules. When a value or property changes, matching selectors are reevaluated and affected styles are recalculated. JavaScript does not need to rewrite style attributes for every native transition.

The state belongs to the element's behavior, not to CSS. Removing a disabled-looking class does not enable a native control if its disabled property remains true. Likewise, adding an invalid class does not change the browser's validity calculation. Application classes can supplement native state, but their meanings must remain synchronized.

Why Form State Styling Matters

Forms ask users to understand where input goes, which information is required, whether a choice is selected, whether a control is available, and how to correct errors. Visible state reduces uncertainty and shortens recovery. Focus feedback supports keyboard navigation, while validation feedback connects constraints to actionable guidance.

Color alone is insufficient. Combine color with outline, text, icon, shape, or position, and expose programmatic state through native attributes or ARIA where appropriate. Every state should remain understandable at zoom, in high contrast, and with assistive technology.

The :focus Pseudo-Class

The :focus pseudo-class matches the control currently receiving input. Focus may arrive through Tab, Shift+Tab, pointer interaction, scripts, access keys, or widget-specific keyboard movement. Text fields typically focus when clicked or tapped, while platform behavior for buttons and other controls can differ.

Focus is functional, not merely decorative. Removing its visible indication makes keyboard navigation difficult or impossible. A custom focus treatment should be at least as clear as the browser default and must not be clipped by overflow or covered by sticky content.

A Focus Style Example

.field-control:focus {
  border-color: #0b63a3;
  box-shadow: 0 0 0 3px rgba(11, 99, 163, 0.2);
  outline: 2px solid transparent;
}

The ring gives location feedback without changing field dimensions. A transparent outline can preserve an edge in some forced-colors environments, though real high-contrast testing remains necessary. Avoid changing border width unless the base state reserves equal space, because resizing fields during focus causes layout movement.

:focus-visible

The :focus-visible pseudo-class matches focused controls when browser heuristics determine that a visible focus indicator is appropriate. It commonly appears during keyboard navigation and for controls requiring keyboard entry, while some pointer-focused buttons may not match. User preferences and browser behavior influence the decision.

Focus-visible helps avoid suppressing keyboard rings merely because designers dislike rings after mouse clicks. Provide a strong focus-visible rule and test actual keyboard, pointer, touch, and programmatic focus workflows. Never remove focus globally without a reliable replacement.

:focus-within for Field Groups

The :focus-within pseudo-class matches a container when it or any descendant is focused. A field wrapper can highlight its label, border, help text, or unit while the internal input is active. This supplies group context around compound controls.

Keep a direct indicator on the focused control as well. Focus-within tells users which group is active; focus or focus-visible identifies the exact control. Nested groups should not create so many rings that location becomes ambiguous.

The :enabled Pseudo-Class

The :enabled pseudo-class matches form controls that support enabled and disabled states and are currently available. It can constrain hover effects, emphasize interactive fields, or distinguish available options. Enabled does not necessarily mean editable: a read-only input may be enabled but still prevent value changes.

A selector such as button:enabled:hover avoids pointer feedback on disabled buttons. The button still needs clear default and focus states, and availability should not be communicated only through opacity or color.

The :disabled Pseudo-Class

The :disabled pseudo-class matches supported controls that are disabled through native HTML or inherited disabledness rules. Disabled controls generally cannot be activated, are removed from sequential focus, and are not submitted with successful form data. Their values may still be visible.

Disabled styling should preserve readability and, when necessary, explain why an action is unavailable. Excessively low opacity can make labels and values inaccessible. Sometimes allowing submission and presenting a specific error is more helpful than disabling an action without explanation.

Disabled Fieldsets

A disabled fieldset can make many descendant controls disabled, with defined exceptions involving its first legend. Those controls can match disabled even without individual disabled attributes. This behavior allows a related group to be switched off consistently.

Do not assume selector matching depends only on literal attribute presence. Inspect computed state and test the legend interaction. If a complete step becomes unavailable, provide contextual text so users understand the prerequisite rather than seeing a faded region with no explanation.

Native Disabled Versus aria-disabled

aria-disabled="true" communicates an unavailable state to assistive technology but does not automatically stop focus, click handling, keyboard activation, or form behavior, and generally does not make :disabled match. A custom control requires script behavior and an attribute selector for styling.

Use native disabled on native controls when the expected behavior is full unavailability. Use aria-disabled only when a recognized pattern calls for an unavailable control to remain discoverable in focus order and the application reliably prevents activation.

The :required Pseudo-Class

The :required pseudo-class matches supported controls whose required state participates in constraint validation. Styling can draw attention to mandatory fields without duplicating a required class. The control still needs the native required attribute or corresponding property to gain behavior.

A visible indicator should be explained at the start of the form and available beyond CSS. Include required wording or another accessible convention in labels and instructions. A generated asterisk alone may be ambiguous or inconsistently announced.

The :optional Pseudo-Class

The :optional pseudo-class matches supported controls that are not required. Forms can label optional fields explicitly, especially when most fields are required, but subtle styling should not make optional inputs appear disabled or unimportant.

Optional empty controls are often valid. Applying green success styling to every valid field can therefore make untouched optional fields look completed. Design feedback around user needs, not every technically true pseudo-class.

What Are :valid and :invalid?

The :valid and :invalid pseudo-classes reflect HTML constraint validation. A control can be invalid because a required value is missing, a type is malformed, a pattern fails, a number is outside limits, a step is wrong, or another native constraint is not satisfied. Controls barred from constraint validation do not behave like ordinary validatable controls.

Forms and fieldsets may also reflect descendant validity according to applicable rules. This enables group-level presentation, but specific messages should still identify the control and reason that need correction.

Native Validation Constraints

HTML attributes such as required, type, pattern, min, max, step, minlength, and maxlength can contribute to validity. Browser APIs expose validity details and a validation message. Custom script can set a custom validity error when application rules require it.

Constraint support varies by control type, and locale affects date and number entry. Test realistic values, pasted content, autofill, and mobile keyboards. CSS only reflects the resulting state; it does not define or explain the business rule.

A Validity Example

<label for="work-email">Work email</label>
<input id="work-email" type="email" required
       aria-describedby="work-email-error">
<p id="work-email-error" class="error">Enter a valid work email.</p>

input:invalid {
  border-color: #b42318;
}

input:valid {
  border-color: #18794e;
}

This demonstrates selectors, but unconditional invalid styling appears before interaction because an empty required field is invalid. Production forms should control feedback timing and reveal the associated message when useful.

Avoiding Immediate Error Styling

Displaying every invalid state on initial load makes a blank form look like a failed form. Users have not made errors yet. A common approach adds a class such as .was-submitted after a submission attempt, then scopes invalid styles beneath it. Another approach marks individual fields as touched after suitable interaction.

The right timing depends on the task. Validate early enough to help, but not so early that feedback interrupts typing. Avoid clearing error text before the user has actually corrected the value.

:user-valid and :user-invalid

The :user-valid and :user-invalid pseudo-classes are designed to represent validity after browser-defined user interaction, reducing premature feedback. Their activation timing and support should be verified across project browsers and webviews.

They can progressively enhance a form, while submission-time application logic remains the dependable baseline. Error summaries, focus management, associated messages, and server validation are still required for robust workflows.

Server-Side Validation Is Still Required

Client-side constraints improve speed and convenience but are not security controls. Requests can bypass the browser, values can be modified, and business rules may depend on server data. The server must validate, authorize, normalize, and safely process every submitted value.

When the server returns errors, connect them to fields and preserve entered values. CSS can style aria-invalid="true" or an application error class, but aria-invalid does not automatically make the native invalid pseudo-class match. Native and server state must be coordinated intentionally.

The :in-range Pseudo-Class

The :in-range pseudo-class matches controls whose current value falls within applicable minimum and maximum constraints. It is useful for number, range, date, time, and other supported controls. A value may also need to satisfy step and type rules, so in-range does not necessarily mean fully valid in every respect.

State styling should supplement visible range instructions. Users should know acceptable boundaries before submitting, not discover them only through a colored border.

The :out-of-range Pseudo-Class

The :out-of-range pseudo-class matches supported controls with values outside their permitted range. It allows a specific visual treatment or associated message distinct from other invalid conditions. Out-of-range values are generally invalid, but invalid can have many other causes.

A message should state both the accepted range and the entered problem. Consider localized date and number formats, decimal steps, and boundary values. Test minimum, maximum, just-inside, and just-outside cases.

Range Example

<label for="experience">Years of experience</label>
<input id="experience" type="number" min="0" max="60">

input:in-range {
  border-color: #18794e;
}

input:out-of-range {
  border-color: #b42318;
}

An empty optional input may not be out of range, while malformed input handling varies by control and browser. Do not use border color alone; provide text and native attributes so the constraint is understandable.

The :read-only Pseudo-Class

The :read-only pseudo-class matches elements the user cannot edit according to platform rules. This includes supported controls with readonly and can include noneditable elements more broadly. A read-only input may still be focusable, selectable, copied, and submitted.

Read-only styling should remain legible and distinguish the value from an unavailable disabled field. If the value is purely informational and does not need control behavior, ordinary text may be more semantic than a read-only input.

The :read-write Pseudo-Class

The :read-write pseudo-class matches editable controls and editing hosts such as appropriate contenteditable elements. It can apply a shared editable treatment without enumerating every input type. Enabled and read-write are not identical: an enabled read-only input is available but not editable.

Custom rich-text editors require much more than a border style. Keyboard operation, selection, formatting controls, accessible naming, sanitization, and persistence must be designed and tested separately.

Read-Only Versus Disabled

Readonly preserves several interactions and normally preserves form submission, while disabled removes availability and successful submission. The attributes also apply to different control sets. The correct choice depends on whether users should inspect, copy, focus, or submit the value.

Do not choose disabled merely to obtain gray styling. Select semantics first, then style the resulting pseudo-class. A visually similar treatment can conceal important behavioral differences.

The :checked Pseudo-Class

The :checked pseudo-class matches checked checkboxes and radio buttons, and selected options in supported contexts. It updates after user or script changes. Checked is persistent selection state, unlike the brief active press state.

CSS can style the control, its adjacent label, or later related content through combinators. Native input semantics, names, grouping, and focus must remain intact. Hiding the control with display none removes it from keyboard and accessibility workflows.

Checkbox and Label Example

<input id="terms" class="choice" type="checkbox" required>
<label for="terms">I accept the terms</label>

.choice:checked + label {
  color: #18794e;
  font-weight: 700;
}

.choice:focus-visible + label {
  outline: 3px solid #f2a900;
  outline-offset: 3px;
}

The associated label toggles the checkbox, and focus remains visible even if the input is visually customized. The required condition still needs validation feedback if users submit without checking.

Radio Groups

Radio buttons sharing a name represent one mutually exclusive choice. Place the group in a fieldset with a legend so its question is announced. Checked styling can emphasize the selected label, but every option must remain readable and operable.

If one radio in a required group is required, browser validity applies to the group according to HTML rules. Error messaging should identify the group rather than treating each option as an unrelated field.

The :indeterminate Pseudo-Class

The :indeterminate pseudo-class represents a mixed or unresolved state for supported controls. A checkbox's indeterminate property is commonly set by script for “select all” behavior when some child options are selected. It is separate from checked and does not submit a third value.

Use a visible mixed indicator and keep parent-child state synchronized. Assistive technology should receive the appropriate mixed state through native or ARIA semantics. CSS alone cannot calculate and set checkbox indeterminacy from arbitrary child controls.

The :default Pseudo-Class

The :default pseudo-class identifies the default control among related form elements according to HTML rules, such as an initially selected option or default button. Default is not necessarily the same as current checked state after users interact.

This distinction can support “restore defaults” interfaces or emphasize the suggested submit action. Test control-specific browser behavior and avoid making the default option look mandatory when alternatives remain valid.

The :placeholder-shown Pseudo-Class

The :placeholder-shown pseudo-class matches an input or textarea while its placeholder is displayed. It can distinguish an empty presentation state and is often used in floating-label patterns. Once a value prevents placeholder display, the state stops matching.

Placeholder text is not a label. It disappears during entry, may have low contrast, and is poor for persistent instructions. Every control needs an accessible, visible label appropriate to the design.

Floating Labels

A floating-label design commonly combines focus and not-placeholder-shown to move a label above the field. The markup and selector relationship must support autofill, pasted values, validation, prefilled server data, and browsers that handle placeholders differently. A placeholder containing a space is sometimes used only to activate the selector, which deserves documentation.

Ensure the label never overlaps user text, remains readable at zoom, and is programmatically associated. A conventional label above the input is usually more robust and easier to maintain.

The :autofill Pseudo-Class

The :autofill pseudo-class can match fields populated by browser autofill. Browser styling may have strong built-in behavior, and vendor-prefixed forms have historically been required in some engines. Support and override capabilities should be tested against current project browsers.

Do not hide autofill status or make filled text unreadable. Use appropriate autocomplete tokens so password managers and browsers understand each field. Autofill is an important usability and accessibility feature, not a style problem to defeat.

Combining Form States

Chained pseudo-classes require every condition. input:required:invalid targets required fields that are currently invalid, while input:enabled:read-write:focus targets available editable focused inputs. Combinations can prevent broad styling but may become hard to reason about.

Use the shortest selector that expresses the user-facing condition. If business state such as “server rejected” is involved, expose a class or truthful ARIA attribute rather than forcing it into native pseudo-class logic.

Styling Related Labels and Messages

Sibling selectors can transfer a control state to nearby content. input:invalid ~ .error can reveal a later message, while input:checked + label styles an adjacent label. These rules depend on shared parentage and document order.

Visual proximity is not a programmatic association. Use for and id for labels and aria-describedby or appropriate error relationships for messages. Dynamic error announcements may require a live region or focused error summary depending on the workflow.

Using :has() for Form Groups

The relational :has() pseudo-class can style a wrapper based on descendant state. .field:has(input:invalid) marks a group containing an invalid input, and .field:has(:focus-visible) adds context around keyboard focus. This can reduce state classes for purely presentational grouping.

Has does not replace labels, messages, validation timing, or support analysis. Scope it to stable components and consider a fallback when target browsers include older webviews.

Specificity

Most form-related pseudo-classes contribute one class-category count. input:invalid has specificity 0-1-1, while .field input:required:invalid has three class-category counts and one type count. Chaining states increases weight.

Avoid escalating selectors to overpower browser or component styles without understanding the cascade. Use classes, cascade layers, source order, and low-specificity foundations deliberately. Important declarations may be needed for limited platform quirks, but should not become the default form strategy.

Forced Colors and Native Appearance

Operating-system high-contrast and forced-colors modes can replace backgrounds, borders, shadows, and text colors. A validation state based only on a pale background may disappear. Preserve outlines, text messages, icons, and system-compatible borders. Test rather than assuming a custom theme survives.

Removing native appearance from checkboxes, radio buttons, selects, or date controls creates responsibility for every state, including focus, disabled, checked, indeterminate, hover, and high contrast. Use accent-color when modest theming can preserve native behavior.

Mobile and Touch Considerations

Input type and attributes influence mobile keyboards, pickers, and autofill. CSS pseudo-classes display the resulting state but cannot request the correct keyboard by themselves. Use semantic types, inputmode where appropriate, autocomplete tokens, and clear labels.

Keep targets large, do not rely on hover, ensure error text stays visible above on-screen keyboards, and test orientation and zoom. Native date or number controls can vary across platforms, so avoid designs that assume one exact internal appearance.

Common Mistake: Green Borders on Every Valid Field

An optional empty field can be valid, and a prefilled or default value may be valid before user interaction. Painting all valid controls green can make untouched fields appear confirmed. It also creates visual noise that weakens meaningful success feedback.

Show positive feedback only when it helps users complete a risky or complex task. Combine interaction state, application state, or explicit confirmation rather than styling every technically valid value.

Common Mistake: Using Placeholder as a Label

Placeholder text disappears when users type, often has reduced contrast, and does not provide a reliable persistent name. Placeholder-shown can support styling, but it does not transform placeholder content into a proper label.

Use a label associated with the control. Provide examples or formatting hints in help text, and keep them accessible while the user enters and reviews a value.

Common Mistake: Confusing Read-Only and Disabled

Disabled fields are unavailable and commonly excluded from submission; read-only fields remain focusable or selectable in many cases and normally submit their value. Styling both identically hides behavioral differences and can frustrate users trying to copy information.

Select the state according to workflow and then design each treatment. If a displayed value is not really a control, use semantic text rather than either state.

Common Mistake: Trusting CSS Validation

Valid and invalid reflect browser constraints, not every business or security rule. CSS cannot verify uniqueness, account permissions, stock availability, server time, or malicious requests. It can also be bypassed completely.

Validate on the server, return structured errors, preserve entered values, and connect each message to its field. CSS should make the result clear, not serve as the validation engine.

Debugging Form Pseudo-Classes

Inspect the control in browser developer tools and force focus, valid, invalid, checked, disabled, or other supported states when available. Check element properties, attributes, inherited disabledness, validity flags, validationMessage, value, and actual DOM relationships. Confirm that the stylesheet loaded and the selector parsed.

If a declaration is crossed out, inspect specificity, layers, native appearance, important rules, and source order. Test real interaction after forced-state inspection because browser validation timing, autofill, and focus heuristics may differ.

Testing Strategy

Test blank, valid, malformed, boundary, out-of-range, pasted, autofilled, server-rejected, disabled, read-only, checked, unchecked, and indeterminate states. Navigate by keyboard in both directions, submit without completing fields, correct errors, refresh, and restore browser history where relevant.

Use screen readers, zoom, forced colors, touch, mobile keyboards, password managers, and translated labels. Automated tests can verify attributes and some states, but manual testing is necessary for timing, discoverability, focus, and recovery.

Best Practices

Start with native labels and controls, then style browser-maintained states. Provide strong focus-visible feedback, delay intrusive errors until interaction, associate messages, distinguish disabled from read-only, and keep selection controls keyboard accessible. Use semantic attributes rather than presentation classes when native behavior applies.

Validate again on the server, expose server errors truthfully, preserve contrast and high-contrast behavior, and test autofill and mobile input. Keep selectors scoped and readable. Use application classes or attributes for domain state that native pseudo-classes cannot represent.

Interview Questions and Answers

What are form-related pseudo-classes? Selectors that match controls based on focus, availability, requiredness, validity, range, editability, selection, or other native state. What is the difference between disabled and read-only? Disabled removes availability and usually submission; read-only prevents editing while preserving several interactions and submission. Does invalid replace server validation? No.

Why avoid immediate invalid styling? Required fields are invalid while initially empty, before users make an error. What does placeholder-shown mean? The control is currently displaying its placeholder. Can aria-invalid trigger invalid? Not by itself. What specificity do these pseudo-classes have? Most contribute class-category weight.

Practical Review Checklist

  • Use semantic labels, native controls, and truthful HTML attributes.
  • Provide a visible focus indicator for every keyboard-operable control.
  • Distinguish enabled, disabled, read-only, and editable behavior.
  • Explain required fields in text and associate help with each control.
  • Delay strong invalid feedback until interaction or submission.
  • Pair valid, invalid, range, and server states with useful messages.
  • Keep checked, radio-group, and indeterminate controls accessible.
  • Do not use placeholders as labels or CSS as server validation.
  • Test autofill, mobile keyboards, forced colors, zoom, and recovery.
  • Use application state for rules native pseudo-classes cannot express.

Key Takeaway

Form-related pseudo-classes connect CSS to browser-managed control state. They can reveal focus, availability, requiredness, validity, range, editability, selection, placeholder display, autofill, and group context without scripts continually rewriting styles. Combined with relationships and has, they can present state across labels, messages, and wrappers.

The visual layer must remain grounded in semantics. Native attributes create behavior, accessible text explains requirements and errors, application logic manages domain state, and the server validates every request. When pseudo-classes reinforce those responsibilities instead of replacing them, forms become clearer, more maintainable, and more usable across keyboards, touch devices, assistive technology, and browser environments.