
The menu that opens from its button
Menus, Transitions, Text
Free
The cookie banner written as one sentence, in the minimal style: We use cookies for essentials, analytics and marketing. The kinds are words of the sentence and real switches: touched, a word is struck by a thin vermilion line, loses weight and width and hops over the others to the end of the sentence, which finds its new order with FLIP («essentials only, not analytics or marketing»). Once chosen, the sentence closes into a line and a small link is left that says what was chosen.
A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Consent in one line</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- the cookie banner written as one sentence, near the middle of the page (at the bottom on a real site). The kinds of cookies are words of the
sentence and real switches: struck with a thin line they move to the end («not analytics»), and the sentence
finds its new order. Once chosen, the sentence closes into a line, and a small link is left to open it again.
The words are made by script.js, from the values at its top -->
<div class="consent" data-consent>
<section class="banner" role="region" aria-label="Cookie consent" data-banner data-autoclic>
<span class="banner__frame" aria-hidden="true" data-frame></span>
<p class="sentence" data-sentence></p>
<div class="actions" data-actions>
<button class="pill pill--main" type="button" data-accept>
<span class="pill__bg" aria-hidden="true"></span>
<span class="pill__label" data-text="Accept all">Accept all</span>
</button>
<button class="pill" type="button" data-save>
<span class="pill__bg" aria-hidden="true"></span>
<span class="pill__label" data-text="Save choices">Save choices</span>
</button>
</div>
</section>
<button class="reopen" type="button" hidden data-reopen>
<span class="reopen__mask"><span class="reopen__text" data-reopen-text>Cookie settings <span class="reopen__state">· <span data-summary>all on</span></span></span></span>
<span class="reopen__line" aria-hidden="true" data-reopen-line></span>
</button>
<p class="sr" role="status" aria-live="polite" aria-atomic="true" data-live></p>
</div>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="script.js"></script>
</body>
</html>Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.
23 values to change
With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:
Take “Consent in one line” from Animorio and fit it into my siteThe sentence is written from START, ALWAYS and KINDS at every change: the kinds that are on with commas and and (with only when essentials is alone), then, if some are off, a comma, not and the ones off with or. Every piece is an element with a key: a kind is always the same button (role switch, aria-checked), and moves from one part of the sentence to the other; a comma or a joining word is named after the word that comes after it, so when the order changes the old one fades and a new one rises where it is needed, and no word crosses another. Commas and the full stop live inside the word before them: they never part at the end of a line.
Before the change every piece is measured (getBoundingClientRect, transforms included, so a change halfway starts again from where the words are). Then the new order is put in, every word gets its final weight and width (wght 610 or 300, wdth 100 or 84) and is measured again. Every piece starts moved by the difference and goes back to zero with power3.inOut in RECOMPOSE seconds. While they travel, every word keeps the width it will have at the end and the banner its new size: the struck word slims down on the way, and without this it would have pulled the others along (on a phone it would also have moved the line breaks).
The line is a 1.5 pixel span inside the word’s text, always as long as the letters, at the middle of the small letters, measured with measureText: it is drawn with scaleX from the left in STRIKE seconds and lifts off to the right. The word stays put while the line runs, then goes: on the first line it hops over the others (HOP ems up with sine, and a squash to 0.94 with elastic.out when it lands); if it changes line, or would hop into the line above, it folds flat where it was and opens where it goes with back.out, so it never covers the other words.
The capsule is a layer of its own, with left, top, width and height tweened in pixels: it follows the sentence when it changes length and, once chosen, closes into a hairline one pixel high (edge and shadow fade with a CSS variable, --sh), turns to ink and runs down to the size of the link, while the words fold with scaleY and the wdth axis at 62. The link rises out of the hairline inside a mask and the hairline is spent from both ends. Opening again does the same backwards, and the capsule opens with back.out, a pixel past and back.
Tab goes from essentials to analytics and marketing (in the order the sentence names them at that moment) and to the pills; Space or Enter strike and turn back on, and the focus stays on the word even after it has moved. Every change is announced in an aria-live region with the new sentence; closing, the focus moves to the link, opening again to the first kind. The preview’s click (detail 0) plays a round in a GSAP timeline: it strikes marketing, then analytics, turns analytics back on, saves, opens again, accepts all and opens again as it started. A real touch stops the round.
| Name | File | Default | What it changes |
|---|---|---|---|
START | script.js | 'We use cookies for' | The words before the kinds. |
ALWAYS | script.js | 'essentials' | The kind that is always on and cannot be struck. |
KINDS | script.js | ['analytics', 'marketing'] | The kinds that can be struck, in the order the sentence names them. |
STRIKE | script.js | 0.34 | The seconds the line takes to strike a word, or to lift off it. |
RECOMPOSE | script.js | 0.72 | The seconds the sentence takes to find its new order. |
HOP | script.js | 0.85 | How high the word passing over the others hops, in ems. |
CLOSE | script.js | 1 | How slowly the banner closes into a line and opens again (1 is the base). |
WEIGHTS | script.js | [330, 610, 300] | The weight of the sentence, of a kind that is on and of a struck one (100 to 900). |
STRUCK_WIDTH | script.js | 84 | The width (wdth axis) of a struck word, 62 to 125. |
For the cookie consent of a site that wants to say it in a few words, but also for any choice that reads as a sentence: the notifications you get (we’ll email you about orders and news), the data an app shares, the ingredients to leave out of a dish.
The capsule takes the whole width but twelve pixels each side, the sentence wraps onto two lines and the pills sit under it, side by side, half each. Words changing line fold and open again instead of crossing the others; the same animations, only measured for a finger.
No hops, no folds, no running lines: the strike appears whole, the new sentence fades in in place, the banner fades out and the link fades in where it was, and the same opening again.
GSAP 3.15.0, Archivo (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP 28 KB.
Tested on 16 October 2026
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.
With your account’s email and password.
or with your email
You’re signed in as .
With your account, you can download the zip of every animation and every pack, for free.