Button 06
Cells of colour light the button from the side the pointer came in by, a ragged front of early cells ahead, the label turning to the ink colour exactly where the front has passed, and go dark toward the side the pointer left by.
Documentation
-
Copy to Webflow
Add Geist and Geist Mono to your site's fonts (Site settings, Fonts, Google Fonts) before pasting: Webflow removes any font your site has not installed. -
Add custom CSS
Custom CSS in WebflowThis CSS already ships inside the Webflow embed, so a pasted section needs nothing added. Paste it yourself, in the page or the site custom code, only if the effect runs on several pages. The tutorial explains where.
See the tutorialThe look of the preview is thebutton-set-*classes of the paste: the button at rest, its row and the words around it. The Embed only places the moving layers and holds the two colours of the effect. On a button of your own, keep your class and add the attribute. -
Add custom JS
Custom JavaScript in WebflowThis script already ships inside the Webflow embed, settings block included. You paste it yourself, before the closing body tag of the page or the site custom code, only when the effect has to run on several pages, and the tutorial says how to choose.
See the tutorial -
Use it on your own elements
How to add a custom attribute in WebflowA custom attribute is a pair, a name and a value, typed in the Settings panel of the element you selected, under Custom attributes. The name is what the script looks for, the value is what it reads. The tutorial shows the panel.
See the tutorialCustom attributeSelect the element on the canvas and open the Settings panel. Under Custom attributes, add a pair: the name on the left, the value on the right.Custom attribute What it does data-button-motion="pixel" Cells of colour light the button from the side the pointer came in by, and go dark toward the side it left by. This file plays pixel. Another animation of the set needs that button's own code on the page: without it, the button stays as the page styled it and the browser console says so. An empty or unknown value plays fill when fill's code is on the page, pixel otherwise, and says so in the console.SettingsEvery setting is a custom attribute too, added the same way and on the same element. An attribute always wins over the defaults written in the code.Attribute Default What it is data-button-motion-color #ffab1a The colour of the cells. Options: any CSS colour. data-button-motion-ink auto The colour of the label once the cells are under it. auto, the default, picks #15100c or #f4ece0, whichever has the higher contrast on color, so a pale colour gets the dark label and a dark or saturated one the light label; the default ink #15100c gets #f4ece0. Options: auto, or any CSS colour, kept as it is, so one that reads on color. data-button-motion-duration 0.6 Length of the front crossing the button, in seconds; the cells go out over 1.4 times it. data-button-motion-press 0.96 The scale of the button while it is pressed. data-button-motion-grain 6 The size of one cell, in pixels, the one length of the component that is not fluid.
Two files, no build step. It also needs GSAP 3 on the page, loaded with the tag in step 1. Without it the button stays exactly as the page styled it, and nothing moves.
It runs in the browser only. Load it with a script tag in plain HTML, in a
client-side script in Astro, never in the frontmatter. In React, call
ButtonMotion.mount(ref.current) in useEffect and
destroy() in its cleanup.
-
Tune it, either way
Per element with an attribute, or once for the whole site by editingDEFAULTSat the top of the file.Attribute Default What it is Lower Higher data-button-motion-color #ffab1a The colour of the cells. Options: any CSS colour. data-button-motion-ink auto The colour of the label once the cells are under it. auto, the default, picks #15100c or #f4ece0, whichever has the higher contrast on color, so a pale colour gets the dark label and a dark or saturated one the light label; the default ink #15100c gets #f4ece0. Options: auto, or any CSS colour, kept as it is, so one that reads on color. data-button-motion-duration 0.6 Length of the front crossing the button, in seconds; the cells go out over 1.4 times it. Snappy, closer to a state change Slow, the front is seen travelling data-button-motion-press 0.96 The scale of the button while it is pressed. A deep press, the button shrinks visibly under the click Barely a press, and 1 turns it off data-button-motion-grain 6 The size of one cell, in pixels, the one length of the component that is not fluid. Fine cells, a soft dissolve that reads as noise Coarse blocks, clearly 8-bit; above about a third of the button height only a few blocks are left -
Drive it from JavaScript
When a button arrives after the page has loaded, when something else than the pointer has to play it, or when the page reacts to a hover.const element = document.querySelector('[data-button-motion]'); element.addEventListener('buttonmotion:enter', (e) => { console.log(e.detail); }); const button = ButtonMotion.mount(element); // the button auto() mounted, or a new one button.enter(); // plays the hover in, from the centre button.leave(); // and out button.refresh(); // after changing an attribute // Buttons added after the page loaded: mount everything marked inside them. ButtonMotion.auto(container); // When the button leaves the page: ButtonMotion.destroy(element); // the content goes back exactly as it was
-
Fit it to your page
- Mark the button:
data-button-motion="pixel"on the<button>or the<a>that is the button. Its content becomes the label: the engine moves the nodes into one span at mount and puts them back at destroy. - Settings go on the same element, as more attributes:
data-button-motion-grain="10". Three levels, least specific first:DEFAULTS, thenwindow.ButtonMotionSettings = { grain: 10 }declared before the engine, then the element's own attributes. - **
inkfollowscolorunless you give it.** Left out, or set toauto, it is the dark#15100cor the light#f4ece0, whichever reads better oncolor: a blue#2f5beaand a deep ink#15100cget the light label, a pale colour the dark one. An ink given by attribute, by the page block or tomount()is kept as it is. - **
colorandinkare custom properties**,--button-motion-colorand--button-motion-ink, so a rule of yours on the button changes them as surely as an attribute does, and loses to an attribute the engine wrote. The engine picks the ink from the colour it was given, so a colour set only by a rule of yours needs its--button-motion-inkset beside it. - The preview ships whole.
snippet.htmlwith the last part ofbutton-06.css, after the linelook, left out of the Webflow Embed, is the preview: the same button, row and words, in Geist and Geist Mono, loaded by the font tag of the Code tab. Thosebutton-set-*classes are never read by the engine. On a button of your own, keep your class and add the attribute; that last part of the file can then go. - On Webflow, add the fonts first. Add Geist and Geist Mono to your site's fonts (Site settings, Fonts, Google Fonts) before pasting: the paste names them, and the Designer drops a font the site does not have.
- Other buttons of the set on the same page. Every button of the set ships the same base. Paste another button's Embed, or load its two files, in any order: the file that loads first builds the base, and each one after it only adds its animation. The base rules its stylesheet repeats are the same rules. Each animation reads the
DEFAULTSof its own file and the settings block of its own Embed, thewindow.ButtonMotionSettingsthat stood when its file loaded. - A value whose file is not on the page waits for it: with this file alone, a button marked
data-button-motion="roll"stays the page's plain button, and the console says so after ten seconds. An empty or unknown value playsfillwhen fill's file is on the page, and the first animation on the page otherwise,pixelwith this file alone, with a warning that names the element. - A finger gets the animation for the length of the tap, since a touch pointer enters on touch and leaves on release.
- The keyboard plays the enter from the centre when the button takes a focus the browser would show a ring for, and the leave when focus goes. Enter and Space press it like a click does.
- From JavaScript:
ButtonMotion.mount(el, overrides)returns{ element, settings, mode, enter(), leave(), refresh(), destroy() }, ornullfor a button whose animation's file is not on the page.enter()andleave()play from the centre.refresh()reads the attributes again and, if the value ofdata-button-motionchanged to an animation on the page, rebuilds the button for it.destroy()puts the content back exactly as it was.ButtonMotion.destroy(el),ButtonMotion.refresh(el),ButtonMotion.auto(root),ButtonMotion.defaults(theDEFAULTSof the first file of the set on the page) andButtonMotion.modes(the animations on the page) complete it.ButtonMotion.register(pack)is how a file of the set hands its animation to the base; the files call it themselves. - Events, all bubbling, with
{ element, mode }indetail:buttonmotion:readyonce GSAP is there and the button is built (plusreduced),buttonmotion:enterandbuttonmotion:leavearound every hover (plusvia:pointer,touch,keyboardorapi). - The state is on the element as
data-button-motion-state,idleorhover, for a rule of the page that wants to follow it. - **Under
prefers-reduced-motion: reduce** the button jumps to its hover state and back with no travel, which keeps the hover readable, and the press is skipped. - The words of the demo are the demo's: the preview ships with them, and on a real page every label is the user's own, in the page's language, or asked for.
- Mark the button:
-
Avoid the pitfalls
- **
pixelcopies the label's markup** for the ink coloured layer, so anidinside the button exists twice. Keep ids off the label. - The cells are drawn on a canvas the size of the button at the device pixel ratio, so a very large button with a small
grainis capped at about 2 400 cells by coarsening the grain. A button whose background is an image shows the cells over it, under the words. - The button needs a box. A bare
<a>is inline and cannot be transformed; the sheet makes itinline-blockat zero specificity, and a class of yours that setsdisplaywins.flexandinline-flexare fine.position: staticset by a class of yours would let the layers escape: the engine's layers are absolute inside the button. - The corners are yours, and the grid of cells follows them through
border-radius: inherit. - The colour covers the border. The grid of cells is laid over the border box, so a dark border of 2px around a blue colour does not stay as a dark ring: the engine writes the four border widths at enter as
--button-motion-bt,-br,-bband-bl, the layer moves out by them, andborder-radius: inheritis then the outer radius. A button with no border writes nothing. - A page that never gets GSAP shows a plain button, styled by its class, and the engine says so in the console after ten seconds. Nothing is built before GSAP is there, so the label is not even wrapped.
- The stylesheet places the layers. Without
button-06.cssthe layers of the animation have no place and no colour, and the engine says so in the console. - Two copies of the set's base on one page are one. The file that loads first builds the base and the others use it, so the button of a newer version of the bank pasted beside an older one runs on the older base. The first file to bring an animation keeps it, too: the whole set's file and this one on the same page play
pixelwith the settings of the one that loaded first. - **Webflow turns a
<button>with children into a Link block**: the engine reads the attribute, never the tag, so an<a>with the attribute is a button to it.
- **
One click on Copy the AI prompt copies everything an assistant needs to build this effect: the complete code, the markup, the dependency, every setting and every pitfall.
What it does
Button 06 (Pixel) is a button hover animation for Webflow and vanilla JavaScript, built on GSAP 3. Also called pixel button hover effect, pixel fill button or button hover pixel animation.
Cells of colour light the button from the side the pointer came in by, a ragged front of early cells ahead, the label turning to the ink colour exactly where the front has passed, and go dark toward the side the pointer left by. Played with GSAP on the button the page already has: the engine sets no font, size, border or background, only the two colours of the effect, and puts the label back exactly on destroy. Keyboard focus, touch and reduced motion are handled. Button 06 of the Button set: another button of the set pasted on the same page shares its base. Pattern type pixel dissolve button, 8-bit hover, cta hover.
Updated
Questions
Select the button, open the Settings panel and add the custom attribute data-button-motion="pixel" on the button or link itself. The button keeps its own class: the engine sets no font, size, border or background, only the two colours of the effect.
Yes, GSAP 3 with no plugin, loaded from a CDN. The tag that ships with it loads version 3.12.5. Without GSAP the button stays as the page styled it, nothing moves, and the console says so after ten seconds.
Set data-button-motion-grain on the button, the size of one cell in pixels. The default is 6. Lower gives fine cells that read as noise, higher gives coarse 8-bit blocks, and above about a third of the button height only a few blocks are left.
Set data-button-motion-color to any CSS colour for the cells. The label colour, ink, is auto by default: it picks #15100c or #f4ece0, whichever has the higher contrast on that colour. Set data-button-motion-ink to force a label colour of your own.
Yes. Keyboard focus plays the enter from the centre and the leave when focus goes, and a finger gets the animation for the length of the tap. Under prefers-reduced-motion: reduce the button jumps to its hover state and back with no travel, and the press is skipped.