How to Handle Hover States on Touch Devices: CSS Media Query Solution

Wrap all hover effects in a @media (hover: hover) and (pointer: fine) query to prevent false hover activations on touchscreens while preserving desktop interactions.

The emilkowalski/skills repository documents a robust pattern for handling hover states on touch devices that eliminates spurious animations while keeping visual feedback intact for mouse users. Touch-first devices trigger :hover pseudo-classes on tap, which can cause unexpected layout shifts or lingering hover styles if not properly gated.

Why Touch Devices Trigger False Hover States

On smartphones and tablets, tapping an element briefly activates its :hover state even though the user cannot actually hover. This creates a jarring experience where buttons scale up, colors change, or menus appear on touch, then stick until the user taps elsewhere. Traditional JavaScript detection methods often fail because many modern laptops support both touch and mouse inputs simultaneously.

The CSS Solution: Media Query Capability Detection

The Instagit style guide solves this by gating every hover-related rule behind a media query that only matches devices capable of true hover interactions. As implemented in skills/review-animations/STANDARDS.md, the accessibility section shows the media query that "gates hover motion — touch fires false hovers on tap"【skills/review-animations/STANDARDS.md#L166-L168】:

@media (hover: hover) and (pointer: fine) {
  .element:hover { transform: scale(1.05); }
}

Understanding the Media Query Parameters

This approach uses two specific media features to accurately target desktop interactions:

  • hover: hover → The user agent reports that hover interactions are present and available
  • pointer: fine → The pointing device is precise (e.g., a mouse), not a coarse touchscreen

When both conditions evaluate to true, the rule executes. Otherwise, the browser ignores the hover style completely, preventing the false-positive hover animations that plague touch-only devices.

Implementation Examples from the Source Code

The pattern is documented consistently across the repository, with skills/emil-design-eng/SKILL.md providing additional context under "Touch device hover states"【skills/emil-design-eng/SKILL.md#L545-L553】.

Basic CSS Guard Pattern

Use this foundation to ensure hover effects only apply to supported hardware:

/* styles.css */
@media (hover: hover) and (pointer: fine) {
  .btn:hover {
    background-color: var(--color-primary-hover);
    transform: scale(1.04);
    transition: transform 120ms ease-out;
  }
}

React Component Integration

No JavaScript detection is required when using this CSS approach. Import your guarded styles normally:

import './Button.css';

export const Button = ({ children }: { children: React.ReactNode }) => (
  <button className="btn">{children}</button>
);

Progressive Enhancement with Base Styles

If you need touch devices to receive a non-animated fallback state, declare base rules outside the media query:

/* Base style – always applied */
.btn {
  background-color: var(--color-primary);
  transition: background-color 120ms ease-out;
}

/* Hover only on devices that support it */
@media (hover: hover) and (pointer: fine) {
  .btn:hover {
    background-color: var(--color-primary-hover);
  }
}

Summary

  • Gate hover effects behind @media (hover: hover) and (pointer: fine) to prevent touch devices from triggering false hover states on tap
  • Reference the source in skills/review-animations/STANDARDS.md and skills/emil-design-eng/SKILL.md for the canonical implementation pattern
  • Leverage CSS media queries instead of JavaScript user-agent sniffing for more reliable device capability detection
  • Provide base styles outside the media query to ensure touch users still receive visual feedback through other means

Frequently Asked Questions

Why do touch devices trigger hover states on tap?

Touchscreens simulate a hover event on the first tap to support websites that rely on hover for dropdown menus or tooltips. The browser fires the hover state at the touch location, but since the finger immediately covers the element, this often creates a sticky hover effect that remains until the user taps elsewhere. The @media (hover: hover) query detects whether the primary input mechanism can truly hover over elements, filtering out these touch-generated false positives.

What is the difference between hover: hover and pointer: fine?

The hover: hover media feature reports whether the primary input mechanism allows the user to hover over elements, returning true for mice and false for basic touchscreens. The pointer: fine media feature describes the accuracy of the primary pointing device, distinguishing between precise inputs like mice (fine) and imprecise inputs like fingers (coarse). Using both together ensures you target only mouse-driven desktop environments where hover enhances usability rather than confuses it.

Do I need JavaScript to detect touch devices for hover states?

No. The CSS Media Query Level 4 specification provides the hover and pointer features that accurately query device capabilities without JavaScript. According to the source code analysis, this CSS-only approach eliminates the need for user-agent sniffing or ontouchstart detection scripts that often break on hybrid devices like touchscreen laptops. The media query adapts automatically when users switch between touch and mouse inputs on convertible laptops.

Should I use pointer: coarse instead of pointer: fine?

Use pointer: fine when you want to enable hover effects specifically for precise pointing devices. If you use pointer: coarse, you would target touchscreen inputs, which is the opposite of what you want for hover effects. To exclude touch devices from hover animations, you must verify that pointer: fine exists (indicating mouse precision) combined with hover: hover (indicating hover capability). Targeting pointer: coarse would apply styles specifically to touchscreens, which typically cannot use hover interactions meaningfully.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →