webdevpuneet.com

Resizable, Draggable Floating Modal Window — Real Desktop-Style Window Behavior

Resizable, Draggable Floating Modal Window · Modals · Plain HTML, CSS & JS · Live preview

CategoryModals

Copy & paste

Source Code

<div class="demo">
  <button class="open-btn" id="openWinBtn">Open floating window</button>

  <div class="win-overlay" id="winOverlay">
    <div class="float-win" id="floatWin" role="dialog" aria-modal="false" aria-labelledby="winTitle" style="width: 340px; height: 240px; top: 60px; left: 60px;">
      <div class="win-titlebar" id="winTitlebar">
        <span id="winTitle">Notes.txt</span>
        <button class="win-close" id="winClose" aria-label="Close window">×</button>
      </div>
      <div class="win-body">
        <p>Drag this window by its title bar. Drag any edge or corner to resize it — the window respects a minimum width and height so it can never be shrunk into nothing.</p>
        <textarea class="win-textarea" placeholder="Type here..."></textarea>
      </div>
      <div class="win-resize-handle win-resize-e" data-dir="e"></div>
      <div class="win-resize-handle win-resize-s" data-dir="s"></div>
      <div class="win-resize-handle win-resize-se" data-dir="se"></div>
      <div class="win-resize-handle win-resize-w" data-dir="w"></div>
      <div class="win-resize-handle win-resize-n" data-dir="n"></div>
    </div>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), 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

Drag-to-move via the title bar using Pointer Events and setPointerCapture for reliable tracking at any speed
Resize from any of four edges or the corner handle, sharing one parameterized resize routine
Correct edge-anchored resizing — dragging the left or top edge keeps the opposite edge visually fixed in place
Minimum width/height clamps prevent the window from ever collapsing to zero or negative size
All resize math is computed from the drag's fixed starting snapshot, avoiding incremental drift over a long gesture
Corner handle reuses the same edge logic as its two adjacent edge handles, with zero special-case code
Works with mouse, touch, and pen input identically, since Pointer Events unify all three

About this UI Snippet

Draggable, Resizable Windows — Building Real Desktop-Style Interaction on the Web

Screenshot of the Resizable, Draggable Floating Modal Window snippet rendered live

Most web modals are fixed in place — centered, non-movable, non-resizable. Some tools genuinely need more: a floating panel a user can drag out of the way and resize like a real desktop window, useful for things like a notes panel, a live preview, or a debugging console that shouldn't block the content behind it. This snippet implements both drag-to-move and resize-from-any-edge using the Pointer Events API and correct edge-anchoring math.

Pointer Events with setPointerCapture, not mousemove on document

Both the drag and resize handlers call setPointerCapture(e.pointerId) on pointerdown, which routes all subsequent pointer events (pointermove, pointerup) to that same element even if the pointer moves outside its bounds mid-drag — a real problem with a small resize handle, since a fast mouse movement can easily outrun a 6px-wide edge strip. Without capture, moving the pointer just past the handle's edge during a fast drag would silently stop the resize; with it, the resize (or drag) keeps tracking correctly for the entire gesture no matter how far the pointer strays from the original handle.

Resizing computes new dimensions from the drag's *start* state, not incrementally

Every resize calculation is resizeState.startW + dx (or minus, depending on direction) — the delta is always measured against the position and size captured at the moment the drag *began*, never accumulated frame-by-frame from the previous frame's already-updated size. This avoids a subtle class of drift bug where small rounding or event-timing inconsistencies could compound over a long drag; because every frame recomputes from the same fixed starting snapshot, the result is always exactly consistent with how far the pointer has actually moved in total.

Why resizing from the west or north edge also moves the window's position

Dragging the *right* edge only needs to change width — the window's top-left corner stays put. But dragging the *left* edge is different: for the window to visually stay anchored at its unchanged right edge while its left edge tracks the pointer, both the width and the left position must update together, derived from the same delta. The code computes the new width first (clamped to the minimum), then sets left based on exactly how much that clamped width actually differs from the start width — not based on the raw, unclamped pointer delta — which is what keeps the window's right edge perfectly stationary even once the minimum-width clamp kicks in.

One shared resize routine for every handle

Rather than writing separate logic for each of the five resize handles (north, south, east, west, and the southeast corner), every handle shares one pointermove handler that checks which characters are present in its data-dir string ('e', 's', 'se', etc.) and applies only the relevant edge logic. The southeast corner handle needs no special-case code at all — it simply has both 'e' and 's' in its direction string, so both the east-edge and south-edge branches of the shared logic run for it automatically.

Step by step

How to Use

  1. 1
    Click "Open floating window"A draggable, resizable panel appears over the page, positioned near the top-left of the viewport.
  2. 2
    Drag the title barMoves the entire window; the drag tracks correctly even at high pointer speed thanks to pointer capture.
  3. 3
    Drag any edge or the bottom-right cornerResizes the window from that side. Dragging the left or top edge also correctly repositions the window so its opposite edge stays fixed.
  4. 4
    Try shrinking the window past its minimum sizeWidth and height stop shrinking at MIN_W/MIN_H, and position stops adjusting accordingly, so the window can never collapse to nothing.
  5. 5
    Close and reopenClick the × in the title bar; reopening resets to the initial size and position defined in the inline style attribute.

Real-world uses

Common Use Cases

Floating notes or scratchpad panels
A movable, resizable notes window a user can position anywhere on screen without blocking their main work area.
DEBUG
Debug or console overlays
Developer tools panels that need to stay on top of an app while being repositioned and resized freely.
PREVIEW
Live preview windows
A floating live-preview pane (for a design tool or editor) that a user can move and resize to compare against the main canvas.
MULTITASK
Multi-window in-browser workspaces
Browser-based "desktop" style apps managing several simultaneously open floating panels.

Got questions?

Frequently Asked Questions

setPointerCapture routes every subsequent pointer event to the element that captured it, even once the pointer moves outside that element's bounds — critical for a narrow resize handle, since a fast drag can easily move the cursor past a 6px-wide edge strip mid-gesture. Without capture, the resize would silently stop the instant the pointer left the handle.

For the window to look like it's being resized from the left (with its right edge staying fixed), both the width and the left offset must change together. The code derives the new left position from exactly how much the clamped new width differs from the starting width, keeping the right edge perfectly stationary even when the minimum-width clamp is active.

Deriving every frame's size from the same original starting width/height and the total pointer delta since the drag began avoids compounding rounding or timing drift that an incremental frame-over-frame approach could introduce over a long drag gesture.

MIN_W and MIN_H constants are applied via Math.max() on every width/height calculation, so the window can never shrink below 260px wide or 180px tall regardless of how far the pointer is dragged past that limit.

Yes — Pointer Events unify mouse, touch, and pen input under one API, so the same drag and resize handlers work identically for a finger drag on a touchscreen as they do for a mouse.

Store win.style.top/left/width/height to localStorage on pointerup for both the drag and resize handlers, and read those values back to set the window's initial inline style the next time it opens.