Tear-off
Any element gets a perforated line and a strip to tear off.
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 box that carriesdata-tear-offhas to hug the front: no padding, no border, nothing else inside but the two faces. The layout of the front has to agree withdata-tear-off-strip: the script cuts where the setting says. And nothing close around the element may clip it withoverflow: hidden, the strip leaves its box. -
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 attributesSelect 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-tear-off Marks an element with a strip to tear off: a box that hugs the element and holds nothing else. It takes no value: leave the value field empty. It carries the settings. Put it around any element of the page, as many as you like; each one tears on its own. data-tear-off-front On the element itself, whole, strip included: a coupon, a ticket, a card, a picture. It takes no value. It decides the size, and the script cuts it where the side and strip settings say. Without it nothing is mounted, and the console says so. [ Required ] data-tear-off-under On the face that shows where the strip was, after the front: a code, a price, a link. It takes no value. The stylesheet lays it behind the strip and it is out of reach until the strip is off. Without it the page behind the element shows where the strip was. [ Optional ] data-tear-off-label On the element, with a few words as its value: what a screen reader says for the button that tears the strip. Without it the button says Tear off the strip. [ Optional ] data-tear-off-back-label On the element, with a few words as its value: what a screen reader says for the same button once the strip is off. Without it the button says Put the strip back. [ Optional ] data-tear-off-state Written by the engine on the element: idle when whole, tearing while the line gives, part when a tear was left in part, loose when the strip is free in the hand, falling, torn, and returning on the way back. Never written by hand. [ Optional ] 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-tear-off-back-color auto Colour of the back of the paper, the side the strip shows as it turns over in its fall. Options: auto, the front's own background colour mixed with paper white, and paper white alone under a front with no background; or any CSS colour. Taken out of this block, --tear-off-back on the element takes over. data-tear-off-side right Which side of the element is the strip, which also turns the line. Options: right and left, an upright line and a strip beside it; top and bottom, a level line and a strip above or below it. data-tear-off-strip 30% Depth of the strip, from that side of the element to the line, any CSS unit or a percentage of the element. data-tear-off-hole clamp(3px, 0.32vw, 5px) Width of one hole of the line, any CSS unit. data-tear-off-gap clamp(9px, 0.9vw, 14px) Distance from the middle of one hole to the next, any CSS unit; the script and the stylesheet fit a whole number of them in the line. data-tear-off-ragged 0.5 Size of the teeth the torn edge keeps between two holes, 0 to 1. data-tear-off-seed 7 Which torn edge is drawn, a whole number. data-tear-off-from pull The end of the line that gives first. Options: pull, the end nearer to where the strip was taken; start, the top of an upright line or the left of a level one; end, the other. A key always tears from start unless end is set. data-tear-off-shadow 0.5 Strength of the shadow the torn length casts on the page, 0 to 1. data-tear-off-threshold 0.8 Share of the line torn past which letting go finishes the tear, 0 to 1. data-tear-off-release rest What letting go before that does. Options: rest, the torn length springs back against what still holds and stays a hair open; hold, it stays at the angle it was left at; back, the tear closes again, hole by hole. data-tear-off-put-back true Whether the key, on a strip already torn off, puts it back. Options: true, Enter or Space puts the strip back; false, a strip torn off is gone for good, and only putBack() from a script brings it back.
Two files, no build step.
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
TearOff.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-tear-off-back-color auto Colour of the back of the paper, the side the strip shows as it turns over in its fall. Options: auto, the front's own background colour mixed with paper white, and paper white alone under a front with no background; or any CSS colour. Taken out of this block, --tear-off-back on the element takes over. data-tear-off-side right Which side of the element is the strip, which also turns the line. Options: right and left, an upright line and a strip beside it; top and bottom, a level line and a strip above or below it. data-tear-off-strip 30% Depth of the strip, from that side of the element to the line, any CSS unit or a percentage of the element. A thin tab, like the tear tab of a parcel: it bends in a smooth curve A deep strip, like the stub of a ticket; past half the element it is the larger part that leaves data-tear-off-hole clamp(3px, 0.32vw, 5px) Width of one hole of the line, any CSS unit. At 0 no holes at all: a line that tears where nothing shows Large round holes, like a notebook; keep it under two thirds of gap or the holes touch data-tear-off-gap clamp(9px, 0.9vw, 14px) Distance from the middle of one hole to the next, any CSS unit; the script and the stylesheet fit a whole number of them in the line. A fine line of many holes, a tear that runs like a zip Few holes and wide bridges of paper, each one a tooth data-tear-off-ragged 0.5 Size of the teeth the torn edge keeps between two holes, 0 to 1. At 0 a clean cut from hole to hole Rough paper, teeth up to a third of gap data-tear-off-seed 7 Which torn edge is drawn, a whole number. Any other number draws another edge; two elements with the same seed and the same size tear alike data-tear-off-from pull The end of the line that gives first. Options: pull, the end nearer to where the strip was taken; start, the top of an upright line or the left of a level one; end, the other. A key always tears from start unless end is set. data-tear-off-shadow 0.5 Strength of the shadow the torn length casts on the page, 0 to 1. At 0 no shadow at all, and a flat look A heavy shadow, a strip held high over the page data-tear-off-threshold 0.8 Share of the line torn past which letting go finishes the tear, 0 to 1. A short pull is enough, the rest of the line gives by itself At 1 the hand has to tear to the last hole data-tear-off-release rest What letting go before that does. Options: rest, the torn length springs back against what still holds and stays a hair open; hold, it stays at the angle it was left at; back, the tear closes again, hole by hole. data-tear-off-put-back true Whether the key, on a strip already torn off, puts it back. Options: true, Enter or Space puts the strip back; false, a strip torn off is gone for good, and only putBack() from a script brings it back. -
Drive it from JavaScript
When something other than a hand should tear a strip: a coupon that opens when a form is sent, a button elsewhere that reveals every code, a strip put back when the offer is reset, or a sound on each hole.const element = document.querySelector('[data-tear-off]'); element.addEventListener('tearoff:torn', (e) => { console.log(e.detail); }); const strip = TearOff.mount(element); // the one auto() mounted, or a new one strip.tear(); // tears it to the end and lets it fall strip.putBack(); // puts it back in place element.addEventListener('tearoff:torn', () => { /* the face underneath is in reach */ }); element.addEventListener('tearoff:rip', (e) => { /* one bridge gave: e.detail.progress */ }); // When the element leaves the page: strip.destroy(); // removes the two boxes the script added and the listeners
-
Fit it to your page
- Structure: three marks.
data-tear-offgoes on a box that hugs the element and holds nothing else. Inside it,data-tear-off-frontgoes on the element itself, whole, strip included: a coupon, a ticket, a card, a picture.data-tear-off-undergoes on the face that shows where the strip was. The front decides the size. - Where the line is: two settings.
sidesays which side of the element is the strip,striphow deep it is. What is printed on the strip and what on the stub is your own layout: the script cuts the front where the two settings say, whatever is there, and a title that runs across the line is torn in two. - The face underneath is real content. It is the page's own element: a code, a price, another link. Once the script is there, the stylesheet lays it behind the strip, in the box of the strip plus one hole towards the line, and it shows through the holes. It is out of reach,
inert, until the strip is off, then it can be read, selected and clicked. - The front stays the page's element. At rest nothing wraps it and nothing copies it; a press on the strip that did not travel is handed to the front as a click, so a link on the strip still works.
- Any size, and an element turned or scaled by a transform of its own or of a parent: the pointer is read in the element's own space, and the strip falls down the screen, not down the element.
- One setting for every element of the page, without opening the file: declare
window.TearOffSettings = { release: 'back' }in a script placed beforetear-off.js. The three levels, least specific first, areDEFAULTS, then that object, then adata-tear-off-*attribute on the element itself (data-tear-off-side="top"). - The back can also come from the stylesheet: with
backColorleft onauto, a custom property--tear-off-backon the element is read before the front's own colour. - What the script adds and writes. Two boxes as the last children of the element:
data-tear-off-pieces, empty at rest, anddata-tear-off-handle, a realbuttonlaid over the strip. On the element,data-tear-off-state:idlewhen whole,tearingwhile the line gives,partwhen a tear was left in part,loosewhen the strip is free in the hand,falling,torn, andreturningon the way back;data-tear-off-at, the side of the strip, and three custom properties,--tear-off-strip,--tear-off-holeand--tear-off-gap, the settings as written, from which the stylesheet draws the line. While a strip is out of its line, copies of the front insidedata-tear-off-pieces, markeddata-tear-off-piece,inertand without their ids; and an inlineclip-pathon the front, which stays for as long as a part of the strip is torn. - Events, all on the element, all bubbling, all with the instance in
detail.strip:tearoff:grab{ from }when a hand starts to pull;tearoff:rip{ progress, hole }each time a bridge of paper gives, a place for a sound;tearoff:release{ progress, beyond }when the hand lets go of a strip still on its line;tearoff:tornonce the strip has fallen away and the face underneath is in reach;tearoff:backonce a strip that was torn off is back in place. - **From JavaScript, beyond
mount,autoanddestroy**: the api carrieselement,front,under,settings,tear()which tears the strip to the end by itself,putBack()which puts a torn strip back and closes a tear left in part,progress(),state(),isRunning()andrefresh(), to call after a setting or an attribute changed. - The labels of the button:
data-tear-off-labelon the element is what a screen reader says for it while the strip is on, "Tear off the strip" without one, anddata-tear-off-back-labelonce it is off, "Put the strip back" without one. - Keyboard: each strip is one Tab stop, the button over it. Enter or Space tears it off; the same key puts it back when
putBackallows. The button says expanded once the face underneath is in reach. WithputBackoff, the button is disabled once the strip is gone and the focus moves to the face underneath, so what was found is read at once. - Touch: a finger on the strip pulls it, and the page does not scroll from there; a finger anywhere else on the element scrolls the page as usual. A tap on the strip is a tap on the front.
- **
prefers-reduced-motion**: no pull, no bend, no fall. A click on the strip, or the key, takes it off at once; the key puts it back at once. Turning the preference off brings the tear back without a reload. - Without JavaScript the element is whole, with no holes and nothing to pull, and the face underneath is a plain block right after it, in the order of the markup: nothing is hidden, so the code or the link is still there to read and to click.
- 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.
- In the Webflow Designer the two faces show one under the other. The Designer never runs the Embed, so the canvas shows each face underneath below its front, where its text can be edited, and no holes. On the published site, and in preview, it sits behind the strip.
- Structure: three marks.
-
Avoid the pitfalls
- **The box that carries
data-tear-offhugs the front.** No padding, no border, nothing else inside but the two faces: the line and the face underneath are placed in that box, and the strip is measured on the front. - **The layout of the front has to agree with
strip.** The script cuts where the setting says. If the stub of your ticket is 26% of its height, setdata-tear-off-strip="26%"; if it is a fixed band, give both the same length. A front with a border counts it: the depth runs from its outer edge. - **Do not give the front a
maskor aclip-pathof your own.** The holes are a mask on it and the tear is a clip-path on it. Shape it withborder-radius. - Style the front by its own class, not by a selector that needs its parent. While a strip is out of its line the script shows copies of the front inside another box: a rule written
.card > .frontno longer reaches them, a rule written.frontdoes. - A front that holds a video, a canvas or a form field is copied like the rest, and a copy of those starts empty. Keep them on the stub side, or out of the front.
- Nothing close around the element may clip it with
overflow: hidden: the strip leaves its box, and falls. On the section itself it is fine, and it keeps a falling strip from adding a scrollbar. - A front with no background of its own tears, but its back falls on paper white; set
backColor. - The face underneath has no size of its own once the script is there: the stylesheet gives it the box of the strip. Lay its content out to fit that box, and do not give it a position or a z-index.
- Until the script runs, the face underneath is in the flow, after the front. With the script in the page's own markup, as the Embed is, it runs before the first paint. Loaded late, the page shows the block for a moment; hide it yourself in that case, or load the script earlier.
- Mounted while hidden, an element measures nothing and breaks nothing: the line is read when a strip is first pulled, never at mount.
- **The box that carries
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
Tear-off is a cursor animation for Webflow and vanilla JavaScript, with no library. Also called tear off coupon effect, perforated ticket tear animation or tear away ticket stub.
Any element gets a perforated line and a strip to tear off. Take the strip and pull it away from the rest: the line gives bridge by bridge from the end you pull from, at the pace of the hand, the torn length bends off the page and casts a shadow, and both sides of the tear show the same ragged edge, small teeth between the half holes, drawn from a seed. Let go early and what has torn stays torn, the strip resting against what still holds. Pull past the last hole and the strip comes free, follows the hand, and falls away when released, turning over to show its back. What is left is the stub with its torn edge and a second face of your own where the strip was: a code, a price, a link that can be clicked. The holes are a mask on the page's own element and the tear is a clip-path on it: nothing is turned into a bitmap, so the text stays sharp on any screen, at any size, on an element turned on the page. Each strip is one Tab stop, Enter or Space tears it off and puts it back. Under reduced motion the strip is simply there or gone. Without the script the element is whole and the second face is a plain block after it. No library, no canvas, no picture.
Updated
Questions
Put data-tear-off on a box that hugs the coupon, data-tear-off-front on the coupon itself, strip included, and data-tear-off-under on the code that shows where the strip was. data-tear-off-side says which side is the strip, right by default, and data-tear-off-strip how deep it is, 30% by default. The code is out of reach until the strip is off, then it can be read, selected and clicked.
No. The holes are a CSS mask on the page's own element and the tear is a clip-path on it, so nothing is turned into a bitmap and the text stays sharp at any size. The ragged edge, small teeth between the half holes, is drawn from data-tear-off-seed, with data-tear-off-ragged for the size of the teeth.
What has torn stays torn, the strip resting against what still holds: that is data-tear-off-release="rest", the default, and back closes the tear again instead. Past threshold, 0.8 of the line by default, a release finishes the tear. Pulled past the last hole, the strip comes free, follows the hand and falls away, turning over to show its back.
Yes. Each strip is one Tab stop, a real button laid over it: Enter or Space tears it off and the same key puts it back. With data-tear-off-put-back="false" the button is disabled once the strip is gone and the focus moves to the face underneath, so what was found is read at once. From a script, tear() and putBack() do the same.
Without the script the element is whole, with no holes, and the face underneath is a plain block right after it, so the code or the link is still there to read and to click. Under prefers-reduced-motion there is no pull, no bend and no fall: a click on the strip, or the key, takes it off at once.