CSS Pseudo-Classes: :hover, :focus and :active
Introduction
CSS interaction pseudo-classes let a stylesheet respond to temporary states without adding or removing classes through JavaScript. The most familiar are :hover, :focus, and :active. They can make links, buttons, fields, cards, and navigation controls feel responsive, but each represents a different condition. Hover describes pointing-device presence, focus identifies the element currently receiving input, and active usually describes the short activation period while a control is being pressed.
Good state styling does more than add visual polish. It communicates what is interactive, where a keyboard user is located, and whether an action has begun. Poor state styling can hide essential information from touch users, remove focus visibility, or confuse a pressed moment with a persistent selected state. Understanding input differences and overlapping states is therefore essential for accessible, resilient interfaces.
What Is a Pseudo-Class?
A pseudo-class is a keyword added to a selector to match an element under a defined condition. It begins with one colon and appears after the element, class, attribute, or other compound-selector components it qualifies. The general pattern is selector:pseudo-class. For example, button:hover matches a button while it satisfies the browser's hover condition.
Pseudo-classes do not create new elements and usually require no markup changes. Browsers update matching as state changes, then recalculate affected styles and render the new appearance. Structural pseudo-classes such as :first-child describe document relationships, while user-action pseudo-classes describe interaction. This tutorial concentrates on hover, focus, active, and closely related focus patterns.
Basic Syntax
.action:hover {
background-color: #0f5f8f;
}
.action:focus-visible {
outline: 3px solid #f2a900;
outline-offset: 3px;
}
.action:active {
transform: translateY(1px);
}
These rules may all match the same control at different times, and some can match simultaneously. A keyboard-focused button may remain focused after activation. A mouse pointer can hover a focused button, and pressing it can add active while hover and focus still apply. The cascade determines which declarations win when states set the same property, so state rules should be designed as a coordinated system.
Why Interaction States Matter
Interaction states provide feedback between user intent and application response. Hover can preview that an element is actionable. Focus shows which control will receive the next keyboard command. Active gives immediate confirmation that a press is occurring before an application completes its work. Together, they reduce uncertainty and support efficient navigation.
State styles must not be the only way to identify an interactive element. Links should still look like links, buttons should have appropriate semantics, fields need labels, and controls need understandable names. CSS feedback enhances those foundations. It cannot convert an inert div into a keyboard-operable button or supply missing semantics.
What Is :hover?
The :hover pseudo-class matches when a pointing mechanism designates an element, commonly when a mouse cursor is over its rendered area. It can also match an ancestor while one of its descendants is hovered, depending on the document relationship and browser behavior. Hover generally ends when the pointer leaves, although touch browsers may emulate or retain hover in device-specific ways.
Typical uses include changing link color, emphasizing a button, revealing secondary visual detail, or elevating a card. Hover should communicate availability or provide an enhancement, not gate essential actions or information. Many users navigate without a mouse, and some devices cannot hover at all.
A Basic Hover Example
<a class="course-link" href="css.html">Explore CSS</a>
.course-link {
color: #124f79;
text-decoration-thickness: 1px;
}
.course-link:hover {
color: #0a3150;
text-decoration-thickness: 3px;
}
The link remains identifiable before interaction and receives stronger emphasis on hover. This is more robust than removing its underline and depending on a hover color to reveal clickability. The hover style supplements the default state rather than repairing an unclear default.
Hover on Links
Links commonly change color, underline position, background, or decoration thickness when hovered. Preserve sufficient contrast in both states and avoid layout shifts. Increasing font weight can change text width, causing nearby navigation items to move. Decoration, color, or a non-layout-changing background often provides steadier feedback.
The destination and accessible name remain properties of the anchor, not of its hover design. If a link contains an icon and text, hovering either descendant can match the anchor. Use padding on the anchor itself to create a comfortable pointer target rather than styling a small text fragment inside a larger visual area.
Hover on Buttons
A button hover state can darken its background, adjust border contrast, or slightly change elevation. The result should remain visually related to the normal button so users recognize continuity. Large movement, scaling, or color reversal can be distracting and may shift neighboring content if dimensions are not stable.
Do not apply hover feedback to disabled controls as though they were actionable. A selector such as .button:not(:disabled):hover limits the effect for native buttons. Disabled behavior and explanation should be considered carefully; sometimes preventing an action with a clear validation message is more useful than presenting an unexplained disabled control.
Hover and Touch Devices
Touchscreens often lack a persistent pointer that can hover before activation. Some browsers emulate hover on the first tap, retain it after a tap, or clear it only after another interaction. Hybrid laptops can switch between touch, trackpad, mouse, and keyboard during one session. Device width therefore does not reliably indicate hover capability.
Essential menus, tooltips, and controls must be available through tap and keyboard actions rather than hover alone. Test real touch behavior because desktop emulation may not reproduce every sticky-hover detail. Design the default state to be complete and use hover as optional feedback.
Hover and Pointer Media Features
CSS media features can detect the primary input's hover and pointer characteristics. A query such as @media (hover: hover) and (pointer: fine) can limit subtle pointer-specific enhancements to devices whose primary input can hover accurately. The any-hover and any-pointer features describe any available input rather than only the primary one.
These queries are useful for enhancements, but capability can change when peripherals connect, and users may choose different inputs. Core functionality should not depend on the query. Keep targets large enough for coarse pointers and ensure keyboard users receive equivalent focus feedback.
What Is :focus?
The :focus pseudo-class matches the element that currently has focus. Focus identifies where keyboard commands and many input events are directed. Users may move focus with Tab, Shift+Tab, arrow keys in composite widgets, access keys, scripts, or pointer interaction. Text inputs often receive focus when clicked or tapped, while browser behavior for buttons can differ by platform.
Only one element in a document focus context is normally focused at a time, though embedded documents and shadow trees add boundaries. Focus is a functional state, not just a visual one. Removing its visible indication can make an interface unusable for keyboard users.
Which Elements Can Receive Focus?
Native links with an href, buttons, inputs, selects, textareas, summaries, and other interactive controls are focusable according to platform rules. Elements can also become focusable through tabindex. A value of zero joins the natural tab sequence according to DOM order, while negative one allows programmatic focus without ordinary sequential navigation.
Positive tabindex values create a separate priority order and are usually harmful because they diverge from source order and become difficult to maintain. Adding tabindex to a div does not provide button semantics, keyboard activation, disabled behavior, or form integration. Prefer the native element that matches the action.
A Basic Focus Example
.field-input {
border: 1px solid #778899;
}
.field-input:focus {
border-color: #0b63a3;
box-shadow: 0 0 0 3px rgba(11, 99, 163, 0.22);
outline: 2px solid transparent;
}
The field receives a clear focus treatment without changing its dimensions. The transparent outline can help in forced-colors environments, although focus designs should be tested rather than assumed. Avoid styling only border color because it may be too subtle or indistinguishable for some users. A visible ring outside the control is usually easier to locate.
Why Focus Visibility Is Essential
A keyboard user cannot rely on a mouse pointer to identify the next action. The focus indicator answers “where am I?” as the user moves through links, form fields, menus, and buttons. If the indicator disappears, the underlying controls may remain operable but navigation becomes guesswork.
Do not write outline: none or outline: 0 without supplying an equally visible replacement. Check the indicator against every background, at browser zoom, in high-contrast or forced-colors modes, and around controls near viewport edges. Focus should not be obscured by sticky headers, overlays, or clipped containers.
:focus Versus :focus-visible
The :focus-visible pseudo-class matches focused elements when the browser determines that a visible focus indicator is appropriate. User-agent heuristics typically show it for keyboard navigation and for controls that require keyboard input, while suppressing an extra ring for some pointer-focused controls. The exact heuristic belongs to the browser and user preferences.
This allows authors to preserve keyboard visibility without necessarily displaying the same ring after every mouse click. A common pattern provides a basic focus fallback and a stronger focus-visible treatment, with selectors ordered and tested for target browsers. Never suppress focus globally based only on assumptions about input modality.
A Focus-Visible Pattern
.action:focus {
outline: 2px solid transparent;
}
.action:focus-visible {
outline: 3px solid #f2a900;
outline-offset: 3px;
}
The focus rule preserves a forced-colors-friendly outline boundary, while focus-visible provides the designed ring when browser heuristics call for it. Some projects use :focus:not(:focus-visible) to remove pointer-only decoration, but that approach must be verified across supported browsers and controls. The objective is reliable visibility, not eliminating rings for visual preference.
Using :focus-within
The :focus-within pseudo-class matches an element when it or any descendant is focused. It is useful for highlighting a complete form group, navigation region, search box, or card while an internal control receives input. For example, .field:focus-within can change the label color and group border without moving focus away from the input.
Focus-within complements rather than replaces the focused control's own indicator. A group highlight supplies context, while the direct focus style identifies the exact active element. Ensure both remain visible when components nest.
Focus in Forms
Form focus styles should connect the control to its label, instructions, and validation feedback. A group can use focus-within to emphasize its label, while the input uses focus-visible or focus for a clear ring. Placeholder text is not a label and disappears during entry, so it cannot carry the entire explanation.
Avoid changing field height, border width without box sizing, or surrounding margins on focus because those changes cause layout movement. If an error and focus state occur together, combine them deliberately so the error color does not erase the location indicator. Shape, ring, icon, and text can work together.
Programmatic Focus
Applications often move focus after opening a dialog, adding an error summary, changing a route, or completing an asynchronous action. CSS :focus responds automatically once script moves focus. The script should choose a meaningful destination, avoid unexpected movement during ordinary typing, and restore focus when a temporary interface closes.
Programmatic focus is not a substitute for logical DOM order. It should support a coherent workflow. Test whether focused content is visible, whether scrolling hides context, and whether focus lands on a semantic element capable of communicating the change.
What Is :active?
The :active pseudo-class usually matches an element during the activation gesture: after a primary pointer press begins and before it is released, or during the corresponding keyboard activation behavior defined by the element and browser. The state is brief. It provides immediate pressed feedback but does not represent an application state that persists after activation.
A button might move down by one pixel or darken while active. After release, the pseudo-class stops matching even if the button remains focused. For a persistent toggle, selected tab, expanded disclosure, or current navigation item, use native checked state, aria-pressed, aria-selected, aria-expanded, or an application class as appropriate.
A Basic Active Example
.action {
transform: translateY(0);
}
.action:active {
transform: translateY(1px);
box-shadow: none;
}
The small movement can reinforce the physical press metaphor. Keep the change subtle and stable; large scaling may cause blurred text, motion discomfort, or an apparent target shift. The action must still be handled by the button or link semantics. Active styling does not execute a command.
Active Is Not the Current Page
Developers sometimes use active as though it meant “the active navigation item.” CSS :active lasts only during activation. A link representing the current page should expose that state through markup, commonly aria-current="page", and can be styled with an attribute selector. A selected tab should use the semantics required by the tab pattern.
Naming a custom class .active is legal but can blur the distinction during discussion. More descriptive names such as .is-current, .is-selected, or an ARIA attribute make persistent state clearer and reduce accidental confusion with the pseudo-class.
Mouse and Keyboard Activation
Pointer activation commonly enters active on press and leaves it on release or cancellation. Keyboard behavior depends on the native control and key. Buttons activate through Space and Enter according to browser behavior; links primarily use Enter. The timing of active feedback may differ, so test real native elements with both input methods.
Custom controls frequently fail because scripts listen only for click-like pointer events or recreate keyboard behavior incompletely. Use button for actions and anchor for navigation. Native controls already coordinate focus, activation, disabled state, and assistive technology better than a styled generic element.
When States Overlap
A button can be hovered, focused, and active simultaneously. If each rule sets background color, selectors have equal pseudo-class weight when their remaining selectors are the same, so later source order wins. This can make a focus indication disappear during hover or cause active feedback to be overwritten.
Design states as layers of feedback. Let hover adjust background, focus-visible add an outline, and active adjust transform, so they do not unnecessarily compete. When the same property must change, define combined selectors such as .button:hover:active only when the visual requirement is clear. Test every overlap instead of previewing each state in isolation.
Recommended State Ordering
The traditional mnemonic “LoVe HAte” orders link selectors as :link, :visited, :hover, and :active so later equal-specificity states can override earlier ones. Modern components also need focus and focus-visible, and no single sequence replaces deliberate property design. A common approach places base and visited rules first, then hover, focus or focus-visible, and active, while ensuring the focus ring uses a property that later rules do not remove.
Ordering matters only when cascade conditions and properties conflict. Inspect actual specificity, layers, and source order rather than applying a mnemonic mechanically.
Specificity of Interaction Pseudo-Classes
Hover, focus, focus-visible, focus-within, and active each contribute one class-category count to selector specificity. Therefore button:hover has 0-1-1, while .button:hover has 0-2-0. The colon does not make a pseudo-class stronger than a normal class.
Chaining states adds each pseudo-class: .button:hover:active has three class-category counts in total, one from button and two from states. Keep selector weight predictable and use cascade layers or source order rather than unnecessarily repeating classes to force state rules.
Transitions Between States
Transitions can smooth color, background, border, shadow, opacity, and transform changes. Define the transition on the base selector so both entry and exit animate. Keep durations short for direct controls; slow feedback makes an interface feel unresponsive. Avoid transition: all, which can animate unintended layout or accessibility-related changes.
Motion should not be required to understand state. Respect prefers-reduced-motion when movement is substantial, and do not delay the visibility of a focus indicator. Immediate focus location is more important than decorative animation.
Hover-Only Menus and Tooltips
A submenu revealed only through hover excludes keyboard and touch users and may disappear while a user moves the pointer between trigger and content. A tooltip that exists only on hover similarly hides information from focus users. Pair hover with focus-visible or focus-within when the behavior is appropriate, and use a click-controlled disclosure when users must interact with the revealed content.
Tooltips should supplement a clear accessible name, not contain essential instructions unavailable elsewhere. Interactive popup patterns require state, dismissal, focus, and keyboard decisions that CSS hover alone cannot provide.
Navigation Patterns
Navigation links need distinguishable default, hover, focus, active-press, and current-page states. The current page is persistent and should use aria-current; focus identifies keyboard location; hover previews pointer targeting; active shows the brief press. These meanings should remain visually distinguishable without relying only on color.
Underline, border, background, font treatment, and focus outline can be combined carefully. Avoid changing dimensions across states, and keep focus from being clipped by overflow-hidden navigation containers. On small screens, provide tap-driven menu controls rather than hover-dependent dropdowns.
Button Patterns
A reliable button starts with a clear resting appearance and adequate contrast. Hover may deepen the background, focus-visible adds a strong external ring, active supplies restrained pressed feedback, and disabled uses native semantics with a design that remains legible. Loading is a persistent application state rather than an interaction pseudo-class and should prevent duplicate submission appropriately.
Icon-only buttons require accessible names and sufficiently large targets. Tooltips may explain unfamiliar icons but should appear through both hover and focus. The button itself must remain understandable and operable without the tooltip.
Card and List Item Patterns
Interactive list rows and cards should expose the actual focused link or button. Applying only .card:hover can make pointer behavior polished while leaving keyboard focus almost invisible. Use a direct focus-visible style on the control and optionally .card:focus-within to highlight the container.
Do not place multiple conflicting actions inside one stretched link area. When a card includes save, menu, and title-link actions, each control needs its own target and focus treatment. Hovering the container should not imply that every empty area performs the same action unless that behavior is semantically implemented.
Disabled and Read-Only States
Disabled is not the opposite of hover, focus, or active; it is a separate control state. Native disabled form controls are generally removed from sequential focus and do not activate, though pointer styling can vary. CSS should avoid hover and active effects that suggest availability. A not-disabled condition can constrain those rules.
Read-only fields can still receive focus and allow selection, so they may validly show focus styles. Distinguish read-only from disabled in both behavior and presentation. Do not reduce opacity so far that text becomes difficult to read.
High Contrast and Forced Colors
Custom state colors and shadows may be replaced or suppressed in forced-colors mode. Focus indicators based entirely on a subtle box shadow can disappear. System colors, outlines, borders, and forced-color-aware testing help preserve state visibility. Avoid disabling browser adjustments unless a carefully tested custom control requires it.
The objective is not identical rendering across modes but equivalent clarity. Users must still identify hover where available, current focus, pressed feedback, disabled state, and selected state. Test with operating-system high contrast or browser forced-colors emulation rather than assuming color substitutions.
Common Mistake: Removing the Outline
Global rules such as *:focus { outline: none; } erase essential keyboard feedback across the application. A designer may have intended to remove the default ring after mouse clicks, but the rule also affects keyboard and assistive-technology workflows.
Use focus-visible when appropriate and provide a tested replacement. If a component clips the ring, fix overflow, add safe outline offset, or place a contextual focus-within treatment on the container. Hiding the symptom does not solve the layout problem.
Common Mistake: Using Hover for Essential Content
Instructions, prices, actions, and validation information should not exist only in a hover state. Keyboard users may never trigger it, touch users may receive inconsistent behavior, magnification users can lose it while moving the pointer, and print or simplified views may omit it entirely.
Keep essential content present or reveal it through a semantic, operable control. Hover can preview or emphasize the same information. If a popup must remain open for interaction, manage it through an explicit expanded state rather than pointer position alone.
Common Mistake: Treating :active as Selected
Active ends when the activation gesture ends. Styling a tab, menu entry, filter, or toggle only with active causes the selected appearance to vanish immediately. Persistent state belongs in markup or application state, such as checked, aria-pressed, aria-selected, aria-current, or a meaningful class.
Use active as immediate physical feedback in addition to persistent semantics. This division makes styling understandable: the pseudo-class describes the gesture, while the attribute or class describes the resulting application state.
Common Mistake: Causing Layout Shift
Adding a border only on hover or focus can increase element dimensions and move surrounding content. Changing font weight can alter text width. Scaling a card can overlap neighbors or create clipped edges. State changes should usually preserve layout geometry.
Reserve border space in the base state with a transparent border, use outline for external focus indication, set stable dimensions, and apply transforms carefully. Test long translated labels and narrow containers because a small width change can create an unexpected line wrap.
Debugging Pseudo-Class Styles
Browser developer tools can force hover, active, focus, focus-visible, and other states for inspection. Select the element, enable the desired state, and review matched rules and overridden declarations. Forced state is useful for visual debugging but should be followed by real mouse, touch, and keyboard interaction because browser heuristics and event timing may differ.
If focus does not appear, confirm that the element is focusable and actually focused. If active seems absent, slow the interaction through developer tools or choose a more visible temporary style. Check cascade layers, specificity, source order, disabled state, overlays, pointer-events, and media-query conditions.
Testing Across Input Methods
Test each interactive component with mouse, trackpad, keyboard, touch, and assistive technology appropriate to the project. Navigate forward and backward by keyboard, activate controls using expected keys, zoom the page, switch orientation, and connect a mouse to a touch-capable device when possible. Confirm that focus never becomes lost or hidden.
Automated checks can detect some missing accessible names, contrast failures, and focus-order issues, but they cannot judge every state transition. Visual regression tests can capture hover and focus variants. Manual workflow testing remains necessary for timing, discoverability, sticky hover, and motion comfort.
Best Practices
Design a complete resting state first, then add hover as an optional pointer enhancement, focus-visible as strong keyboard location feedback, and active as brief press confirmation. Keep persistent selected or expanded state in semantic attributes or application classes. Avoid hover-only actions, preserve native control semantics, and maintain stable dimensions across states.
Use focus-within for useful group context without replacing the direct focus ring. Coordinate overlapping properties and cascade order. Respect reduced motion, high contrast, touch input, zoom, and translated content. Test the same workflow through every supported input method rather than evaluating isolated screenshots.
Interview Questions and Answers
What is a pseudo-class? It matches an element in a defined state or structural condition. What does hover represent? Pointing-device designation, usually the pointer being over the element. What does focus represent? The element currently receiving input. What does active represent? The brief activation period while a control is being pressed. How is focus-visible different? It uses browser heuristics to show focus indication when users are likely to need it.
Can states overlap? Yes; a button can be hovered, focused, and active simultaneously. Do these pseudo-classes have ID specificity? No, each contributes class-category weight. Should outline be removed? Only when replaced by an equally visible, tested focus indicator. Does active mean selected? No; persistent state requires appropriate semantics or application state.
Practical Review Checklist
- Keep interactive elements recognizable before hover or focus.
- Provide a visible keyboard focus indicator on every operable control.
- Use
:focus-visiblethoughtfully without suppressing necessary focus feedback. - Do not place essential information or actions behind hover alone.
- Use
:activefor temporary press feedback, not persistent selection. - Keep state changes from resizing or shifting surrounding content.
- Ensure overlapping hover, focus, active, error, disabled, and selected styles remain clear.
- Respect touch, coarse pointers, reduced motion, high contrast, and zoom.
- Use native links, buttons, and form controls whenever they match the behavior.
- Test with mouse, keyboard, touch, and relevant assistive technology.
Key Takeaway
Hover, focus, and active describe different moments in interaction. Hover is pointer-oriented and may be unavailable. Focus is the functional input location and must remain visible. Active is brief press feedback and disappears after activation. Treating them as interchangeable visual effects leads to inaccessible controls and confusing persistent-state behavior.
Build from semantic HTML, provide a complete default state, and let each pseudo-class add the feedback suited to its meaning. Coordinate overlapping styles through the cascade, preserve layout stability, and test real input methods. When state design communicates availability, location, and response without depending on one device, the interface becomes clearer for everyone.