26 CSS Link Effects15 / 26

Pure CSSMIT licensed

CSS Sweep / Wipe Text Reveal Hover

Clip-path is the cleanest reveal machine in CSS: a recolored copy of the label wipes across the original, edge razor-sharp, no gradients, no measuring. Ships as a straight left-to-right recolor, a slanted-edge diagonal curtain, and the show-off variant — the label wipes into a completely different word ('Pricing' becomes 'From $0/mo').

Published Updated

Live Demo
Try it

The code

<section class="cle-15" aria-label="Sweep reveal hover demo">
  <div class="cle-15__stage">
    <div class="cle-15__var"><a href="#a" class="cle-15__wipe" data-text="Manifesto">Manifesto</a><small class="cle-15__cap">A · straight recolor wipe</small></div>
    <div class="cle-15__var"><a href="#b" class="cle-15__blade" data-text="Collections">Collections</a><small class="cle-15__cap">B · slanted curtain</small></div>
    <div class="cle-15__var"><a href="#c" class="cle-15__swap"><span class="cle-15__base">Pricing</span><span class="cle-15__alt" aria-hidden="true">From $0/mo</span></a><small class="cle-15__cap">C · wipes to a new word</small></div>
  </div>
</section>
.cle-15,
.cle-15 *,
.cle-15 *::before,
.cle-15 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cle-15 {
  width: 100%;
  min-height: 100vh;
  --paper: oklch(0.96 0.01 85);
  --ink: oklch(0.24 0.02 60);
  --accent: oklch(0.68 0.19 45);
  font-family: 'Segoe UI',system-ui,sans-serif;
}

.cle-15__stage {
  width: 100%;
  height: 100vh;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 44px;
  background: linear-gradient(165deg,oklch(0.3 0.025 55),oklch(0.21 0.02 50));
  padding: 44px 32px;
}

.cle-15__var {
  display: grid;
  gap: 13px;
  justify-items: center;
}

.cle-15__cap {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: oklch(0.6 0.04 55);
}

.cle-15 a {
  position: relative;
  color: var(--paper);
  text-decoration: none;
  font-size: clamp(30px,4.6vw,42px);
  font-weight: 800;
  letter-spacing: -.02em;
}

.cle-15 a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 6px;
  border-radius: 4px;
}

.cle-15__wipe::after,
.cle-15__blade::after {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  color: var(--accent);
  transition: clip-path .45s cubic-bezier(.3,.8,.2,1);
}

.cle-15__wipe::after {
  clip-path: inset(0 100% 0 0);
}

.cle-15__wipe:hover::after,
.cle-15__wipe:focus-visible::after {
  clip-path: inset(0 0 0 0);
}

.cle-15__blade::after {
  clip-path: polygon(-20% 0,-5% 0,-20% 100%,-35% 100%);
  transition-duration: .5s;
}

.cle-15__blade:hover::after,
.cle-15__blade:focus-visible::after {
  clip-path: polygon(-20% 0,115% 0,100% 100%,-35% 100%);
}

.cle-15__swap {
  display: inline-grid;
  text-align: center;
}

.cle-15__swap > span {
  grid-area: 1/1;
  white-space: nowrap;
}

.cle-15__alt {
  color: oklch(0.2 0.02 50);
  background: var(--accent);
  border-radius: 10px;
  padding: 0 .35em;
  clip-path: inset(0 100% 0 0 round 10px);
  transition: clip-path .45s cubic-bezier(.3,.8,.2,1);
}

.cle-15__swap:hover .cle-15__alt,
.cle-15__swap:focus-visible .cle-15__alt {
  clip-path: inset(0 0 0 0 round 10px);
}

@media (prefers-reduced-motion: reduce) {
  .cle-15 * {
    transition-duration: .01s !important;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Link Effect from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Sweep / Wipe Text Reveal Hover
Source: https://codefronts.com/motion/css-link-effects/reveal-sweep/

Clip-path is the cleanest reveal machine in CSS: a recolored copy of the label wipes across the original, edge razor-sharp, no gradients, no measuring. Ships as a straight left-to-right recolor, a slanted-edge diagonal curtain, and the show-off variant — the label wipes into a completely different word ('Pricing' becomes 'From $0/mo').
## HTML
```html
<section class="cle-15" aria-label="Sweep reveal hover demo">
  <div class="cle-15__stage">
    <div class="cle-15__var"><a href="#a" class="cle-15__wipe" data-text="Manifesto">Manifesto</a><small class="cle-15__cap">A · straight recolor wipe</small></div>
    <div class="cle-15__var"><a href="#b" class="cle-15__blade" data-text="Collections">Collections</a><small class="cle-15__cap">B · slanted curtain</small></div>
    <div class="cle-15__var"><a href="#c" class="cle-15__swap"><span class="cle-15__base">Pricing</span><span class="cle-15__alt" aria-hidden="true">From $0/mo</span></a><small class="cle-15__cap">C · wipes to a new word</small></div>
  </div>
</section>
```
## CSS
```css
.cle-15,
.cle-15 *,
.cle-15 *::before,
.cle-15 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cle-15 {
  width: 100%;
  min-height: 100vh;
  --paper: oklch(0.96 0.01 85);
  --ink: oklch(0.24 0.02 60);
  --accent: oklch(0.68 0.19 45);
  font-family: 'Segoe UI',system-ui,sans-serif;
}

.cle-15__stage {
  width: 100%;
  height: 100vh;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 44px;
  background: linear-gradient(165deg,oklch(0.3 0.025 55),oklch(0.21 0.02 50));
  padding: 44px 32px;
}

.cle-15__var {
  display: grid;
  gap: 13px;
  justify-items: center;
}

.cle-15__cap {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: oklch(0.6 0.04 55);
}

.cle-15 a {
  position: relative;
  color: var(--paper);
  text-decoration: none;
  font-size: clamp(30px,4.6vw,42px);
  font-weight: 800;
  letter-spacing: -.02em;
}

.cle-15 a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 6px;
  border-radius: 4px;
}

.cle-15__wipe::after,
.cle-15__blade::after {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  color: var(--accent);
  transition: clip-path .45s cubic-bezier(.3,.8,.2,1);
}

.cle-15__wipe::after {
  clip-path: inset(0 100% 0 0);
}

.cle-15__wipe:hover::after,
.cle-15__wipe:focus-visible::after {
  clip-path: inset(0 0 0 0);
}

.cle-15__blade::after {
  clip-path: polygon(-20% 0,-5% 0,-20% 100%,-35% 100%);
  transition-duration: .5s;
}

.cle-15__blade:hover::after,
.cle-15__blade:focus-visible::after {
  clip-path: polygon(-20% 0,115% 0,100% 100%,-35% 100%);
}

.cle-15__swap {
  display: inline-grid;
  text-align: center;
}

.cle-15__swap > span {
  grid-area: 1/1;
  white-space: nowrap;
}

.cle-15__alt {
  color: oklch(0.2 0.02 50);
  background: var(--accent);
  border-radius: 10px;
  padding: 0 .35em;
  clip-path: inset(0 100% 0 0 round 10px);
  transition: clip-path .45s cubic-bezier(.3,.8,.2,1);
}

.cle-15__swap:hover .cle-15__alt,
.cle-15__swap:focus-visible .cle-15__alt {
  clip-path: inset(0 0 0 0 round 10px);
}

@media (prefers-reduced-motion: reduce) {
  .cle-15 * {
    transition-duration: .01s !important;
  }
}
```

How this works

Stack a styled duplicate of the label on the original and animate the duplicate's clipping rectangle:

.wipe{ position:relative; }
.wipe::after{
  content: attr(data-text);   /* the duplicate */
  position: absolute; inset: 0; color: var(--accent);
  clip-path: inset(0 100% 0 0);        /* fully clipped from the right */
  transition: clip-path .45s cubic-bezier(.3,.8,.2,1);
}
.wipe:hover::after{ clip-path: inset(0 0 0 0); }

inset() interpolates smoothly, the text underneath never moves, and because the duplicate comes from data-text, screen readers see one link with one name. B swaps the rectangle for a four-point polygon() whose leading edge is skewed 15% — the same interpolation rules apply because the point count doesn't change between states. C stacks a different string (from data-swap) with its own background pill; both layers sit on one inline-grid cell so the box sizes itself to the longer of the two.

Make it yours

  • Direction: clip from the left with inset(0 0 0 100%), top-down with inset(0 0 100% 0).
  • B's slant: move the two mid points (±15%) further apart for a more dramatic blade.
  • C's swap copy: pure HTML — data-swap="From $0/mo"; style its pill via the ::after-free span rules.
  • Layer anything on the duplicate: gradient text, shadows, even a different font-weight (keep widths similar or the edge telegraphs).

Gotchas — read before shipping

  • The duplicate must be aria-hidden or attr()-generated — never a second real text node, or screen readers announce the link twice.
  • Duplicated-text tricks assume one line; a wrapped label and its absolute copy wrap at different widths. Keep it on headings/nav, not prose.
  • Polygon states must keep the same number of points — add a vertex to one state and the transition snaps instead of sweeping.

Browser support

ChromeSafariFirefoxEdge
111+16.2+113+111+

clip-path basic shapes + transitions are Baseline everywhere (Safari unprefixed since 13.1). oklch sets the stated floor.

Search CodeFronts

Loading…