Push menu
A full-screen menu that does not cover the page: it is behind it.
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 tutorialOn a site of your own, three steps. 1. Paste: one section arrives, Push menu section, which holds the block Page: move all your sections in here, the menu (Menu) and the Embed. 2. In the Navigator, drag every section of your page into that Page block, navbar and footer included, and delete the example screens. 3. Leave the menu and the Embed where they are, beside the Page block, never inside it. Whatever stays outside the block, a navbar or a footer placed straight in the Body, does not go back with the card and stays over the menu. The Body itself cannot be the page, since the menu has to sit outside it. The Page block carriesdata-push-menu-pageand needs the background of your page: behind it there is the menu, which keeps an opaque background of its own. -
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-push-menu Goes on the menu, the nav placed after the page, and takes no value: leave the value field empty. It marks the block the engine mounts on, which carries the settings and the state, and which becomes the layer under the page. Without it on any element, the embed marks the nav around the first link marked data-push-menu-link itself. data-push-menu-page Marks the page: the one wrapper around everything a visitor sees but the menu, header and footer included. It is the box that is set back as a card. It sits right before the menu, never around it. Without it the menu is not mounted, and the console says so. [ Required ] data-push-menu-toggle Marks the button that opens the menu, anywhere on the page, as a rule in its header: a link to the id of the menu, which the script turns into a button with aria-expanded and aria-controls. Without it nothing opens the menu but JavaScript. [ Required ] data-push-menu-close Marks the button of the menu that closes it. A button that lives in the page is out of reach once the page is a card, so the menu has its own. Without it the engine builds one, first in the menu. [ Optional ] data-push-menu-link Marks what arrives one after the other when the menu opens, and what takes the accent under the pointer: the links as a rule. A link to a section of the page, #paper for one, also shows that section in the card. Without it on anything, every link of the menu is taken. [ Optional ] data-push-menu-backing Marks the box that wears the shadow of the card and takes the click on it: one empty block, last in the menu. Its shadow is its class. Without it the engine builds one. [ Optional ] data-push-menu-state Written by the engine on the menu and on the page: closed, opening, open or closing. The stylesheet hides and shows the menu from it. Never written by hand. [ 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-push-menu-accent #ffab1a Colour of the link under the pointer or the keyboard, written on the menu as --push-menu-accent. Options: any CSS colour; set it empty and a --push-menu-accent of the stylesheet takes over. data-push-menu-side right Side of the window the page is set down on. The links come out from that side, and the card leans to face the other. Options: right; left, and the blocks of the menu are sent to the right side of the window. data-push-menu-scale 0.72 Size of the page once it is a card, as a share of the window, on a wide window. data-push-menu-shift 44vw How far the card travels sideways from the centre of the window, any CSS length, on a wide window. Part of the card leaves the window: that is what frees the room for the links. data-push-menu-drop 0vh How far the card travels down, any CSS length, on a wide window. data-push-menu-tilt 9 Lean of the card in perspective, in degrees, around its upright axis. It passes its angle and settles as the card lands. data-push-menu-radius clamp(14px, 1.8vw, 28px) Corner radius of the card on screen, any CSS length. data-push-menu-open 1.05 Time the page takes to be lifted, carried aside and set down, in seconds; the links are timed from it. data-push-menu-close 0.8 Time the page takes to come back, in seconds, and to reach the section of the link that was chosen. data-push-menu-ease power4.inOut Shape of the sideways travel, a GSAP ease name. The lift and the lean keep their own. Options: any GSAP ease; inOut reads as a sheet carried, an out ease as a sheet thrown. data-push-menu-travel clamp(28px, 6vw, 96px) Distance a link arrives from, any CSS length: it starts that far towards the card, under it, and slides to its place. data-push-menu-stagger 0.06 Delay between two links arriving, in seconds. data-push-menu-peek true Whether a link to a section of the page shows that section in the card while the pointer or the keyboard is on it. Leaving the links brings back the place the visitor was at. Options: true; false, and the card only moves when a link is chosen. data-push-menu-breakpoint 767 Widest window, in px, that gets the phone card: phoneScale, phoneShift and phoneDrop instead of scale, shift and drop. data-push-menu-phone-scale 0.74 Size of the card on a phone, as a share of the window. data-push-menu-phone-shift 52vw Sideways travel of the card on a phone, any CSS length. data-push-menu-phone-drop 24vh Downward travel of the card on a phone, any CSS length: it brings the card to the lower corner, under the links and within reach of a thumb. data-push-menu-escape true Whether Escape closes the menu and hands the focus back to the button. Options: true; false. data-push-menu-close-on-page true Whether a click or a tap on the card brings the page back. The card also rises under the pointer to say so. Options: true; false, and the card is only a picture. data-push-menu-close-on-link true Whether choosing a link closes the menu. A link to a section of the page then scrolls there while the page comes back. Options: true; false, and a link to a section only shows it in the card. data-push-menu-label-open Menu Text of a button placed outside the page while the menu is closed. A button inside the page travels with the card and keeps its text; nothing is written on a button that holds an element. Options: any text. data-push-menu-label-close Close Text of that button while the menu is open, and of the Close button the engine builds when the menu has none. Options: any text.
Two files, no build step. It also needs GSAP 3 on the page for the motion, loaded with the tag in step 1. Without it the menu still opens and closes, at once and with no motion.
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
PushMenu.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-push-menu-accent #ffab1a Colour of the link under the pointer or the keyboard, written on the menu as --push-menu-accent. Options: any CSS colour; set it empty and a --push-menu-accent of the stylesheet takes over. data-push-menu-side right Side of the window the page is set down on. The links come out from that side, and the card leans to face the other. Options: right; left, and the blocks of the menu are sent to the right side of the window. data-push-menu-scale 0.72 Size of the page once it is a card, as a share of the window, on a wide window. Under 0.5 the page is a thumbnail and nothing in it can be read Past 0.85 the card hides most of the menu and no longer reads as set back data-push-menu-shift 44vw How far the card travels sideways from the centre of the window, any CSS length, on a wide window. Part of the card leaves the window: that is what frees the room for the links. Under 25vw the card covers the links Past 60vw only a strip of the page is left in view data-push-menu-drop 0vh How far the card travels down, any CSS length, on a wide window. At 0 the card stays level with the centre Past 20vh its foot leaves the window data-push-menu-tilt 9 Lean of the card in perspective, in degrees, around its upright axis. It passes its angle and settles as the card lands. At 0 the card is flat, a plain scaled page Past 18 the text of the page is hard to read and the card looks like a door data-push-menu-radius clamp(14px, 1.8vw, 28px) Corner radius of the card on screen, any CSS length. At 0 the card keeps the square corners of the window Past 48px the corners eat a sticky header data-push-menu-open 1.05 Time the page takes to be lifted, carried aside and set down, in seconds; the links are timed from it. Under 0.6 the three moves merge into one jump Past 1.5 the menu feels slow to answer data-push-menu-close 0.8 Time the page takes to come back, in seconds, and to reach the section of the link that was chosen. Under 0.5 a long scroll to a far section is a blur Past 1.2 the page is slow to become usable again data-push-menu-ease power4.inOut Shape of the sideways travel, a GSAP ease name. The lift and the lean keep their own. Options: any GSAP ease; inOut reads as a sheet carried, an out ease as a sheet thrown. data-push-menu-travel clamp(28px, 6vw, 96px) Distance a link arrives from, any CSS length: it starts that far towards the card, under it, and slides to its place. At 0 the links only fade in Past 160px the links cross the window rather than come out from under the card data-push-menu-stagger 0.06 Delay between two links arriving, in seconds. At 0 every link arrives together Past 0.12 the last link of a long menu arrives well after the card has landed data-push-menu-peek true Whether a link to a section of the page shows that section in the card while the pointer or the keyboard is on it. Leaving the links brings back the place the visitor was at. Options: true; false, and the card only moves when a link is chosen. data-push-menu-breakpoint 767 Widest window, in px, that gets the phone card: phoneScale, phoneShift and phoneDrop instead of scale, shift and drop. Under 600 a large phone held sideways gets the wide card Past 991 a small laptop gets the phone card data-push-menu-phone-scale 0.74 Size of the card on a phone, as a share of the window. Under 0.5 the card is too small to aim at Past 0.85 it covers the links data-push-menu-phone-shift 52vw Sideways travel of the card on a phone, any CSS length. Under 40vw the card covers the links Past 70vw the strip left in view is too thin for a thumb data-push-menu-phone-drop 24vh Downward travel of the card on a phone, any CSS length: it brings the card to the lower corner, under the links and within reach of a thumb. At 0 the card stays beside the links, across the whole height Past 45vh only its top corner is left data-push-menu-escape true Whether Escape closes the menu and hands the focus back to the button. Options: true; false. data-push-menu-close-on-page true Whether a click or a tap on the card brings the page back. The card also rises under the pointer to say so. Options: true; false, and the card is only a picture. data-push-menu-close-on-link true Whether choosing a link closes the menu. A link to a section of the page then scrolls there while the page comes back. Options: true; false, and a link to a section only shows it in the card. data-push-menu-label-open Menu Text of a button placed outside the page while the menu is closed. A button inside the page travels with the card and keeps its text; nothing is written on a button that holds an element. Options: any text. data-push-menu-label-close Close Text of that button while the menu is open, and of the Close button the engine builds when the menu has none. Options: any text. -
Drive it from JavaScript
When something other than the button has to open or close the menu, a key or a call to action for one, or when the page reacts to it.const element = document.querySelector('[data-push-menu]'); element.addEventListener('pushmenu:open', (e) => { console.log(e.detail); }); const menu = PushMenu.mount(element); // the nav auto() mounted, or a new one menu.open(); // and menu.close(), menu.toggle(), menu.isOpen() element.addEventListener('pushmenu:close', (e) => { console.log('closing', e.detail.menu); }); // When the menu leaves the page: menu.destroy(); // brings the page back at once and gives the markup back as it was
-
Fit it to your page
- Two parts, side by side. The page is one wrapper,
data-push-menu-page, around everything a visitor sees but the menu: header, content, footer. The menu,data-push-menu, is anavplaced right after it, not inside it. That is the whole structure, and the only thing to do on an existing site: wrap the page, add the nav. - On a site of your own, the whole page goes in the wrapper: header, navbar, every section and the footer. Whatever is left outside it, straight in
body, does not go back with the card and stays over the menu.bodyitself cannot carrydata-push-menu-page, since the menu has to sit outside the page. - On a real Webflow site, three steps. Paste: the section that arrives,
Push menu section, holds a block namedPage: move all your sections in here, the menu and the Embed. In the Navigator, drag every section of your page into that block, navbar and footer included, and delete the example screens. Leave the menu and the Embed beside the block, as its siblings, never inside it. - The content is yours. Any number of links, any markup inside the nav, any page inside the wrapper.
data-push-menu-linkmarks what arrives one after the other; without it every link of the menu is taken. - The button carries
data-push-menu-toggleand can be anywhere, as a rule in the header of the page. Make it a link to the id of the menu,href="#menu": with the script it becomes a button,role="button",aria-expandedandaria-controls; without it, it goes to the menu. - Close is a button or a link of the menu marked
data-push-menu-close. A button that lives in the page is out of reach once the page is a card, so the menu needs its own; when it has none the engine builds one, first in the menu, with the text oflabelClose. - The page needs a background of its own, and fills the window at least: behind it there is the menu, not the body. The stylesheet proposes
min-height: 100vh,position: relativeand az-indexof 1 on it. - Sticky and fixed boxes of the page are held where they were. A sticky header needs nothing. A fixed header, a fixed button in a corner, a fixed bar are found at each opening and kept at their place in the card.
- Links to a section of the page,
href="#paper", are the ones the card follows: under the pointer or the keyboard the card shows the section (peek), and choosing the link brings the page back there, the address and the browser's own position updated as a plain anchor would.scroll-margin-topon the section andscroll-padding-toponhtmlare respected. A link to another page is left to the browser. - Closing: the Close button, a click on the card, Escape, or a link. All but a link bring the page back where the visitor was.
- Keyboard: Enter or Space on the button opens the menu and moves the focus to Close; Tab walks the menu and nothing else, since the page is inert; Up, Down, Home and End walk it too; Escape closes and puts the focus back on the button.
- On a phone, at or under
breakpoint, the card goes to the lower corner:phoneScale,phoneShiftandphoneDrop. A tap on a link closes; a tap on the card brings the page back. - One setting for the whole site, without opening the file: declare
window.PushMenuSettings = { tilt: 12 }in a script placed beforepush-menu.js. The three levels, least specific first, areDEFAULTS, then that object, then adata-push-menu-*attribute on the menu itself (data-push-menu-tilt="12",data-push-menu-phone-drop="30vh"). - What the script writes:
data-push-menu-stateon the menu and on the page,closed,opening,openorclosing; on the menu,--push-menu-accentanddata-push-menu-sidewhen it was not there, which the stylesheet reads to mirror the menu. On the buttons,role,aria-expandedandaria-controls, with anidon a menu that had none. While the menu is open,inertandaria-hiddenon the page, its transform, its clip-path, a translate on each of its fixed boxes andoverflow-x: clipon its parent; all of it is taken off when the page is back. - From JavaScript:
PushMenu.mount(element)returns the instance, withopen(),close(),toggle(),isOpen(),refresh()after a layout change while the menu is open, anddestroy(). The menu emitspushmenu:openandpushmenu:close. - **
prefers-reduced-motion**: the page is set back at once, flat, and the links are simply there; closing is as direct, and the card does not follow the links. So is it when GSAP is not on the page, lean included. - Without the script the menu is the last block of the page, a plain list of links, and the button is a link that goes to it.
- The words of the markup are the preview's, and they ship as they are. Put your own in their place.
- 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.
- Two parts, side by side. The page is one wrapper,
-
Avoid the pitfalls
- The wrapper holds everything, the menu excepted. What is left outside it stays in place over the menu: a cookie bar, a chat button injected in
body. That is sometimes wanted, for a button that must stay live while the menu is open; anything else goes inside. - The page and the menu share a parent, and the page comes first. The page sits above the menu with a
z-indexof 1; a parent that makes a stacking context of its own, a transform or a filter on it, is fine as long as it holds both. - A transparent page shows the menu through it. Give the wrapper the background the body had.
- A fixed box that spans the window from top to bottom,
top: 0; bottom: 0, keeps the height it had but is no longer stretched by the window while the page is a card. Fixed boxes inside another transformed box of the page were already placed against that box, and are left alone. - A fixed box that appears while the menu is open is not held: the page is inert then, so nothing should. Call
refresh()if one does. - The scrollbar stays. Nothing is written on
htmlorbody: the wheel, the finger and the keys are refused instead, so the layout does not shift and a sticky header keeps sticking. On a system with classic scrollbars the bar remains over the menu; dragging it moves the page inside the card. - The breakpoint lives in two places when the look changes with it: the
breakpointsetting moves the card, the@media (max-width: 767px)block at the end of the stylesheet restyles the page, and in Webflow the mobile landscape breakpoint of each class. - **Settings handed to
mount()after the page loaded are dropped.**auto()mounts on DOM ready, andmount()on a menu already mounted returns the running one. A page-wide block goes inwindow.PushMenuSettings, whichauto()reads itself. - One menu per page. One page is set back by one menu; a second
data-push-menuis refused, and the console says so. - In a framework that owns the DOM, write the backing yourself, one empty
divmarkeddata-push-menu-backingas the last child of the menu, and a Close button, so the engine adds nothing; callPushMenu.destroy(el)when the menu leaves.
- The wrapper holds everything, the menu excepted. What is left outside it stays in place over the menu: a cookie bar, a chat button injected in
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
Push menu is an animated navigation component for Webflow and vanilla JavaScript, built on GSAP 3. Also called perspective push menu, page push back menu or 3D off-canvas menu.
A full-screen menu that does not cover the page: it is behind it. Opening takes the whole page as one sheet and sets it back, smaller, to one side, leaning in perspective, with round corners and a deep shadow, like a card put down on a table, and the menu is what was underneath, its links coming out from under the card one after the other in large type. The page stays in view as that card, at the place the visitor had reached, sticky and fixed headers included, and the document never jumps: it keeps its scroll and nothing is written on html or body. A link to a section of the page shows that section in the card while the pointer or the keyboard is on it; choosing it brings the page back already there. A click on the card, the Close button or Escape brings it back where it was. While the menu is open the page is inert, out of the focus order and of the accessibility tree, and cannot be scrolled; the menu is a real nav of real links, the button carries aria-expanded and aria-controls, the focus moves into the menu and returns to the button. On a phone the card drops to the lower corner, where a thumb reaches it. Under reduced motion the page is simply set back, with no travel and no lean. Without the script the menu is a plain list of links at the foot of the page, and the button is a link to it. Not the curtain menu, which covers the page, nor the anchored or the mega menu, which open a box over it: here the page itself is the moving part. Pattern type push menu, perspective navigation, off-canvas menu, page pushed back.
Updated
Questions
Two parts, side by side: one wrapper marked data-push-menu-page around everything a visitor sees, header and footer included, and after it a nav marked data-push-menu, with data-push-menu-toggle on the button that opens it. In Webflow, paste, then drag every section of your page into the block named Page: move all your sections in here, and leave the menu and the Embed beside it. Opening sets the page back as a tilted card and the menu is what was underneath.
No. The wrapper never leaves the flow: the card is the page itself, cut with a clip-path to what the window shows and transformed around the centre of the window, so the document keeps its scroll and nothing is written on html or body. A sticky header stays at the top of the card, and a fixed header or a fixed corner button is found at each opening and held where it was.
Yes, by default. A link to a section of the page, href="#paper", shows that section in the card while the pointer or the keyboard is on it, and choosing it brings the page back already there. Leaving the links brings back the place the visitor was at, and data-push-menu-peek="false" keeps the card still until a link is chosen.
GSAP 3 drives the motion, with no plugin, and Webflow's own copy is used when it is there. data-push-menu-scale, 0.72 by default, is the size of the card, data-push-menu-shift, 44vw, how far it goes aside, data-push-menu-tilt, 9 degrees, its lean, and data-push-menu-side the side it is set down on. Without GSAP or under reduced motion the page is set back at once, flat, and the links are simply there.
The menu is a real nav of real links. The button carries aria-expanded and aria-controls, opening moves the focus to Close and closing puts it back on the button, and Escape, the Close button or a click on the card brings the page back. While the menu is open the page is inert, out of the focus order and of the accessibility tree. At or under data-push-menu-breakpoint, 767 px by default, the card drops to the lower corner, within reach of a thumb.