CSS State-Based Pseudo-Classes
Introduction
State-based pseudo-classes let CSS select elements according to conditions maintained by the browser or exposed through native HTML. A checkbox can be checked, a field can be disabled, a required input can be valid or invalid, a link can be visited, and an element can match the current URL fragment. CSS can respond to those conditions immediately without adding presentation classes through JavaScript.
These selectors are essential for clear forms, accessible controls, navigation feedback, and lightweight interactive patterns. Their convenience also creates risks. A green border does not make data valid on the server, a hidden checkbox is not automatically accessible, and a disabled-looking div is not disabled. Reliable state styling begins with semantic HTML and uses pseudo-classes to communicate behavior already present.
What Are State-Based Pseudo-Classes?
A state-based pseudo-class is a keyword prefixed with a colon that matches an element while a defined condition is true. Examples include :checked, :disabled, :enabled, :required, :optional, :valid, :invalid, :read-only, :read-write, :target, :link, and :visited. User-action states such as hover, focus, and active belong to the same broader pseudo-class system.
The browser updates matching as values, attributes, focus, history, or the URL changes. Declarations then enter the normal cascade. The pseudo-class does not store application data; it reflects a condition defined by HTML and CSS specifications or browser state.
Basic Syntax
input:disabled {
background: #eef1f4;
}
input:checked + label {
font-weight: 700;
}
input:invalid {
border-color: #b42318;
}
Each pseudo-class is attached to the selector for the element whose state is tested. The adjacent sibling combinator in the checked example then selects a following label. The label does not become checked; it is styled because it follows an input that is checked. Reading each relationship explicitly prevents confusion in complex form controls.
How Browsers Update State Matching
Browsers parse selectors when stylesheets load and maintain information that helps them recalculate relevant styles. When a user checks a box, types into a field, follows a fragment link, or when script changes a native state property, affected selectors are reevaluated. Matching declarations can appear or disappear without the page manually rewriting style attributes.
This process is dynamic but not magical. The underlying element must support the state. Adding :checked to a div selector does not make a div checkable, and setting a class named disabled does not cause the native disabled pseudo-class to match. State comes from semantics and APIs; CSS observes it.
Why State Selectors Matter
State styles answer practical questions: Is this option selected? Can this control be used? Is a value required? Did browser validation accept the current entry? Which section does the URL target? Has this link been visited? Clear visual feedback reduces uncertainty and helps users recover from errors.
State should rarely be communicated by color alone. Text, native control appearance, icons, outlines, and programmatic semantics may all contribute. Styling must preserve contrast, keyboard operation, high-contrast modes, zoom, and assistive-technology interpretation.
Link State Pseudo-Classes
Links have several state selectors. :link matches hyperlinks that have not been visited, while :visited matches links whose destinations are in browser history. :any-link matches both visited and unvisited links. Hover, focus, focus-visible, and active can add interaction feedback.
A link must usually have an href to participate in link-state matching. An anchor used only as a named target is not necessarily a hyperlink. Use a button for actions and an anchor with a meaningful destination for navigation; visual styling should follow behavior rather than imitate it.
:link and :any-link
The :link pseudo-class provides a low-markup way to style unvisited links. The :any-link pseudo-class is convenient for declarations shared by both visited and unvisited hyperlinks, such as underline thickness, focus offset, or general font treatment.
:any-link {
text-underline-offset: 0.18em;
}
:link {
color: #0b5d91;
}
Class selectors are still useful for component variants. A navigation link may combine a component class with any-link so the rule communicates both role and link state.
:visited and Privacy Restrictions
The :visited pseudo-class can help users recognize destinations they have already opened. Browsers deliberately restrict which properties visited rules may expose because unrestricted styling and computed-style inspection could reveal browsing history. Color-related properties are generally the intended use, while layout, visibility, images, and many other properties are restricted or behave as though the link were unvisited.
Scripts cannot reliably read whether a link is visited through computed styles. Design visited feedback as a privacy-preserving enhancement. Maintain sufficient contrast and do not hide visited destinations or change content based on history.
Ordering Link States
The traditional LVHA order places :link, :visited, :hover, and :active so later equal-specificity states override earlier ones. Modern interfaces also require focus and focus-visible. Rather than following a mnemonic blindly, decide which properties each state controls and preserve the focus indicator through every overlap.
A link may be visited, hovered, focused, and active at once. Use source order, distinct properties, and combined selectors when necessary. The current page is not represented by active or visited; use aria-current="page" and an attribute selector for that persistent state.
User-Action States in Context
Hover, focus, and active are state-based in a broad sense, but they describe interaction rather than stored form or validation state. Hover reflects pointer designation, focus identifies the current input destination, and active usually lasts during a press. Their behavior can overlap with checked, valid, or disabled conditions.
A checked radio may also be focused, an invalid field may be focused, and an enabled button may be hovered and active. Design combinations deliberately so one state does not erase another. A focus ring should remain visible when hover changes the background.
What Is :checked?
The :checked pseudo-class matches checked checkboxes and radio buttons, and selected option elements where applicable. It reflects native state, including state changed by user interaction or JavaScript through the relevant properties. It does not match a generic element carrying a class named checked.
Checked is persistent until the control changes again, unlike the brief active pseudo-class. It is appropriate for selected options, toggle controls backed by a real checkbox, and sibling-driven presentation. The control's semantics and accessible name must remain intact.
Styling a Checked Label
<input id="updates" class="choice-input" type="checkbox">
<label for="updates" class="choice-label">Email updates</label>
.choice-input:checked + .choice-label {
background: #e4f4ec;
border-color: #18794e;
}
The label follows the input, so the adjacent sibling selector can style it when the input is checked. Clicking the associated label toggles the checkbox through native behavior. Keep the input focusable and provide an obvious focus-visible treatment. Visually hiding it is acceptable only with a tested technique that preserves keyboard and assistive-technology access.
Radio Button State
Radio buttons sharing a name form a group in which only one option is checked at a time. CSS can style the checked option's label and supporting content. The group needs a fieldset and legend when the options answer one question, giving users the context that visual grouping alone cannot provide.
Do not simulate radio behavior with unrelated checkboxes merely because checked selectors are convenient. Native radio controls provide expected arrow-key behavior, exclusivity, form submission, and accessibility semantics. CSS should enhance their appearance without replacing those contracts.
The :indeterminate State
The :indeterminate pseudo-class matches controls in an indeterminate condition, such as a checkbox whose JavaScript property is set to indicate a mixed selection. It can also apply in specification-defined situations for radio groups or progress elements. For checkboxes, indeterminate is visual state separate from checked and is not submitted as a third checkbox value.
A “select all” control can use this state when some, but not all, child options are selected. The design should communicate “mixed” through more than color, and scripts must keep the parent and children synchronized.
What Are :disabled and :enabled?
The :disabled pseudo-class matches elements that support being disabled and currently are disabled. The :enabled pseudo-class matches supported controls that are available for interaction. A disabled native control generally cannot be activated, is commonly removed from sequential keyboard navigation, and may not be submitted with form data.
A div with aria-disabled="true" does not automatically match disabled because ARIA communicates semantics but does not implement native behavior. Such custom controls require attribute selectors and complete behavior management, although native elements should be preferred whenever possible.
Disabled Fieldsets
Disabling a fieldset can disable many descendant form controls according to HTML rules, with special handling for content in the first legend. Those controls can match disabled even if they do not each carry a disabled attribute. This inherited disabledness is browser behavior, not CSS inheritance.
Test fieldset patterns rather than assuming attribute presence is the only source of state. Preserve readable labels and explanations; disabled styling should not reduce contrast so far that users cannot understand what is unavailable or why.
Enabled Does Not Mean Valid
An enabled control is available for interaction, but its current value may be valid, invalid, empty, required, optional, read-only, or out of range. State pseudo-classes represent separate dimensions that can overlap. A selector such as input:enabled:invalid requires both conditions.
Combining states can make rules precise, but long chains are difficult to reason about. Start from the user-facing condition and include only selectors needed to distinguish it. Validation logic belongs in constraints and application code, not in an increasingly complicated CSS selector.
:required and :optional
The :required pseudo-class matches form controls whose HTML state requires a value for constraint validation. :optional matches supported controls that are not required. These selectors let forms mark required fields consistently without duplicating presentation classes.
Users still need an understandable textual indication of required fields. A red border or asterisk generated only through CSS may not be announced or may be ambiguous. Include “required” in accessible instructions or labels according to the form design, and ensure optional conventions are explained.
What Are :valid and :invalid?
The :valid and :invalid pseudo-classes reflect HTML constraint validation. Constraints can come from required, type, pattern, minimum, maximum, step, length, and other supported rules. A form element can also match based on whether its relevant descendant controls satisfy validation.
Client-side validity improves feedback but does not replace server validation. Users and automated clients can bypass browser constraints, and business rules may require server data. Treat CSS validity as presentation of the browser's current evaluation, not a security boundary.
A Validation Example
<label for="email">Work email</label>
<input id="email" type="email" required
aria-describedby="email-error">
<span id="email-error" class="error">Enter a valid email.</span>
input:invalid {
border-color: #b42318;
}
input:invalid ~ .error {
display: block;
}
The pattern demonstrates matching, but a production form should avoid presenting an untouched empty required field as an error immediately. A submitted or touched class, or user-invalid where supported, can make timing more considerate. Error text must be associated with the input and announced appropriately when it appears.
Avoiding Premature Validation
Required fields are invalid while empty, including when a form first loads. Styling every invalid input bright red before users interact can make the page look broken and overwhelm the real task. Validation feedback should generally appear after submission, blur, or meaningful interaction, depending on the workflow.
Applications can add a class to the form after a submit attempt, then use .was-submitted input:invalid. Newer :user-valid and :user-invalid states aim to reflect user interaction according to browser rules. Verify target support and behavior before relying on them exclusively.
:user-valid and :user-invalid
The user-valid and user-invalid pseudo-classes distinguish validation state after sufficient user interaction, reducing immediate error styling on untouched controls. Their exact activation timing follows browser behavior, so they should be tested across supported environments. They still derive from native constraint validation.
A robust form can use them as an enhancement while retaining submission-time application logic. Never rely only on border color. Provide concise messages, indicate how to correct the value, move focus to an error summary when appropriate, and validate again on the server.
:in-range and :out-of-range
Range-aware controls with minimum and maximum constraints can match :in-range or :out-of-range. These selectors are useful for number, date, time, and other supported input types. Out-of-range is related to invalid but communicates a more specific reason.
The UI should state the allowed range in text before users make an error. Localized formats, step constraints, and browser input behavior need testing. CSS can highlight the condition, while the associated message explains it.
:placeholder-shown
The :placeholder-shown pseudo-class matches an input or textarea while placeholder text is being displayed. It can help distinguish an empty field from one containing a value, but placeholder behavior is not a reliable substitute for required, valid, or user interaction state.
Floating-label patterns often combine placeholder-shown with focus and sibling selectors. They must keep a persistent visible label, handle autofill, pasted values, invalid values, zoom, and browser differences. A placeholder should provide an example or hint, not carry the only label.
:default
The :default pseudo-class identifies the default item among a group of related form controls, such as the initially checked radio or checkbox, or a default submission control according to HTML rules. It describes default status rather than the current checked status, so a user can change the selection while the original default relationship remains relevant.
This distinction can be useful in settings interfaces that offer “restore defaults.” Do not assume default and checked always match after interaction. Test the actual control type and browser behavior.
:read-only and :read-write
The :read-only pseudo-class matches elements not editable by the user, while :read-write matches editable elements according to platform rules. These states apply more broadly than input elements with a readonly attribute; editing hosts such as contenteditable elements can participate.
A read-only field can usually receive focus and allow text selection, unlike a disabled control. It may also be submitted. Styling should preserve legibility and make the difference understandable. Do not make read-only text look disabled if users still need to copy or inspect it.
Read-Only Versus Disabled
Readonly prevents value editing on supported controls but preserves several normal interactions. Disabled removes availability more completely and affects focus and form submission. The correct choice comes from product behavior, not visual preference. CSS selectors merely reflect that choice.
If a value is informational and not an editable form control, plain text may be more semantic than a read-only input. Use read-only fields when their control behavior, selection, or submission role is genuinely useful.
The :target Pseudo-Class
The :target pseudo-class matches the element whose id corresponds to the current URL fragment. Navigating to page.html#contact makes the element with id contact the target. CSS can highlight the section, open a visual treatment, or draw attention after an in-page link.
Target state is reflected in browser history and deep links, which can be useful for accordions, tabs, or notes, but CSS alone may not supply the full semantics and keyboard behavior those widgets require. Ensure headings or target elements can receive appropriate focus when navigation context demands it.
A :target Example
<a href="#installation">Installation</a>
<section id="installation">
<h2>Installation</h2>
<p>Follow the setup steps.</p>
</section>
section:target {
border-inline-start: 4px solid #f2a900;
background: #fff9e8;
}
The fragment remains shareable and works without script. Account for sticky headers with scroll margin so the targeted heading is not hidden. Do not use target color as the only indication of navigation context.
Open and Disclosure States
Modern CSS includes states for elements that can open or close, such as :open for supported disclosure or picker behavior and :popover-open for displayed popovers. Details elements can also be selected through the open attribute. Browser support and exact applicability should be checked against project requirements.
Prefer native details and summary for straightforward disclosures and the popover API for suitable nonmodal overlays. State styling can rotate an icon or reveal layout, but keyboard interaction, dismissal, focus behavior, and accessible naming still come from the HTML feature and any required script.
Fullscreen and Modal States
The :fullscreen pseudo-class matches an element displayed through the Fullscreen API, allowing it to use the viewport effectively and adjust controls for that context. Dialog elements shown modally can match :modal in supporting browsers. These are browser-managed states rather than arbitrary classes.
Applications still need clear exit controls, focus management, and error handling around the related APIs. CSS should account for safe areas, contrast, and responsive sizing. A pseudo-class can style a state after entry; it cannot request permissions or enter the state by itself.
Combining Multiple States
Chained pseudo-classes express logical AND. input:required:invalid matches only inputs that are both required and invalid. button:enabled:hover:active requires all three conditions. Comma-separated selectors express alternatives rather than a shared condition.
Use combinations to prevent misleading states, but avoid chains that mirror complex business logic. A class or data attribute maintained by the application is often clearer for domain-specific conditions. Native pseudo-classes should describe native conditions.
State Selectors with Relationships
Combinators can transfer the visual effect of one element's state to another target. input:checked + label styles an adjacent label, input:invalid ~ .error styles a later message, and .field:focus-within .hint styles a descendant hint while any field control has focus.
The relationship must match the DOM: siblings need the same parent, adjacent targets must immediately follow, and descendants must be inside the context element. Keep messages programmatically associated even when CSS uses proximity for styling.
State Selectors and :has()
The relational :has() pseudo-class lets a parent or earlier element respond to descendant or relative state. For example, .field:has(input:invalid) can style the whole field group, while .card:has(:focus-visible) can provide container context around the focused control.
This reduces some wrapper classes, but support policies and selector readability matter. Has does not create accessible semantics. It simply changes which element receives CSS based on a relationship to a stateful element.
Specificity
Most state-based pseudo-classes contribute one class-category count to specificity. input:invalid has 0-1-1, while .field input:required:invalid has three class-category counts and one type count. The state is not automatically stronger than a normal class.
Functional pseudo-classes such as is, not, has, and where follow special specificity rules. Where contributes zero; is, not, and has take the strongest selector argument. Cascade origin, importance, layers, and source order can decide conflicts before or after specificity, so inspect the complete cascade.
State Does Not Replace Semantics
A class named checked does not make a div a checkbox. A gray appearance does not disable activation. A red border does not communicate an error to a screen reader. A hidden panel revealed by CSS does not automatically update expanded state or move focus. Native state selectors are most reliable when attached to native elements.
When a custom widget is necessary, implement the established keyboard and ARIA pattern, expose state through truthful attributes, and style those attributes. CSS follows semantics; it should not be used to fake them.
Accessibility Considerations
Every meaningful state needs perceivable and programmatic communication. Combine color with text, icons, shape, or position. Preserve focus indicators, ensure disabled and read-only content remains readable, associate validation messages, and announce dynamic changes when required. Test keyboard navigation, screen readers, zoom, high contrast, forced colors, and touch input.
Do not hide native controls using techniques that remove them from accessibility or focus when users still need to operate them. The visual design can be custom while the semantic control remains present and functional.
Common Mistake: Styling Invalid Fields Immediately
An empty required field is invalid before the user begins. Applying a strong invalid style on initial load creates a page full of errors and weakens the meaning of later validation. Delay presentation until an attempted submission or meaningful interaction through an application class or supported user-invalid behavior.
Positive valid styling should also be restrained. An optional empty field can be valid without the user completing anything, so a green success indicator may be misleading. Define what feedback helps the user rather than styling every technical state.
Common Mistake: Confusing Disabled and ARIA Disabled
Native disabled changes browser behavior and matches disabled. aria-disabled="true" communicates disabled semantics but generally does not stop focus, clicks, or keyboard events and does not make the disabled pseudo-class match. Custom controls must enforce behavior through script and can be styled with an attribute selector.
Prefer a native button with disabled when removal from interaction is intended. Use aria-disabled only when the pattern genuinely requires a focusable but unavailable control and supplies an explanation.
Common Mistake: Using Checked for General Application State
The checkbox hack can reveal menus or panels through :checked, but forcing unrelated application state into a hidden checkbox can complicate semantics, history, focus, and synchronization. It is appropriate for simple form-backed choices, not a universal state-management system.
For menus, dialogs, tabs, and asynchronous workflows, use the native element or script pattern designed for the behavior. Expose persistent state through checked, open, ARIA attributes, or classes according to its real meaning.
Debugging State Selectors
Inspect the element in browser developer tools and confirm it supports the state. Many tools can force hover, focus, active, visited-independent link simulations, valid, invalid, or checked states for inspection. Review matched and overridden declarations, attributes, properties, form constraints, and the final DOM relationship.
When a form state seems wrong, check the element's validity properties and validation message in the console. Confirm disabled fieldsets, readonly behavior, min and max constraints, required state, hidden siblings, and script updates. Test real interaction after forcing a visual state because timing and semantics may differ.
Testing Strategy
Test initial, changed, error, recovery, disabled, read-only, and submission states. Use keyboard, pointer, touch, and relevant assistive technology. Refresh with URL fragments, navigate browser history, revisit links, autofill fields, and allow password managers to interact. Verify that state remains understandable when colors are overridden or motion is reduced.
Automated accessibility and browser tests can catch many regressions, but manual testing is necessary for error timing, discoverability, focus, and real input behavior. Include state combinations rather than validating each selector in isolation.
Best Practices
Start with semantic HTML and native controls. Use pseudo-classes to reflect browser-managed state, keep selectors scoped to components, and combine them only when the user-facing condition requires it. Preserve focus and readable contrast, associate messages, and avoid deriving business state from purely visual conditions.
Use attributes or classes for application-specific state, and keep ARIA truthful. Delay intrusive validation until interaction, distinguish disabled from read-only, use target for shareable fragment context, and verify newer states against the project's browser support. Test every important transition and recovery path.
Interview Questions and Answers
What is a state-based pseudo-class? It matches an element while a defined browser or native HTML condition is true. What does checked match? Checked checkboxes or radios and selected options where applicable. What is the difference between disabled and read-only? Disabled controls are unavailable and generally excluded from focus and submission, while read-only controls can often be focused, selected, and submitted.
Do valid and invalid replace server validation? No. What does target match? The element whose id equals the URL fragment. Does aria-disabled make disabled match? No. What is pseudo-class specificity? Most state pseudo-classes contribute class-category weight. Can states be combined? Yes; chained pseudo-classes require every condition to be true.
Practical Review Checklist
- Use native elements that actually support the intended state.
- Keep link, interaction, form, validation, and application states conceptually separate.
- Preserve focus visibility when other states overlap.
- Pair color with text, icons, shape, or programmatic semantics.
- Delay invalid styling until users have interacted or attempted submission.
- Associate form instructions and errors with their controls.
- Distinguish disabled, read-only, checked, selected, and current states.
- Use truthful ARIA attributes and implement behavior for custom controls.
- Validate data on the server even when native constraints are present.
- Test keyboard, touch, history, autofill, zoom, high contrast, and dynamic updates.
Key Takeaway
State-based pseudo-classes connect CSS to conditions the browser already understands. They can distinguish visited links, checked choices, unavailable controls, required fields, validation results, editability, URL targets, disclosures, and other native states. Because matching updates automatically, they reduce presentation-only scripting and keep styles close to semantic behavior.
The strongest implementations do not mistake visual state for functional state. Build with native HTML, expose application state through meaningful attributes or classes, communicate changes beyond color, and test overlapping conditions across input methods. CSS should reveal and reinforce truth in the interface, not manufacture a state that the document and application do not actually possess.