Dock menu
A site navigation that lives as a small floating bar at the foot of the window: the name of the site, the name of the section on screen, a button.
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 dock is styled open: give its class the size of the sheet (a max-width and a max-height), the left, right and bottom that keep it off the edges, and a radius of half the height of the bar so the closed bar is a pill. Nothing above it may carry a transform or a filter, or it stops being fixed to the window. Leave a padding at the foot of the page for the bar. -
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-dock-menu Goes on the dock, a nav, and takes no value: leave the value field empty. It marks the block the engine mounts on: the one surface that is the bar when closed and the sheet when open, and which carries the settings and the state. Without it on any element of the page, the embed marks the nav around the first bar itself. data-dock-menu-sheet Marks the sheet, the first block of the dock: everything the menu shows, your links, lists, pictures. It is folded away while the dock is closed. Without it the dock is not mounted, and the console says so. [ Required ] data-dock-menu-bar Marks the bar, the second and last block of the dock: what is left on screen while it is closed, and the foot of the sheet while it is open. Each of its direct children is an item that swells under the pointer. Without it the dock is not mounted. [ Required ] data-dock-menu-toggle Marks the button of the bar that opens and closes the sheet. The engine gives it aria-expanded and aria-controls. Without it the dock is not mounted. [ Required ] data-dock-menu-current Marks the box of the bar that shows the name of the section on screen. What it says in the markup is shown before the first section. Without it the bar names nothing; the links are still marked. [ Optional ] data-dock-menu-link Marks what arrives one after the other in the sheet, the links as a rule. A link that points at an id of the page, #paper, also makes that element a section the bar names with the words of the link. Without it on anything in the sheet, every link of the sheet is taken. [ Optional ] data-dock-menu-name Marks, inside a link, the box whose words name the section in the bar. Without it the last box of the link is taken, or the text of the link. [ Optional ] data-dock-menu-block Marks a block of the sheet that arrives as a whole after the links: a featured block, a row of secondary links. Without it such a block is simply there from the first frame. [ Optional ] data-dock-menu-feature Marks the frame of a picture, a box holding an image: the frame is uncovered from its foot while the picture settles to its size. Without it nothing is uncovered. [ Optional ] data-dock-menu-label Marks, inside the button, the box whose text the engine rewrites with labelOpen and labelClose. Without it the text of the button is rewritten only when the button holds no element. [ Optional ] data-dock-menu-line Marks each of the two lines of the button's icon, which cross while the sheet is open. Without it the icon does not change. [ Optional ] data-dock-menu-section="Name" Goes on an element of the page, not of the dock: makes it a section the bar names with this value, for a section no link of the dock points at, or to name one otherwise than its link does. [ Optional ] data-dock-menu-state, data-dock-menu-mode, data-dock-menu-hidden, data-dock-menu-morph Written by the engine on the dock: the state, closed, opening, open or closing; the mode, desktop or phone; hidden while the bar is away on a scroll down; morph while the surface travels. The stylesheet lays the dock out from them. Never written by hand. [ Optional ] data-dock-menu-window, data-dock-menu-word, data-dock-menu-backdrop Built by the engine: the window and the word inside the section name, and the scrim right before the dock. 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-dock-menu-accent #ffab1a Colour of the dot that marks the section on screen, before its name in the bar and at the end of its link in the sheet, and of the keyboard ring of the button; written on the dock as --dock-menu-accent. Options: any CSS colour; an empty string in the settings block writes nothing and leaves the --dock-menu-accent of your stylesheet. data-dock-menu-position center Where the bar docks along the foot of the window, and the side the sheet grows from. Options: center, the sheet grows both ways; left or right, the bar sits in that corner and the sheet grows away from it. data-dock-menu-open 0.8 Time the bar takes to grow into the sheet, in seconds; the links, the blocks and the picture are timed from it. data-dock-menu-close 0.5 Time the whole closing takes, in seconds: the content leaves in the first third, then the surface shrinks back into the bar. data-dock-menu-ease power3.out Shape of the growth, a GSAP ease name. Options: any GSAP ease; an out ease answers at once and settles, inOut starts late under the finger, back.out(1.2) overshoots the size of the sheet. data-dock-menu-stagger 0.045 Delay between two links arriving, in seconds. data-dock-menu-rise clamp(14px, 2.4vh, 24px) Height a link comes up from as it arrives, any CSS length; the blocks rise from a little over half of it. data-dock-menu-scrim 0.5 Darkness of the page behind the open sheet, from 0 to 1. The scrim also takes the clicks made outside. data-dock-menu-track true Whether the bar names the section on screen and marks its link. Options: true; false, the bar keeps the words written in the markup. data-dock-menu-line 0.4 Height of the window, from 0 at the top to 1 at the foot, that the top of a section has to reach for the bar to name it. data-dock-menu-swap 0.5 Time the section name takes to change, in seconds: the old word rolls out, the new one in, and the bar goes to the width of the new word. data-dock-menu-hide true Whether the closed bar slides away on a scroll down and comes back on a scroll up. It always stays while the sheet is open, while a link carries the page and while the keyboard is in it, and it comes back at the foot of the page. Options: true; false, the bar never leaves. data-dock-menu-hide-after 80 How far the page has to go down, in px, before the bar leaves; any scroll up brings it back at once. data-dock-menu-smooth true Whether a link of the dock that points at a section of the page glides there, instead of jumping. Under reduced motion it always jumps. Options: true; false, the browser's own jump, or the smooth scrolling the page already has. data-dock-menu-magnify 0.08 How much an item of the closed bar swells while the pointer of a mouse is on it, as a ratio of its size; its neighbours swell less, by their distance. Touch and reduced motion get none. data-dock-menu-reach clamp(80px, 8vw, 130px) How far along the bar the swell is felt, any CSS length: the distance at which an item gets about a third of it. data-dock-menu-breakpoint 767 Widest window, in px, that gets the phone sheet: the page behind is held and the focus stays in the dock. The look of the preview changes at the same width, 767, in the stylesheet. data-dock-menu-escape true Whether Escape closes the menu and hands the focus back to the button. Options: true; false. data-dock-menu-close-on-outside true Whether a click outside the dock closes it. Options: true; false. data-dock-menu-close-on-link true Whether a click on a link of the dock closes the sheet, for a link that stays on the page. Options: true; false. data-dock-menu-label-open Menu Text of the button while the menu is closed, written in the box marked data-dock-menu-label, or in the button itself when it holds no element. Options: any text. data-dock-menu-label-close Close Text of the button while the menu is open. Options: any text.
Two files and their pictures (1), no build step. It also needs GSAP 3 on the page for the motion, loaded with the tag in step 1. Without it the dock still opens, closes, names the section and hides on a scroll, the sheet and the name changing at once.
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
DockMenu.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-dock-menu-accent #ffab1a Colour of the dot that marks the section on screen, before its name in the bar and at the end of its link in the sheet, and of the keyboard ring of the button; written on the dock as --dock-menu-accent. Options: any CSS colour; an empty string in the settings block writes nothing and leaves the --dock-menu-accent of your stylesheet. data-dock-menu-position center Where the bar docks along the foot of the window, and the side the sheet grows from. Options: center, the sheet grows both ways; left or right, the bar sits in that corner and the sheet grows away from it. data-dock-menu-open 0.8 Time the bar takes to grow into the sheet, in seconds; the links, the blocks and the picture are timed from it. Under 0.5 the growth is not read and the links arrive all at once Past 1.1 the menu feels slow to answer the button data-dock-menu-close 0.5 Time the whole closing takes, in seconds: the content leaves in the first third, then the surface shrinks back into the bar. Under 0.3 the sheet vanishes with no travel Past 0.8 the sheet lingers over the page after the choice is made data-dock-menu-ease power3.out Shape of the growth, a GSAP ease name. Options: any GSAP ease; an out ease answers at once and settles, inOut starts late under the finger, back.out(1.2) overshoots the size of the sheet. data-dock-menu-stagger 0.045 Delay between two links arriving, in seconds. At 0 every link arrives together Past 0.08 the last link of a long list is still arriving after the surface has landed data-dock-menu-rise clamp(14px, 2.4vh, 24px) Height a link comes up from as it arrives, any CSS length; the blocks rise from a little over half of it. At 0 the links only fade in Past 40px the links jump rather than settle data-dock-menu-scrim 0.5 Darkness of the page behind the open sheet, from 0 to 1. The scrim also takes the clicks made outside. At 0 no scrim is built and the page stays as bright as it was Past 0.7 the page is gone and the sheet floats on black data-dock-menu-track true Whether the bar names the section on screen and marks its link. Options: true; false, the bar keeps the words written in the markup. data-dock-menu-line 0.4 Height of the window, from 0 at the top to 1 at the foot, that the top of a section has to reach for the bar to name it. Under 0.2 a section is named late, once it nearly fills the window Past 0.6 it is named while the one before still fills most of the window data-dock-menu-swap 0.5 Time the section name takes to change, in seconds: the old word rolls out, the new one in, and the bar goes to the width of the new word. Under 0.25 the name blinks from one word to the next Past 0.8 a fast scroll leaves the bar a section behind data-dock-menu-hide true Whether the closed bar slides away on a scroll down and comes back on a scroll up. It always stays while the sheet is open, while a link carries the page and while the keyboard is in it, and it comes back at the foot of the page. Options: true; false, the bar never leaves. data-dock-menu-hide-after 80 How far the page has to go down, in px, before the bar leaves; any scroll up brings it back at once. Under 30 a nudge of the trackpad hides the bar Past 300 the bar is still there well into the reading data-dock-menu-smooth true Whether a link of the dock that points at a section of the page glides there, instead of jumping. Under reduced motion it always jumps. Options: true; false, the browser's own jump, or the smooth scrolling the page already has. data-dock-menu-magnify 0.08 How much an item of the closed bar swells while the pointer of a mouse is on it, as a ratio of its size; its neighbours swell less, by their distance. Touch and reduced motion get none. At 0 nothing swells Past 0.2 the words overlap and the button leaves the bar data-dock-menu-reach clamp(80px, 8vw, 130px) How far along the bar the swell is felt, any CSS length: the distance at which an item gets about a third of it. Under 40px only the item under the pointer moves Past 200px the whole bar swells as one data-dock-menu-breakpoint 767 Widest window, in px, that gets the phone sheet: the page behind is held and the focus stays in the dock. The look of the preview changes at the same width, 767, in the stylesheet. Under 600 a small tablet keeps the page scrolling behind a sheet that nearly covers it Past 767 change the @media of the look to match, or the sheet stacks at another width than it locks data-dock-menu-escape true Whether Escape closes the menu and hands the focus back to the button. Options: true; false. data-dock-menu-close-on-outside true Whether a click outside the dock closes it. Options: true; false. data-dock-menu-close-on-link true Whether a click on a link of the dock closes the sheet, for a link that stays on the page. Options: true; false. data-dock-menu-label-open Menu Text of the button while the menu is closed, written in the box marked data-dock-menu-label, or in the button itself when it holds no element. Options: any text. data-dock-menu-label-close Close Text of the button while the menu is open. Options: any text. -
Drive it from JavaScript
When something other than the button has to open the menu, a key or a call to action for one, or when the page reacts to the section on screen.const element = document.querySelector('[data-dock-menu]'); element.addEventListener('dockmenu:section', (e) => { console.log(e.detail); }); const dock = DockMenu.mount(element); // the dock auto() mounted, or a new one dock.open(); // dock.close(), dock.toggle() dock.section(); // the index of the section on screen, -1 before the first element.addEventListener('dockmenu:section', (e) => { console.log(e.detail.index, e.detail.name); }); // When the dock leaves the page: dock.destroy(); // closes at once and gives the markup back as it was
-
Fit it to your page
- Structure: the dock, a
nav, carriesdata-dock-menuand holds two blocks in this order: the sheet,data-dock-menu-sheet, then the bar,data-dock-menu-bar. That order is the one on screen, the sheet over the bar, and the one the keyboard follows. In the bar,data-dock-menu-togglemarks the button anddata-dock-menu-currentthe box of the section name. - Make it yours: write your links. Everything in the sheet is your own markup, any number of links, lists, pictures or blocks: the sheet takes their size and the bar grows to it.
data-dock-menu-linkmarks what arrives one after the other,data-dock-menu-blockwhat follows as a whole (a featured block, a row of secondary links) anddata-dock-menu-featurethe frame of a picture, a box holding animg, uncovered from its foot. All three are optional: without the first, every link of the sheet is taken. - The sections name themselves. Every link of the dock that points at an id of the page,
href="#paper", makes that element a section, and the bar shows the words of the link while the section is on screen: nothing else to write. The words are those of the box markeddata-dock-menu-namein the link, or of its last box, or the link's own text. Whatdata-dock-menu-currentsays in the markup is shown before the first section. A section with no link in the dock is added withdata-dock-menu-section="Its name"on the element. On a site of several pages, where the links leave the page, write the name of the page indata-dock-menu-currentand it stays. - The dock is styled open. Its class gives the size of the sheet: a
max-width, amax-height, and theleft,rightandbottomthat keep it off the edges of the window. Closed, it is as wide as what its bar holds. Give it a radius of half the height of the bar and the closed bar is a pill; give the closed state another radius ([data-dock-menu-state="closed"]) and the engine travels between the two. - Position:
data-dock-menu-position="left"or"right"docks the bar in a corner and grows the sheet from there. - The button carries
aria-expandedandaria-controls, written by the engine. Its text is the box markeddata-dock-menu-label, which the engine rewrites withlabelOpenandlabelClose; the two lines of its icon,data-dock-menu-line, cross while the sheet is open. - Focus and keys: opening moves the focus to the first link of the sheet; closing puts it back on the button. Escape closes, and so does a click outside. On a wide window nothing is trapped and the page still scrolls: Tab out of the dock closes the sheet, into the page or past its last link into the browser's own bar. On a phone, at or under
breakpoint, Tab goes round inside the dock and the page behind is held. - Scroll: the closed bar slides under the foot of the window once the page has gone down
hideAfter, and comes back on any scroll up, at the foot of the page and when the keyboard enters it. A link of the dock carries the page to its section with the bar in place. - Safe area: the look places the dock with
env(safe-area-inset-*), so it clears the home indicator of a phone. The insets are only given to a page whose viewport tag saysviewport-fit=cover. - One setting for every dock of the site, without opening the file: declare
window.DockMenuSettings = { open: 1 }in a script placed beforedock-menu.js. The three levels, least specific first, areDEFAULTS, then that object, then adata-dock-menu-*attribute on the dock itself (data-dock-menu-open="1",data-dock-menu-hide-after="160"). - What the script writes: on the dock,
data-dock-menu-state,closed,opening,openorclosing,data-dock-menu-mode,desktoporphone,data-dock-menu-position,data-dock-menu-hiddenwhile the bar is away,data-dock-menu-morphand a width, a height and three custom properties while the surface travels, all taken off as it lands, and--dock-menu-accent. On the link of the section on screen,aria-current="location". In the box of the section name, a window and a word,data-dock-menu-windowanddata-dock-menu-word. Right before the dock, the scrim,data-dock-menu-backdrop. - From JavaScript:
DockMenu.mount(element)returns the instance, withopen(),close(),toggle(),isOpen(),section(), the index of the section on screen or -1,show()andhide()for the bar,mode(),refresh()after links or sections were added, anddestroy(). The dock emitsdockmenu:open,dockmenu:close,dockmenu:sectioneach time the section on screen changes,dockmenu:hideanddockmenu:show. - **
prefers-reduced-motion**: the sheet is there at once, the name changes with no roll, the bar leaves and comes back with no slide, nothing swells and a link jumps to its section. So is it when GSAP is not on the page, save for the slide and the swell, which are the stylesheet's. - Without the script the dock is not fixed: it stays where the markup puts it, at the foot of the page, and shows the sheet and the name of the site as a plain list of links. The button and the section name are away.
- The words and the picture 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.
- Structure: the dock, a
-
Avoid the pitfalls
- **Nothing above the dock may carry a
transform, afilteror aperspective.** The dock is fixed to the window; an ancestor with one of those becomes what it is fixed to, and the bar scrolls away with the page. - The sheet comes before the bar in the markup. Written the other way round, the bar sits on top and the surface grows downwards out of the window.
- The dock itself takes no padding you count on. The bar is its last block and the sheet the one before; a padding on the dock is kept, but the air around the content belongs on the sheet and on the bar.
- Leave room at the foot of the page. The bar floats over the last lines of the page: give the page a
padding-bottomof the height of the bar and its distance to the edge, as the preview does. - A section too short to reach the line is still named once the page is at its foot. In between, the section before it keeps the name: lower
line, or give the section more height. - On the canvas of the Webflow Designer the dock shows open, fixed at the foot of the canvas, since its class is the open sheet and the Designer does not run the code that folds it. That is where the links are edited; the published page folds it.
- In Webflow the button is a Link block, since the Designer has no button element that holds children: it keeps
role="button", and the paste carries the few lines that let Space press it. - The breakpoint lives in two places.
breakpointswitches the behaviour; the phone look of the preview is a@media (max-width: 767px)block at the end of the stylesheet, and in Webflow the mobile landscape breakpoint of each class. Move one and move the other. - Smooth scrolling of your own. With a scroll library that owns the page, set
smoothtofalseand scroll to the section yourself onclick; the bar still follows the sections. - **Settings handed to
mount()after the page loaded are dropped.**auto()mounts on DOM ready, andmount()on a dock that already carries the menu returns the running one. A page-wide block goes inwindow.DockMenuSettings, whichauto()reads itself. - Links or sections added after the mount are not seen until
refresh().
- **Nothing above the dock may carry a
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
Dock menu is an animated navigation component for Webflow and vanilla JavaScript, built on GSAP 3. Also called floating bottom navigation bar, expanding bottom navigation or dock navigation menu.
A site navigation that lives as a small floating bar at the foot of the window: the name of the site, the name of the section on screen, a button. As the page scrolls the bar names the section that passes, the old name rolling out as the new one rolls in and the bar taking the width of the word; it slides away on a scroll down and comes back on a scroll up. Pressed, it does not pop a panel: the bar itself grows, width, height and radius, anchored on its bottom edge, into the sheet that holds the navigation. The content sits still while the edge of the surface uncovers it, the links come up one after the other once there is room, the picture is uncovered, and closing plays the travel back, the content leaving first. While the pointer runs along the closed bar its items swell slightly towards it. It is a real nav of real links: the button carries aria-expanded, the focus goes into the sheet and back to the button, Escape and a click outside close, on a phone the focus stays inside and the page behind is held, and the dock keeps clear of the home indicator. Under reduced motion the sheet is simply there. Without the script the dock is a plain list of links at the foot of the page. Not the mega menu, whose panel travels along a header, nor the anchored menu, a box in a corner that unfolds: here the navigation is the bar at the bottom, aware of where the page is. Pattern type dock navigation, floating bottom bar, morphing pill menu, scroll spy.
Updated
Questions
Put data-dock-menu on a nav that holds two blocks in this order: the sheet, data-dock-menu-sheet, then the bar, data-dock-menu-bar, with data-dock-menu-toggle on its button. Everything in the sheet is your own markup, and its class gives the open size with a max-width and a max-height. Pressed, the bar itself grows, width, height and radius, into that sheet.
From its own links. Every link of the dock that points at an id of the page, href="#paper", makes that element a section, and the box marked data-dock-menu-current shows the words of the link while the section is on screen. A section with no link is added with data-dock-menu-section on the element, and data-dock-menu-track="false" turns the naming off.
It does by default. The closed bar slides under the foot of the window once the page has gone down data-dock-menu-hide-after, 80 px by default, and comes back on any scroll up and at the foot of the page. data-dock-menu-hide="false" keeps it in place.
GSAP 3 drives the growth, with no plugin: data-dock-menu-open, 0.8 s by default, is the time the bar takes to grow into the sheet and data-dock-menu-ease its shape. Under reduced motion the sheet is simply there, and without the script the dock is a plain list of links at the foot of the page.
Yes. The button carries aria-expanded and aria-controls, opening moves the focus to the first link of the sheet and closing puts it back on the button, and Escape or a click outside closes. On a phone, at or under data-dock-menu-breakpoint, 767 px by default, Tab goes round inside the dock and the page behind is held. The look places the dock with env(safe-area-inset-*), so it clears the home indicator.