Skip to content
animorio
IT
Back to the library

Ink on the button

Square buttons where a brushstroke of ink enters from the side you come from and leaves by the side you go, and the text changes colour right on the brushstroke’s edge.

HoverGSAP, CSS onlyEasyFree

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN.

<!doctype html>
<html lang="it">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>L’inchiostro sul bottone</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/standard.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="banco">
    <p class="nota" data-nota>Passa sopra i bottoni: l’inchiostro entra dal lato da cui arrivi ed esce da quello da cui te ne vai.</p>
    <section class="invito">
      <h1 class="invito__titolo">Stampiamo a mano, una pagina alla volta.</h1>
      <p class="invito__testo">Carta di cotone, caratteri di piombo e un torchio del 1932. Raccontaci cosa ti serve.</p>
      <div class="invito__bottoni">
        <a class="bottone bottone--pieno bottone--grande" href="#" data-bottone><span class="bottone__testo">Chiedi un preventivo<svg class="bottone__freccia" viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M2.5 8h10.5M9 4l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="bottone__inchiostro" aria-hidden="true"><span class="bottone__testo">Chiedi un preventivo<svg class="bottone__freccia" viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M2.5 8h10.5M9 4l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg></span></span></a>
        <a class="bottone bottone--vuoto bottone--grande" href="#" data-bottone><span class="bottone__testo">Guarda i lavori</span><span class="bottone__inchiostro" aria-hidden="true"><span class="bottone__testo">Guarda i lavori</span></span></a>
      </div>
      <p class="invito__etichetta">Cosa stampiamo</p>
      <div class="invito__cartellini">
        <a class="bottone bottone--cartellino" href="#" data-bottone><span class="bottone__testo">Biglietti</span><span class="bottone__inchiostro" aria-hidden="true"><span class="bottone__testo">Biglietti</span></span></a><a class="bottone bottone--cartellino" href="#" data-bottone><span class="bottone__testo">Inviti</span><span class="bottone__inchiostro" aria-hidden="true"><span class="bottone__testo">Inviti</span></span></a><a class="bottone bottone--cartellino" href="#" data-bottone><span class="bottone__testo">Etichette</span><span class="bottone__inchiostro" aria-hidden="true"><span class="bottone__testo">Etichette</span></span></a><a class="bottone bottone--cartellino" href="#" data-bottone><span class="bottone__testo">Manifesti</span><span class="bottone__inchiostro" aria-hidden="true"><span class="bottone__testo">Manifesti</span></span></a><a class="bottone bottone--cartellino" href="#" data-bottone><span class="bottone__testo">Menù</span><span class="bottone__inchiostro" aria-hidden="true"><span class="bottone__testo">Menù</span></span></a>
      </div>
    </section>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

What it does

A small print-shop page with two large buttons and five tags. Passing the mouse over a button, ink crosses it like a brushstroke with a ragged edge: coming from the left it enters from the left, coming from the right it enters from the right. The text and the arrow change colour exactly where the edge passes, mid-word. Leaving, the ink exits towards the side you go; if you come back while it is leaving, it changes its mind and returns from wherever it is. The filled button inks raspberry, the empty one black, the tags green. Right after the page opens, if nobody has passed over it yet, the first button makes a slow brushstroke by itself, as an invitation.

How it works

Two buttons in one

Inside each button there is a second copy, position absolute on top of the first, with the inked colours and the same text in the same place. Cutting the copy reveals the ink, and the text below and the text above meet on the cut: the colour splits right there, with no blending tricks.

A band between two edges

The cut is a clip-path polygon going from var(--da) to var(--a), with nine points per side nudged a few pixels back and forth: the edge looks like a brush’s. At rest both edges sit outside, at −12%; entering from the left --a runs to 112%, entering from the right --da runs to −12%.

Where you come from, where you go

A single pointermove listener on the window checks which button is under the pointer: when it changes, it leaves the old one and enters the new one, and the side is the half of the button where the pointer is at that moment. Leaving to the right moves the left edge, leaving to the left moves the right one.

Interruptible

GSAP animates the two CSS variables: entering takes 0.45 seconds in power3.out, leaving 0.38 in power2.inOut. If the band is not empty when you come back, it does not restart from the edge: both variables go on from where they are, and the brushstroke turns back without jumps.

Values to change

NameFileDefaultWhat it changes
ENTRA, ESCEscript.js0.45, 0.38The seconds to cover and to uncover the button.
--inchiostro-fondo, --inchiostro-testostyle.cssper varianteThe colours of the ink and of the inked text for each variant (filled, empty, tag).
clip-pathstyle.css9 punti per latoThe brushstroke’s edge: more points and larger offsets, a rougher brush.

When to use it

For the main buttons of a site that wants a hand-made touch: workshops, print shops, restaurants, studios. It works with any text and any colour.

On phones

Tapping a button, the ink enters from the finger’s side and after a moment leaves to the right: the gesture remains, even without a hovering mouse.

With reduced motion

No running brushstroke: the ink appears and disappears in place with a 150-millisecond fade.

Pitfalls

  • The inked copy’s text must sit exactly over the base’s: same padding, same line, same height, or a double text shows on the edge.
  • With pointerenter and pointerleave on buttons with children, moving from one child to another can make the ink leave and re-enter: better to ask on every pointermove which button is underneath.
  • The band must start and end outside the button (here at −12% and 112%), or the ragged edge stays visible on the side.
  • The clip-path also cuts the copy’s focus ring: the ring belongs on the button, with outline and a little offset.

Libraries

GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.

Weight: 11 KB of our own files.

Testing

Tested on 27 September 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: Done

License

Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.

All animations