Home / Snippets / Animation /

Background fill

Sweep-fill hover effect from left, right, or center using scaleX on a pseudo-element.

Widely Supported
animationno-js

Quick implementation

/* HTML: <a class="bgfill-link"><span>Link text</span></a> */

.bgfill-link {
  position: relative;
  display: inline-block;
  padding: 0.75rem 2rem;
  border: 2px solid oklch(0.72 0.19 265);
  border-radius: 0.5rem;
  color: oklch(0.72 0.19 265);
  font-weight: 600;
  overflow: hidden;
  transition: color 0.3s ease;
}

.bgfill-link::before {
  content: '';
  position: absolute;
  inset: 0;
  background: oklch(0.72 0.19 265);
  transform: scaleX(0);
  transform-origin: left;   /* left | right | center */
  transition: transform 0.3s ease;
  z-index: 0;
}

.bgfill-link:hover::before,
.bgfill-link:focus-visible::before {
  transform: scaleX(1);
}

.bgfill-link:hover,
.bgfill-link:focus-visible {
  color: oklch(1 0 0);
}

/* Text must sit above the fill */
.bgfill-link span { position: relative; z-index: 1; }

@media (prefers-reduced-motion: reduce) {
  .bgfill-link, .bgfill-link::before { transition: none; }
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer building interactive hover effects.

Goal: A background fill hover animation using a ::before pseudo-element that scales from 0 to full width — no JavaScript.

Technical constraints:
- Use scaleX(0) → scaleX(1) on ::before, not width animation (transform is GPU-composited).
- transform-origin controls direction: left, right, or center.
- Text must sit above the fill with position: relative and z-index: 1.
- Color transitions from border color to white on hover using oklch().
- Include :focus-visible so keyboard users see the fill.
- Use oklch() for all colors, not hex or rgba().
- Wrap transitions in @media (prefers-reduced-motion: reduce).

Framework variant (pick one):
A) Vanilla HTML + CSS only.
B) React component — accept children, fillDirection (left | right | center), color (oklch string), and as (a | button) props.

Edge cases to handle:
- Multi-line text: fill should cover the full element, not just one line.
- RTL languages: left origin should become right automatically (use logical properties if possible).
- Ensure the fill doesn't extend outside rounded corners (overflow: hidden).
- Disabled state: no fill, muted colors.

Return HTML + CSS.

Why this matters in 2026

The background fill is one of the most popular button/link hover effects on the web. The old approach — animating width or background-size — triggers layout recalculation on every frame. Using scaleX on a pseudo-element keeps the animation on the GPU compositor, delivering smooth 60fps even on mobile. It's a one-line origin change to switch between left, right, and center fill directions.

The logic

The ::before pseudo-element covers the full area with inset: 0 and starts at scaleX(0). The transform-origin property controls which edge the fill grows from. On hover, scaleX(1) makes it fill the entire element. The text sits above via z-index: 1, and the element's overflow: hidden clips the fill at the border radius. The color transition happens simultaneously — from the accent color to white — creating a seamless swap.

Accessibility & performance

The :focus-visible selector triggers the same fill for keyboard navigation. The @media (prefers-reduced-motion: reduce) query removes transitions so the fill appears instantly. Using transform: scaleX() instead of width or background-size means the animation runs entirely on the compositor — no layout, no paint, just compositing. This is the same optimization principle browsers use for CSS animations on transform and opacity.