About this snippet

A code panel on the left and a preview on the right, separated by a thin divider you can drag. The panes stay above a minimum width, double clicking the divider snaps back to an even split, and with the divider focused the arrow keys nudge it in small steps.

The drag uses pointer events with setPointerCapture on the divider, so the divider keeps receiving moves even when the cursor races ahead of it or leaves the window. On each move the script converts the pointer position into a percentage of the container, clamps it between the two limits, and writes it to a custom property that the grid template reads. While dragging, a class on the container disables text selection so the drag stays smooth.

The divider has role separator with aria-valuenow updated on every move, and because it is focusable the keyboard path needs nothing beyond the keydown handler.

Size
3.6 KB
Uses
HTML, CSS, JavaScript
Loads
Nothing external
Views
48
<div class="split" id="split">
  <section class="pane">
    <header>index.html</header>
    <pre>&lt;section class="hero"&gt;
  &lt;h1&gt;Ship faster&lt;/h1&gt;
  &lt;p&gt;Drag the divider to resize.&lt;/p&gt;
  &lt;a href="#"&gt;Get started&lt;/a&gt;
&lt;/section&gt;</pre>
  </section>
  <div class="divider" id="divider" role="separator" aria-orientation="vertical" aria-label="Resize panes" aria-valuemin="20" aria-valuemax="80" aria-valuenow="50" tabindex="0"></div>
  <section class="pane preview">
    <header>Preview</header>
    <div class="hero">
      <h1>Ship faster</h1>
      <p>Drag the divider to resize.</p>
      <a href="#">Get started</a>
    </div>
  </section>
</div>
*, *::before, *::after {
  box-sizing: border-box;
}

body {
  font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  margin: 0;
  background: #0f172a;
  color: #e2e8f0;
  padding: 20px;
  min-height: 100vh;
}

.split {
  --left: 50%;
  display: grid;
  grid-template-columns: var(--left) 8px minmax(0, 1fr);
  height: calc(100vh - 40px);
  min-height: 360px;
  border-radius: 14px;
  overflow: hidden;
  background: #1e293b;
}

.split.dragging {
  user-select: none;
  cursor: col-resize;
}

.split.dragging .pane {
  pointer-events: none;
}

.pane {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.pane header {
  padding: 10px 16px;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #94a3b8;
  background: #0f172a;
}

.pane pre {
  margin: 0;
  padding: 18px;
  font-family: Consolas, "SFMono-Regular", Menlo, monospace;
  font-size: 0.875rem;
  line-height: 1.6;
  color: #bae6fd;
  overflow: auto;
  flex: 1;
}

.divider {
  position: relative;
  background: #334155;
  cursor: col-resize;
  touch-action: none;
  transition: background 0.15s ease;
}

.divider::after {
  content: "";
  position: absolute;
  inset: 0 -8px;
}

.divider:hover,
.divider:focus-visible,
.split.dragging .divider {
  background: #3b82f6;
  outline: 0;
}

.preview {
  background: #ffffff;
  color: #0f172a;
}

.preview header {
  background: #f1f5f9;
  color: #64748b;
}

.hero {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 32px;
  overflow: hidden;
}

.hero h1 {
  font-size: clamp(1.5rem, 4cqi, 2.5rem);
  margin: 0 0 8px;
}

.hero p {
  margin: 0 0 18px;
  color: #475569;
}

.hero a {
  align-self: flex-start;
  padding: 10px 18px;
  border-radius: 8px;
  background: #2563eb;
  color: #ffffff;
  font-weight: 600;
  text-decoration: none;
}
const split = document.getElementById("split");
const divider = document.getElementById("divider");
const min = 20;
const max = 80;
let value = 50;

function apply(next) {
  value = Math.min(max, Math.max(min, next));
  split.style.setProperty("--left", value + "%");
  divider.setAttribute("aria-valuenow", Math.round(value));
}

divider.addEventListener("pointerdown", function(e) {
  divider.setPointerCapture(e.pointerId);
  split.classList.add("dragging");
});

divider.addEventListener("pointermove", function(e) {
  if (!divider.hasPointerCapture(e.pointerId)) return;
  const rect = split.getBoundingClientRect();
  apply(((e.clientX - rect.left) / rect.width) * 100);
});

divider.addEventListener("pointerup", function(e) {
  divider.releasePointerCapture(e.pointerId);
  split.classList.remove("dragging");
});

divider.addEventListener("dblclick", function() {
  apply(50);
});

divider.addEventListener("keydown", function(e) {
  if (e.key === "ArrowLeft") apply(value - 2);
  if (e.key === "ArrowRight") apply(value + 2);
  if (e.key === "Home") apply(min);
  if (e.key === "End") apply(max);
});
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