30 CSS Text Animations17 / 30

Pure CSSMIT licensed

CSS Text Fill on Hover Left to Right

Hollow display type that floods with ink on hover: a two-layer clip-path sweep where a gradient clone wipes across outlined caps, and the single-element trick — one background-size declaration filling stroked text with zero extra markup.

Published Updated

Live Demo
Try it

The code

<div class="cat-17-group">
<link href="https://fonts.googleapis.com/css2?family=Archivo:ital,wght@0,500..900;1,500..900&display=swap" rel="stylesheet">
<section class="cat-17a" aria-label="Outlined text fill sweep on hover">
  <div class="cat-17a__stage">
    <h1 class="cat-17a__word" tabindex="0">PORTFOLIO<span class="cat-17a__fill" aria-hidden="true">PORTFOLIO</span></h1>
    <p class="cat-17a__sub" aria-hidden="true">hover — a filled clone un-clips left → right</p>
  </div>
</section>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap" rel="stylesheet">
<section class="cat-17b" aria-label="Single element text fill on hover">
  <nav class="cat-17b__list" aria-label="Index">
    <a class="cat-17b__item" href="#">Work</a>
    <a class="cat-17b__item" href="#">About</a>
    <a class="cat-17b__item" href="#">Contact</a>
  </nav>
  <p class="cat-17b__sub" aria-hidden="true">one element · background-size 0% → 100% clipped to text</p>
</section>
</div>
.cat-17-group {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  width: 100%;
}

.cat-17-group > section {
  flex: 1 1 480px;
  min-width: min(100%,360px);
}

.cat-17a,
.cat-17a *,
.cat-17a *::before,
.cat-17a *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cat-17a {
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: #101318;
}

.cat-17a__stage {
  text-align: center;
}

.cat-17a__word {
  position: relative;
  display: inline-block;
  font-family: 'Archivo','Segoe UI',system-ui,sans-serif;
  font-size: clamp(38px,7.8vw,100px);
  font-weight: 900;
  letter-spacing: .01em;
  color: transparent;
  -webkit-text-stroke: 1.5px rgba(235,242,255,.85);
  cursor: pointer;
}

.cat-17a__fill {
  position: absolute;
  inset: 0;
  color: transparent;
  -webkit-text-stroke: 0;
  background: linear-gradient(92deg,oklch(0.8 0.17 200),oklch(0.75 0.19 300));
  -webkit-background-clip: text;
  background-clip: text;
  clip-path: inset(-5% 100% -5% 0);
  transition: clip-path .55s cubic-bezier(.2,.8,.2,1);
  pointer-events: none;
}

.cat-17a__word:hover .cat-17a__fill,
.cat-17a__word:focus-visible .cat-17a__fill {
  clip-path: inset(-5% 0% -5% 0);
}

.cat-17a__word:focus-visible {
  outline: 2px solid oklch(0.8 0.17 200);
  outline-offset: 10px;
}

.cat-17a__sub {
  margin-top: 22px;
  font-family: ui-monospace,Menlo,monospace;
  font-size: 11px;
  letter-spacing: .16em;
  color: rgba(170,190,225,.4);
}

@media (prefers-reduced-motion: reduce) {
  .cat-17a__fill {
    transition: clip-path .1s;
  }
}

.cat-17b,
.cat-17b *,
.cat-17b *::before,
.cat-17b *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cat-17b {
  --fill: oklch(0.87 0.17 95);
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 30px;
  padding: 48px 24px;
  background: #15130e;
}

.cat-17b__list {
  display: grid;
  gap: 4px;
  text-align: center;
  font-family: 'Space Grotesk','Segoe UI',system-ui,sans-serif;
}

.cat-17b__item {
  display: block;
  font-size: clamp(44px,8vw,92px);
  font-weight: 700;
  line-height: 1.14;
  letter-spacing: -.01em;
  text-decoration: none;
  color: transparent;
  -webkit-text-stroke: 1.25px rgba(240,232,210,.55);
  background: linear-gradient(90deg,var(--fill),var(--fill)) no-repeat 0 0/0% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  transition: background-size .5s cubic-bezier(.2,.8,.2,1);
}

.cat-17b__item:hover,
.cat-17b__item:focus-visible {
  background-size: 100% 100%;
}

.cat-17b__item:focus-visible {
  outline: 2px solid var(--fill);
  outline-offset: 6px;
}

.cat-17b__sub {
  font-family: ui-monospace,Menlo,monospace;
  font-size: 11px;
  letter-spacing: .16em;
  color: rgba(240,232,210,.35);
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .cat-17b__item {
    transition: none;
  }
}
/* No JavaScript — a gradient-filled aria-hidden clone sits over stroked transparent text; hover transitions its clip-path window from inset(0 100% 0 0) to fully open. */

/* No JavaScript — stroked transparent text carries its own gradient background clipped to the glyphs; hover tweens background-size 0%→100% so the ink pours through. */
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 Text Animation 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 Text Fill on Hover Left to Right
Source: https://codefronts.com/motion/css-text-animations/css-mask-wipe-text-reveal-animation/

Hollow display type that floods with ink on hover: a two-layer clip-path sweep where a gradient clone wipes across outlined caps, and the single-element trick — one background-size declaration filling stroked text with zero extra markup.
## HTML
```html
<div class="cat-17-group">
<link href="https://fonts.googleapis.com/css2?family=Archivo:ital,wght@0,500..900;1,500..900&display=swap" rel="stylesheet">
<section class="cat-17a" aria-label="Outlined text fill sweep on hover">
  <div class="cat-17a__stage">
    <h1 class="cat-17a__word" tabindex="0">PORTFOLIO<span class="cat-17a__fill" aria-hidden="true">PORTFOLIO</span></h1>
    <p class="cat-17a__sub" aria-hidden="true">hover — a filled clone un-clips left → right</p>
  </div>
</section>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap" rel="stylesheet">
<section class="cat-17b" aria-label="Single element text fill on hover">
  <nav class="cat-17b__list" aria-label="Index">
    <a class="cat-17b__item" href="#">Work</a>
    <a class="cat-17b__item" href="#">About</a>
    <a class="cat-17b__item" href="#">Contact</a>
  </nav>
  <p class="cat-17b__sub" aria-hidden="true">one element · background-size 0% → 100% clipped to text</p>
</section>
</div>
```
## CSS
```css
.cat-17-group {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  width: 100%;
}

.cat-17-group > section {
  flex: 1 1 480px;
  min-width: min(100%,360px);
}

.cat-17a,
.cat-17a *,
.cat-17a *::before,
.cat-17a *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cat-17a {
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: #101318;
}

.cat-17a__stage {
  text-align: center;
}

.cat-17a__word {
  position: relative;
  display: inline-block;
  font-family: 'Archivo','Segoe UI',system-ui,sans-serif;
  font-size: clamp(38px,7.8vw,100px);
  font-weight: 900;
  letter-spacing: .01em;
  color: transparent;
  -webkit-text-stroke: 1.5px rgba(235,242,255,.85);
  cursor: pointer;
}

.cat-17a__fill {
  position: absolute;
  inset: 0;
  color: transparent;
  -webkit-text-stroke: 0;
  background: linear-gradient(92deg,oklch(0.8 0.17 200),oklch(0.75 0.19 300));
  -webkit-background-clip: text;
  background-clip: text;
  clip-path: inset(-5% 100% -5% 0);
  transition: clip-path .55s cubic-bezier(.2,.8,.2,1);
  pointer-events: none;
}

.cat-17a__word:hover .cat-17a__fill,
.cat-17a__word:focus-visible .cat-17a__fill {
  clip-path: inset(-5% 0% -5% 0);
}

.cat-17a__word:focus-visible {
  outline: 2px solid oklch(0.8 0.17 200);
  outline-offset: 10px;
}

.cat-17a__sub {
  margin-top: 22px;
  font-family: ui-monospace,Menlo,monospace;
  font-size: 11px;
  letter-spacing: .16em;
  color: rgba(170,190,225,.4);
}

@media (prefers-reduced-motion: reduce) {
  .cat-17a__fill {
    transition: clip-path .1s;
  }
}

.cat-17b,
.cat-17b *,
.cat-17b *::before,
.cat-17b *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cat-17b {
  --fill: oklch(0.87 0.17 95);
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 30px;
  padding: 48px 24px;
  background: #15130e;
}

.cat-17b__list {
  display: grid;
  gap: 4px;
  text-align: center;
  font-family: 'Space Grotesk','Segoe UI',system-ui,sans-serif;
}

.cat-17b__item {
  display: block;
  font-size: clamp(44px,8vw,92px);
  font-weight: 700;
  line-height: 1.14;
  letter-spacing: -.01em;
  text-decoration: none;
  color: transparent;
  -webkit-text-stroke: 1.25px rgba(240,232,210,.55);
  background: linear-gradient(90deg,var(--fill),var(--fill)) no-repeat 0 0/0% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  transition: background-size .5s cubic-bezier(.2,.8,.2,1);
}

.cat-17b__item:hover,
.cat-17b__item:focus-visible {
  background-size: 100% 100%;
}

.cat-17b__item:focus-visible {
  outline: 2px solid var(--fill);
  outline-offset: 6px;
}

.cat-17b__sub {
  font-family: ui-monospace,Menlo,monospace;
  font-size: 11px;
  letter-spacing: .16em;
  color: rgba(240,232,210,.35);
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .cat-17b__item {
    transition: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — a gradient-filled aria-hidden clone sits over stroked transparent text; hover transitions its clip-path window from inset(0 100% 0 0) to fully open. */

/* No JavaScript — stroked transparent text carries its own gradient background clipped to the glyphs; hover tweens background-size 0%→100% so the ink pours through. */
```

How this works

The layered version stacks an aria-hidden clone over outlined text (-webkit-text-stroke + transparent fill). The clone is fully filled but starts clipped away; hover slides the clip window open left-to-right:

.fill{clip-path:inset(0 100% 0 0);
  transition:clip-path .5s cubic-bezier(.2,.8,.2,1)}
.word:hover .fill{clip-path:inset(0 0 0 0)}

The single-element version needs no clone: the text itself is stroked and transparent, with a gradient painted as background, clipped to the glyphs, sized 0% 100% and no-repeat. Hovering transitions background-size to 100% 100% — the gradient pours through the letterforms.

Make it yours

  • Fill direction: inset(0 0 0 100%) fills right-to-left; background-position 100% 0 mirrors the single-element version.
  • Vertical flood: animate the Y track of background-size (100% 0% → 100% 100%).
  • Stroke weight: 1px is elegant at 60px+, use 2px below that.
  • Swap the flat fill for a two-stop brand gradient — same declarations.

Gotchas — read before shipping

  • text-stroke eats thin serifs from the outside — bump the optical weight or use a stroke-friendly grotesque.
  • The clone must duplicate the exact text (generate it from the same string) or hover reveals a typo.
  • transition clip-path/background-size, never width — no layout, no CLS.

Browser support

ChromeSafariFirefoxEdge
111+15.4+113+111+

Floor set by oklch() as this demo is written. The core technique itself goes back further — Chrome 57+.

-webkit-text-stroke is de-facto standard in every engine. Without it, text renders solid — the hover still shifts color, nothing breaks.

Search CodeFronts

Loading…