slop audit
Blog post 34 / Accessibility and interaction

CSS hover states: test buttons and links before launch

Use CSS hover states to give controls clear pointer feedback, preserve keyboard focus, and test deployed buttons and links with Playwright before launch.

By Slop Audit · 8 min read

CSS hover states give users pointer feedback on links and buttons, but a color change is only one part of interaction testing. Before launch, check the default, hover, keyboard-focus, and touch behavior on real controls. Keep important information available without a pointer, then test the deployed page so the result reflects the CSS and scripts visitors will receive.

CSS hover states checklist

Use this short pass for navigation links, primary actions, icon buttons, and controls inside menus or dialogs:

  1. Add a relevant :hover style to controls that support pointer interaction.
  2. Keep the base state usable on touchscreens and devices without hover.
  3. Provide a separate visible keyboard-focus state.
  4. Test any tooltip, menu, or other content that appears on hover or focus.
  5. Check that each state remains clear against its actual background.
  6. Add a browser test for the important action and repeat it after CSS changes.

The keyboard accessibility testing checklist covers tab order, focus visibility, and keyboard activation. This guide focuses on pointer feedback and the CSS state changes that are easy to miss in a screenshot of the default page.

Add a CSS hover state with :hover

The CSS :hover pseudo-class applies when a pointing device designates an element. Use it to make an existing link or button feel responsive, not to reveal the only route to important information. MDN documents the `:hover` pseudo-class and the `hover` media feature, which checks whether the primary input mechanism can conveniently hover.

button.primary {
  background-color: #164e63;
  color: #fff;
}

@media (hover: hover) {
  button.primary:hover {
    background-color: #0f354a;
  }
}

button.primary:focus-visible {
  outline: 3px solid #005fcc;
  outline-offset: 3px;
}

This example changes the button background when a hover-capable primary input is available and keeps keyboard focus styling independent. The color values are examples; review both foreground and background in each state. The color contrast accessibility guide explains how to check rendered colors rather than relying on palette names.

On a hybrid device, the primary input may be touch even though a mouse or trackpad is also available. The `any-hover` media feature checks whether any available input can hover. Keep the control usable without hover in either case: a tap cannot depend on a pointer staying over the element.

Use native links and buttons so browser interaction behavior remains predictable. Keep link text, button names, and destinations available in the default state. A hover color can reinforce that an item is interactive, but it should not be the only cue that tells a visitor what an action does.

Pair hover feedback with keyboard focus

Mouse users can move a pointer over a control; keyboard users need to see which control currently has focus. Do not remove the browser outline unless a replacement remains visible on every surface where the control appears. WCAG 2.2 Success Criterion 2.4.7, Focus Visible, requires a mode of operation where keyboard focus is visible.

Use :focus-visible for a focus treatment that the browser shows when it considers a visible indicator appropriate. Do not assume that a hover rule also covers keyboard focus. A stable release check should move through the page with Tab, confirm the active control is visible, and activate the primary task with the keyboard. Follow the keyboard guide for a fuller test of dialogs, widgets, and focus order.

If a hover or focus state reveals extra content, such as a tooltip or submenu, test that behavior separately from a color or border change. WCAG 2.2 Content on Hover or Focus applies to additional content that appears on hover or focus when the criterion's conditions are met; that content needs to be dismissible, hoverable, and persistent. A pointer-only disclosure can strand keyboard and touch users even when the visual state looks polished.

Test CSS hover states with Playwright

Test the public preview or production URL in a browser project with mouse input. Playwright's `Locator.hover()` moves the pointer over a matched element, and its `toHaveCSS()` assertion can confirm the rendered style:

import { expect, test } from "@playwright/test";

test("primary action has a hover state", async ({ page }) => {
  await page.goto("https://preview.example.com/");

  const action = page.getByRole("button", { name: "Run audit" });
  await expect(action).toBeVisible();
  await action.hover();

  await expect(action).toHaveCSS("background-color", "rgb(15, 53, 74)");
});

Use an accessible role and name so the test finds the action a user would identify, not a brittle CSS selector. Assert a deliberate style value or a stable state attribute owned by the component. If the design token changes, update the expectation with the design review instead of weakening the test to accept any computed-style difference.

This test confirms one pointer state. It does not establish that keyboard focus is visible, that the state has adequate contrast, or that the mobile task works by touch. Test those paths separately. The mobile website testing guide and touch target size guide cover narrow layouts and tap behavior.

Check hover and keyboard states on the deployed page

During release QA, check the important actions after the page has loaded and after its menus, consent notices, and dialogs have opened. Compare the resting and hovered state. Then use Tab to check focus and use a touch device or touch-capable test context to complete the same task without hover. If a menu or tooltip appears, test how a visitor can keep it open, dismiss it, and reach its contents.

Repeat the checks after changing shared button styles, color tokens, navigation components, or overlays. Add stable assertions to your website regression testing suite so a later design update does not quietly remove interaction feedback.

Check hover and focus feedback with Slop Audit

Run Slop Audit against a public preview or production URL to collect rendered interaction evidence alongside crawl, accessibility, and mobile findings. Its Browser Run samples up to five visible links, buttons, and form controls, then compares a bounded set of computed styles at rest, on hover, and on focus. The report records how many sampled controls changed at least one of those styles.

Treat that count as a prompt to inspect the actual controls. Slop Audit does not judge whether a style change is noticeable, check contrast for each state, test every control, evaluate every tooltip or menu, or certify WCAG conformance. Use the evidence to find a page that may need review, then complete the Playwright, keyboard, and touch checks for your critical actions.

CSS hover states release checklist

  • Important buttons and links have a relevant pointer-hover treatment.
  • The default control remains understandable and usable without hover.
  • Keyboard users can see focus as they move through the page.
  • Hover- or focus-triggered content remains reachable and dismissible.
  • Colors and outlines were reviewed against the rendered backgrounds.
  • The main action passed a deployed Playwright hover test.
  • The mobile task works with touch, and the control remains large enough to activate.
  • CSS changes are covered by a repeatable regression check.

Record the route, browser input, control name, and observed state with the release note. “The Run audit button stays readable on focus and changes background on hover” gives the next reviewer evidence they can repeat.