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

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

Share & Support

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.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant to explain in detail why resizing from the west or north edge requires updating position alongside size, while resizing from east or south only needs a size change — walking through the geometry of which corner stays anchored in each case. It's also worth asking for a version that constrains the window within the viewport bounds so it can never be dragged fully off-screen, or one that supports multiple simultaneously open floating windows with a z-index "bring to front on click" behavior.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a draggable, resizable floating window/panel in HTML, CSS, and vanilla JavaScript using the Pointer Events API — no external library.

Requirements:
- A panel with a title bar (draggable by mouse or touch to move the whole window) and a close button, plus resize handles on all four edges and at least the bottom-right corner.
- Implement dragging using pointerdown/pointermove/pointerup with setPointerCapture, so the drag continues tracking correctly even if the pointer moves fast enough to leave the title bar's bounds mid-gesture.
- Implement resizing so that dragging the right or bottom edge only changes the window's width or height respectively, while dragging the left or top edge changes both the corresponding dimension AND the window's position, keeping the OPPOSITE edge visually fixed in place — derive the position adjustment from how much the (possibly clamped) new size differs from the starting size, not from the raw unclamped pointer delta.
- Enforce a minimum width and a minimum height that the window can never be resized below, regardless of how far past that limit the pointer is dragged.
- Share one resize calculation routine across all the resize handles, parameterized by which edge(s) each handle affects, so a corner handle naturally combines the logic of its two adjacent edge handles without needing separate special-case code.
- All position and size math during both drag and resize must be computed from the fixed starting snapshot taken at the beginning of the gesture (start position, start size, start pointer coordinates) rather than incrementally accumulated frame by frame.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

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.