Outline Text Fill

Hollow outlined letters fill with colour from the bottom up when you hover, like liquid rising inside the type.

  • CSS
  • Text
  • Hover
  • Added

AI prompt

Create outlined text that fills on hover in pure CSS. Render the word with color: transparent and -webkit-text-stroke: 1.5px in the light ink colour. Stack an ::after layer on top with content: attr(data-text), the accent colour as a solid fill, and clip-path: inset(100% 0 0 0) so it is hidden. On :hover and :focus-visible, transition the clip-path to inset(0 0 0 0) over 500ms with cubic-bezier(0.16, 1, 0.3, 1), so the fill rises from the baseline. Use a heavy display font so the outline reads well. Keep the real text in a visually hidden span and mark the visual element aria-hidden. Under prefers-reduced-motion, swap to the filled state instantly. The word reads "Explore".

Source

Demo.tsx
'use client'

import s from './demo.module.css'

export default function OutlineFill({ labels }: { labels: Record<string, string> }) {
  return (
    <a href="#" className={s.link} onClick={(e) => e.preventDefault()}>
      <span className={s.sr}>{labels.text}</span>
      <span className={s.word} data-text={labels.text} aria-hidden="true">
        {labels.text}
      </span>
    </a>
  )
}
demo.module.css
.link {
  border-radius: 6px;
  text-decoration: none;
}

.link:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 6px; }

.sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.word {
  position: relative;
  display: inline-block;
  font: 800 clamp(2.4rem, 9vw, 3.4rem) / 1.1 var(--font-display);
  letter-spacing: -0.01em;
  color: transparent;
  -webkit-text-stroke: 1.5px var(--stage-ink);
}

.word::after {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  color: var(--stage-accent);
  -webkit-text-stroke: 1.5px var(--stage-accent);
  clip-path: inset(100% 0 0 0);
  transition: clip-path 500ms var(--ease-out);
}

.link:hover .word::after,
.link:focus-visible .word::after { clip-path: inset(0 0 0 0); }

@media (prefers-reduced-motion: reduce) {
  .word::after { transition: none; }
}

Suggest an idea

Seen a motion or a theme you would like to find here? Describe it, and it opens as a prefilled GitHub issue for you to post.

What are you suggesting?

Needs a GitHub account. Suggestions are public.