Fullscreen curtain menu
Fullscreen menu triggered by a burger. On opening, four pale panels fall from the top in a cascade, the burger turns into a cross, then the links rise out of a mask at large size in the right-hand column; the contact details (mono // labels) sit in the bottom left, the footer aligns the copyright with the links column with a system line on the left.
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 tutorial -
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 -
Tune it
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 section 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-curtain-menu Marks the section the effect plays on. It takes no value: leave the value field empty. data-curtain-menu-toggle Marks the burger button, which usually sits outside the menu, in a fixed header. Without it nothing opens the menu but JavaScript, and the console says nothing. [ Required ] data-curtain-menu-panels Marks the box holding the curtain panels, real boxes your classes colour. Without it the engine builds the box and its panels itself. On the menu element the same name is the panel count of the table below. [ Optional ] data-curtain-menu-glow Marks the layer that lights up behind the links. Without it the engine builds it. [ Optional ] data-curtain-menu-links Marks the box holding the links that rise one after the other. Without it the engine takes the first nav inside the menu, and moves no link when there is none. [ Optional ] data-curtain-menu-link Marks one link to move, for links that do not all sit in the same box. As soon as one link carries it, only the marked links rise. [ Optional ] data-curtain-menu-mask Marks the box that clips a link while it rises. Without it the engine wraps each link in a box it builds. [ Optional ] data-curtain-menu-block Marks a side block that settles after the links, a contact column for one. Without it the block is simply there from the start. [ Optional ] data-curtain-menu-footer Marks the line that fades in last, at the foot of the menu. Without it nothing fades in there. [ Optional ] data-curtain-menu-bar Marks a bar of the burger that turns into a cross. Without it the engine takes the first two spans inside the button. [ Optional ] SettingsEvery setting is a custom attribute too, added the same way and on the same section. An attribute always wins over the defaults written in the code.Attribute Default What it is data-curtain-menu-panels 4 Vertical panels the curtain is cut into when the markup has none, 1 to 8. data-curtain-menu-fall 0.55 How long one panel takes to drop over the page, in seconds. data-curtain-menu-fall-stagger 0.07 Delay between two panels starting to fall, in seconds. data-curtain-menu-fall-ease power4.inOut Shape of the fall, where it speeds up and where it settles. Options: any GSAP ease, power2.out and expo.inOut for instance. data-curtain-menu-glow 0.5 Fade-in of the glow that comes up behind the open menu, in seconds, when --curtain-menu-glow gives it a tint (none by default). data-curtain-menu-cross 5 How far each burger bar travels towards the middle as it turns into a cross, any CSS unit. data-curtain-menu-morph 0.4 How long the burger takes to become a cross, in seconds. data-curtain-menu-reveal 0.85 How long one link takes to rise out of its mask, in seconds. data-curtain-menu-reveal-stagger 0.06 Delay between two links rising, in seconds. data-curtain-menu-reveal-ease power4.out Shape of the links rising. Options: any GSAP ease, back.out and power2.out for instance. data-curtain-menu-lift clamp(16px, 2.4vh, 26px) How far the side blocks travel up while they fade in, any CSS unit. data-curtain-menu-blocks 0.5 How long one side block takes to settle in, in seconds. data-curtain-menu-blocks-stagger 0.07 Delay between two side blocks, in seconds. data-curtain-menu-footer 0.5 Fade-in of the line at the foot of the menu, in seconds. data-curtain-menu-open-speed 0.8 Playback speed of the whole opening, 1 being the durations above. data-curtain-menu-close-speed 1.25 Playback speed of the closing, which plays the same timeline backwards. data-curtain-menu-escape true Whether the Escape key closes the menu. Options: true, false. data-curtain-menu-close-on-link true Whether a click on a menu link closes the menu. Options: true, false, false being for links that open elsewhere. data-curtain-menu-label-open Open menu Label read out on the button while the menu is closed. Options: any short phrase. data-curtain-menu-label-close Close menu Label read out on the button while the menu is open. Options: any short phrase.
Two files, no build step. It also needs GSAP 3 on the page, loaded with the tag in step 1.
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
CurtainMenu.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-curtain-menu-panels 4 Vertical panels the curtain is cut into when the markup has none, 1 to 8. Wide slabs drop over the page Narrow slices, the fall reads as a sweep data-curtain-menu-fall 0.55 How long one panel takes to drop over the page, in seconds. The curtain snaps shut A slow, heavy fall data-curtain-menu-fall-stagger 0.07 Delay between two panels starting to fall, in seconds. The panels land as one wall, 0 being all at once A visible ripple across the screen data-curtain-menu-fall-ease power4.inOut Shape of the fall, where it speeds up and where it settles. Options: any GSAP ease, power2.out and expo.inOut for instance. data-curtain-menu-glow 0.5 Fade-in of the glow that comes up behind the open menu, in seconds, when --curtain-menu-glow gives it a tint (none by default). The colour is there with the panels It creeps in under the links data-curtain-menu-cross 5 How far each burger bar travels towards the middle as it turns into a cross, any CSS unit. A flat, wide cross A tight, compact cross data-curtain-menu-morph 0.4 How long the burger takes to become a cross, in seconds. The icon flips The two bars turn slowly under the pointer data-curtain-menu-reveal 0.85 How long one link takes to rise out of its mask, in seconds. The links snap up into place A long, drawn-out rise data-curtain-menu-reveal-stagger 0.06 Delay between two links rising, in seconds. The whole list arrives at once The links come in one by one data-curtain-menu-reveal-ease power4.out Shape of the links rising. Options: any GSAP ease, back.out and power2.out for instance. data-curtain-menu-lift clamp(16px, 2.4vh, 26px) How far the side blocks travel up while they fade in, any CSS unit. They barely move They slide in from further down data-curtain-menu-blocks 0.5 How long one side block takes to settle in, in seconds. They are simply there They drift into place data-curtain-menu-blocks-stagger 0.07 Delay between two side blocks, in seconds. They arrive together One after the other, down the column data-curtain-menu-footer 0.5 Fade-in of the line at the foot of the menu, in seconds. It lands with the links It arrives well after them data-curtain-menu-open-speed 0.8 Playback speed of the whole opening, 1 being the durations above. The menu opens slower It opens faster data-curtain-menu-close-speed 1.25 Playback speed of the closing, which plays the same timeline backwards. The menu lingers on the way out It gets out of the way data-curtain-menu-escape true Whether the Escape key closes the menu. Options: true, false. data-curtain-menu-close-on-link true Whether a click on a menu link closes the menu. Options: true, false, false being for links that open elsewhere. data-curtain-menu-label-open Open menu Label read out on the button while the menu is closed. Options: any short phrase. data-curtain-menu-label-close Close menu Label read out on the button while the menu is open. Options: any short phrase. -
Drive it from JavaScript
When something other than the burger has to open or close the menu, or when the page reacts to it.const element = document.querySelector('[data-curtain-menu]'); element.addEventListener('curtainmenu:open', (e) => { console.log(e.detail); }); const menu = CurtainMenu.mount(element); // null until GSAP is on the page menu.open(); // and menu.close(), menu.toggle(), menu.isOpen() // When the menu leaves the page: menu.destroy(); // drops the listeners, the masks it built and the styles of the timeline
-
Fit it to your page
- One setting for every menu of the page, without opening the file: declare
window.CurtainMenuSettings = { fall: 0.7 }in a script placed beforecurtain-menu.js. The three levels, least specific first, areDEFAULTS, then that object, then adata-curtain-menu-*attribute on the menu element, so one menu can still differ from the page-wide setting. - GSAP 3 has to be on the page. Webflow injects the GSAP core itself, and a script tag inserted at runtime loads on its own schedule, so the engine waits for
gsapinstead of assuming it: up to ten seconds, then it warns in the console that GSAP was not found. The order of the two script tags does not matter. The demo and the embed both name version 3.12.5. - Minimal structure, as in
snippet.html: a button carryingdata-curtain-menu-togglewith twospans inside, and a block carryingdata-curtain-menu. Inside it, the panels (data-curtain-menu-panels), a nav (data-curtain-menu-links), the side blocks (data-curtain-menu-block) and the footer (data-curtain-menu-footer). The button does not have to be inside the menu, which is what lets it live in a fixed header. Outside the menu, it is anydata-curtain-menu-toggleof the document while the page holds a single menu; with several, it is the button whosearia-controlsis the id of the menu, otherwise the button in the samesection. - What the script builds, and only that: one mask around each link (
overflow: hidden, that box is the reveal) and the glow layer. In plain HTML, do not write them into the markup; in a framework that owns the DOM, such as React, write them, as the loading section says. Everything else is markup you own, so a builder can style it. - The panels are real boxes: their colour and their share of the width are a class of yours. Drop the panels container altogether and the script builds
panelsof them, which is what keeps the embed working when it lands alone in a bare section. - The rest is styled, not tuned: the size, weight and colour of a link, the position and radius of the menu glow are your own classes, and the tint of the glow is the custom property
--curtain-menu-glow, transparent by default: the cream and ink look has no glow. The stylesheet,curtain-menu.css, carries the mechanics only. Everything it proposes and does not need (panel colour, panel width, the glow gradient, the z-index of the overlay) sits inside:where(), which carries no specificity: one class of yours wins over it, with no!importantanywhere. - The tint in one line:
--accenton the page, for the hovers and the focus ring, the ink#15100cwithout it. A glow behind the open menu is one more line, for instance--curtain-menu-glow: color-mix(in srgb, var(--accent) 6%, transparent). - The layout of the menu content is yours: in the demo it is a grid with the nav on the right, the contact details in the bottom left and the footer across the full width, and at 767px and under (Webflow's mobile landscape) everything stacks, each zone with its explicit
grid-row. - **From JavaScript, beyond
open,close,toggle,isOpenanddestroy**:CurtainMenu.mount(element, overrides)returnsnullwhile GSAP is not on the page.destroy()gives the markup back as it was written: no inline style and no class of the engine's. Its api also carriessettings, the values it resolved, andtimeline, the GSAP timeline. The element firescurtainmenu:closeas well ascurtainmenu:open, both bubbling, with{ menu }asevent.detail.CurtainMenu.auto(container)mounts every menu found inside a container. - The words of the markup are the preview's, and they ship as they are: pasted, the page reads the way the preview did. Put the user's own content in their place whenever they want, in the page's language. The demo page also puts a
preventDefaulton its nav links: remove it. - 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.
- One setting for every menu of the page, without opening the file: declare
-
Avoid the pitfalls
- **Settings handed to
mount()after the page loaded are dropped.**auto()mounts every menu once GSAP is there and the DOM is ready, andmount()on an element that already carries a menu hands back the existing one untouched. A page-wide block therefore goes inwindow.CurtainMenuSettings, whichauto()reads itself; handed to a latermount()call it is silently dropped, and the settings block ends up looking decorative. - Without GSAP the menu stays invisible for good. Until the script has mounted, the menu content is at its natural place, on top of the page. The timeline is what puts it away, and the timeline only exists once GSAP has landed, which on a slow network is a second later. So the menu stays
visibility: hiddenuntil the engine writesdata-curtain-menu-readyon it. Remove that rule and the page opens on its own menu. - **Do not force
pointer-eventson the overlay in CSS.** The engine writespointer-events: autoon the menu when it opens and removes it once the closing has landed, under reduced motion too. - **Links animated with
yPercentmust beinline-flexorblock.** The mechanics proposeinline-blockinside a:where(), so a class of yours that setsdisplay: inlinestops the rise. - **On mobile, the three zones of the menu must each have their explicit
grid-row**, otherwise the footer overlaps the contact details. - Two menus on a page: tie each button to its menu. Put the button inside its menu, give it
aria-controlswith the id of the menu, or keep it in the samesectionas the menu. A Webflow paste keeps neither the id noraria-controls, so two pasted menus are paired by their sections. A menu left without a button says so in the console and cannot be opened. - **
panelsdoes nothing when the markup has its own panels.**snippet.htmlholds four: to change the count, add or remove.curtainboxes, or drop the container and let the setting build them.
- **Settings handed to
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
Fullscreen curtain menu is an animated navigation component for Webflow and vanilla JavaScript, built on GSAP 3. Also called fullscreen curtain menu animation, full screen menu or fullscreen menu.
Fullscreen menu triggered by a burger. On opening, four pale panels fall from the top in a cascade, the burger turns into a cross, then the links rise out of a mask at large size in the right-hand column; the contact details (mono // labels) sit in the bottom left, the footer aligns the copyright with the links column with a system line on the left. Closing is an accelerated rewind. Ink on cream, the ink #15100c runs through everything: hovers, the blinking caret in the eyebrow, the OK words in the status lines, selection. No glow. The terminal layer stays grey, the accent only underlines its end: that is what integrates it instead of detaching it.
Updated
Questions
Paste the component on the canvas, then change its settings as custom attributes on the menu element, for instance data-curtain-menu-fall="0.7". Add Geist and Geist Mono to the site's fonts before pasting, because the Designer drops a font the site does not have.
No plugin, only the GSAP 3 core. The demo and the Webflow embed name version 3.12.5 from the CDN, and on Webflow the engine waits for the copy Webflow injects instead of loading a second one. If gsap has not arrived after ten seconds, it warns in the console and the menu stays hidden.
Set data-curtain-menu-fall, the time one panel takes to drop, 0.55 seconds by default: lower and the curtain snaps shut, higher and the fall is slow and heavy. data-curtain-menu-fall-stagger, 0.07 by default, is the delay between two panels, and 0 drops them all at once.
Yes to both, by default: escape and closeOnLink are true. Write data-curtain-menu-close-on-link="false" for links that open elsewhere. The engine also keeps aria-expanded on the burger in step and switches its label between Open menu and Close menu.
Add or remove boxes inside the element marked data-curtain-menu-panels: the shipped markup holds four. The panels setting, 4 by default and 1 to 8, only counts when that container is left out and the script builds the panels itself.