About this snippet

Add a desktop-like experience to your web app with this draggable modal. We use precise offset calculations to ensure the window moves smoothly without jumping when you first grab it, and the modern UI includes a blurred backdrop.

Size
4.2 KB
Uses
HTML, CSS, JavaScript
Loads
Nothing external
Views
357
<div class="modal-trigger-area">
  <button class="open-btn" onclick="openModal()">Open Draggable Window</button>
</div>

<div id="dragModal" class="modal-wrapper">
  <div class="modal-window" id="windowBox">
    <div class="modal-header" id="dragHandle">
      <div class="title">
        <span>New Project</span>
      </div>
      <button class="close-btn" onclick="closeModal()">&times;</button>
    </div>
    <div class="modal-body">
      <h3>Drag me around!</h3>
      <p>Grab the gray header bar to move this window. It stays exactly where you leave it.</p>
      <div class="modal-footer">
        <button class="action-btn" onclick="closeModal()">Close</button>
        <button class="action-btn primary">Save Changes</button>
      </div>
    </div>
  </div>
</div>
.modal-trigger-area {
  font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  padding: 50px;
  text-align: center;
}
.open-btn {
  padding: 12px 24px;
  background: #1f2937;
  color: white;
  border: none;
  border-radius: 8px;
  font-weight: 600;
  cursor: pointer;
  transition: transform 0.2s;
}
.open-btn:active {
  transform: scale(0.96);
}

/* Modal Overlay */
.modal-wrapper {
  font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(4px);
  z-index: 1000;
  display: none;
  opacity: 0;
  transition: opacity 0.3s ease;
}
.modal-wrapper.show {
  display: block;
  opacity: 1;
}

/* Draggable Window */
.modal-window {
  position: absolute;
  top: 50%;
  left: 50%;
  /* Initial centering */
  transform: translate(-50%, -50%);
  width: 400px;
  background: #ffffff;
  border-radius: 12px;
  box-shadow: 0 20px 50px rgba(0,0,0,0.15);
  border: 1px solid #e5e7eb;
  overflow: hidden;
}

.modal-header {
  padding: 12px 16px;
  background: #f3f4f6;
  border-bottom: 1px solid #e5e7eb;
  display: flex;
  justify-content: space-between;
  align-items: center;
  cursor: grab;
  user-select: none;
}
.modal-header:active {
  cursor: grabbing;
  background: #e5e7eb;
}

.title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  color: #374151;
}
.close-btn {
  background: none;
  border: none;
  font-size: 1.5rem;
  line-height: 1;
  color: #9ca3af;
  cursor: pointer;
  padding: 0 4px;
}
.close-btn:hover {
  color: #ef4444;
}

.modal-body {
  padding: 24px;
}
.modal-body h3 {
  margin: 0 0 10px 0;
  color: #111827;
}
.modal-body p {
  color: #6b7280;
  line-height: 1.5;
  margin-bottom: 24px;
}

.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 12px;
}
.action-btn {
  padding: 8px 16px;
  border-radius: 6px;
  border: 1px solid #d1d5db;
  background: white;
  font-weight: 500;
  cursor: pointer;
  color: #374151;
}
.action-btn.primary {
  background: #2563eb;
  color: white;
  border-color: #2563eb;
}
const wrapper = document.getElementById("dragModal");
const modal = document.getElementById("windowBox");
const header = document.getElementById("dragHandle");

let isDragging = false;
let startX, startY, initialLeft, initialTop;

function openModal() {
  wrapper.classList.add("show");
  // Reset position to center whenever opened
  modal.style.top = "50%";
  modal.style.left = "50%";
  modal.style.transform = "translate(-50%, -50%)";
}

function closeModal() {
  wrapper.classList.remove("show");
}

header.addEventListener("mousedown", (e) => {
  isDragging = true;
  
  // Get the current computed position
  const rect = modal.getBoundingClientRect();
  
  // Calculate the offset from the mouse to the top-left corner
  startX = e.clientX - rect.left;
  startY = e.clientY - rect.top;

  // Important: Switch from CSS centering to absolute pixel positioning
  // This prevents the "jump" when removing the transform
  modal.style.transform = "none";
  modal.style.left = rect.left + "px";
  modal.style.top = rect.top + "px";
});

document.addEventListener("mousemove", (e) => {
  if (!isDragging) return;
  e.preventDefault();
  
  // Calculate new position
  const x = e.clientX - startX;
  const y = e.clientY - startY;
  
  modal.style.left = `${x}px`;
  modal.style.top = `${y}px`;
});

document.addEventListener("mouseup", () => {
  isDragging = false;
});
Esc
↑↓ moveEnter select

Start from a template

Open an .html file, or HTML, CSS, JS, Markdown, Less or TypeScript files. You can also drop them here. Styles and scripts go into their panes.

Share

Source

Keyboard shortcuts

Save