webdevpuneet.com

Drag to Reveal Side Panel — Free Edge-Drag JS Snippet

Drag to Reveal Side Panel · Navigation · Plain HTML, CSS & JS · Live preview

CategoryNavigation

Copy & paste

Source Code

<div class="drp-stage" id="drpStage">
  <div class="drp-content">
    <h2>Main content</h2>
    <p>Grab the tab on the left edge and drag it inward to reveal the panel. It follows your drag distance live \u2014 release partway and it snaps fully open or fully closed.</p>
  </div>

  <div class="drp-panel" id="drpPanel">
    <h3>Side panel</h3>
    <p>Navigation, filters, or settings live here. Drag the handle back toward the edge to close it, or drag it inward again to fully open.</p>
    <ul>
      <li>Dashboard</li>
      <li>Projects</li>
      <li>Settings</li>
    </ul>
  </div>

  <button type="button" class="drp-handle" id="drpHandle" aria-label="Drag to reveal panel">
    <span class="drp-grip"></span>
  </button>

  <div class="drp-scrim" id="drpScrim"></div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Single-offset source of truth
One value drives the panel, handle, and scrim.
Live drag tracking
Partial drags show a genuinely partial reveal.
Threshold-based snap
Release position decides open vs closed.
Eased settle animation
Only the final snap transitions, not the live drag.
Tap-to-toggle fallback
A near-zero drag still toggles the panel.
Proportional scrim
Backdrop opacity tracks reveal fraction exactly.
Unified mouse + touch
One pointerX() helper drives both input types.
Click-outside close
Tapping the scrim closes an open panel.

About this UI Snippet

Drag to Reveal Side Panel — Live Drag Distance, Snap on Release

Screenshot of the Drag to Reveal Side Panel snippet rendered live

This is a genuine drag-to-reveal gesture, not a click-toggled sidebar — dragging the edge handle partway shows the panel partway revealed in real time, and releasing snaps it fully open or fully closed based on how far you dragged. It is a different mechanic from drag-to-resize panels, which resizes two permanently-visible adjacent panes; here, one pane starts fully hidden and the drag's only job is to reveal or re-hide it.

The offset is the single source of truth

Exactly one number, currentOffset, drives everything: it ranges from -PANEL_WIDTH (fully hidden, panel translated completely off-screen) to 0 (fully revealed), and applyOffset() is the only function that ever sets it. It positions the panel's transform: translateX, moves the handle so it always sits at the panel's current right edge, and derives the scrim's opacity as revealFraction * 0.45 — so the backdrop dims proportionally to how far the panel has been dragged, not in a separate step that could drift out of sync.

Live tracking, not a toggle

onDragMove computes dx, the distance moved since the drag started, and calls applyOffset(startOffset + dx, false) on every single pointer move — the false disables the CSS transition during the drag so the panel's position is pinned exactly to the pointer with no lag or easing, which is what makes a partial drag show a genuinely partial reveal rather than a threshold-triggered snap happening mid-gesture.

Threshold-based snap on release

onDragEnd computes how far open the panel is as a fraction (revealFraction) and compares it to SNAP_THRESHOLD (0.4): past 40% open, it settles fully open; short of that, it settles fully closed. settle() re-enables the transition (withTransition: true) so this final snap animates smoothly, unlike the frame-by-frame drag itself.

A tap still works as a toggle

If the handle receives a click with almost no actual drag distance, it toggles the panel outright — so the control works both as a drag gesture and as a conventional tap-to-toggle affordance, covering visitors who don't realize (or don't want) to drag.

Customizing it

Change PANEL_WIDTH and SNAP_THRESHOLD, move the handle and panel to the right edge by mirroring the transform math, or drop the scrim for a push-content layout instead of an overlay. Pair it with swipe delete list or elastic drag slider for more drag-distance-driven interactions.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA hidden panel and edge handle render over the content.
  2. 2
    Drag the handle inwardThe panel slides open live, tracking your drag distance.
  3. 3
    Release past the halfway pointIt snaps fully open with an eased animation.
  4. 4
    Release before thatIt snaps back fully closed instead.
  5. 5
    Tap the scrim or drag backEither closes an open panel.
  6. 6
    Tune the feelChange PANEL_WIDTH and SNAP_THRESHOLD.

Real-world uses

Common Use Cases

Mobile navigation drawers
Offer an edge-drag alternative to a hamburger menu, where dragging partway reveals the panel partway in real time.
Filter and settings panels
Reveal filters without a permanent sidebar, with a threshold on release deciding whether the panel snaps open or closed.
Notification trays
Drag open a tray instead of using a click-only button, with one offset value driving the panel, handle and scrim.
Resize panel contrast
Contrast with drag resize panels, which keep a panel visible and change its size rather than revealing it.
Touch-first kiosk UIs
Provide a natural gesture for tablets and kiosks, transitioning only the final snap so live dragging stays responsive.
Related: Floating Share Dock
See the Floating Share Dock for a related navigation pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Every pointermove-equivalent event computes dx, the distance moved since the drag started, and calls applyOffset(startOffset + dx, false) with the transition disabled. Because this runs on every single move event with no threshold check in between, the panel's on-screen position is pinned exactly to wherever the pointer currently is — a drag stopped halfway genuinely shows the panel halfway revealed, not snapped to one state or the other mid-gesture.

onDragEnd computes revealFraction, the panel's current offset expressed as a 0-to-1 fraction of PANEL_WIDTH, and compares it against SNAP_THRESHOLD (0.4 by default). If more than 40% of the panel was revealed when the drag ended, settle(true) animates it the rest of the way open; otherwise settle(false) animates it back closed. This threshold is the only thing that decides the outcome — the drag itself never snaps early.

applyOffset takes a withTransition flag. During onDragMove it's always false, so the panel's transform updates instantly and exactly matches pointer position with zero animation lag — any easing during the live drag would make the panel visibly lag behind your finger. Once the drag ends, settle() calls applyOffset with withTransition: true so the final snap to fully open or fully closed animates smoothly instead of jumping.

Drag-to-resize panels keeps two panes permanently visible and uses a drag to redistribute width between them — neither pane is ever fully hidden. This snippet's panel starts completely off-screen (translateX(-100%)) and the drag's entire purpose is to reveal or re-hide it; there's no second pane being resized, and the interaction ends in one of exactly two settled states rather than anywhere along a continuous range.

Keep currentOffset, dragging, and startX/startOffset in refs since they update on every move event, and apply the transform/left/opacity styles imperatively via the refs rather than through state to avoid re-rendering on every pixel of drag. Attach the mouse/touch listeners in a mount effect with cleanup, and only touch component state (e.g. an isOpen boolean) at the end of the gesture in settle().

Yes. While the Drag to Reveal Side Panel snippet gives you one pattern to copy, the free Responsive Navbar Builder at webdevpuneet.com/navbar-builder/ designs a whole navigation bar: logo, links and dropdown menus, a mobile hamburger menu (dropdown panel, slide-in drawer or full screen) and a scroll effect (sticky, shrink, hide on scroll or transparent). It has six presets, a live preview that runs the real code, and exports HTML + CSS + vanilla JS, React, Tailwind, Vue 3 or Angular — or you can click Fork & Edit to keep editing it in My Code.