webdevpuneet.com

IDE Three-Pane Workspace Layout — Icon Rail + Resizable List

IDE Three-Pane Workspace Layout · Layouts · Plain HTML, CSS & JS · Live preview

CategoryLayouts

Copy & paste

Source Code

<div class="ws">
  <nav class="ws-rail">
    <button type="button" class="ws-rail-btn active" data-panel="files" title="Files" aria-label="Files">▤</button>
    <button type="button" class="ws-rail-btn" data-panel="search" title="Search" aria-label="Search">⌕</button>
    <button type="button" class="ws-rail-btn" data-panel="git" title="Source Control" aria-label="Source Control">⑂</button>
    <button type="button" class="ws-rail-btn" data-panel="ext" title="Extensions" aria-label="Extensions">▣</button>
    <button type="button" class="ws-rail-btn ws-rail-bottom" data-panel="settings" title="Settings" aria-label="Settings">⚙</button>
  </nav>

  <aside class="ws-list" id="wsList" style="width:230px">
    <div class="ws-list-head" id="wsListHead">Files</div>
    <div class="ws-list-body" id="wsListBody"></div>
    <div class="ws-resizer" id="wsResizer"></div>
  </aside>

  <main class="ws-main">
    <div class="ws-tabs">
      <div class="ws-tab active"><span>layout.css</span><i>×</i></div>
      <div class="ws-tab"><span>index.html</span><i>×</i></div>
    </div>
    <div class="ws-editor" id="wsEditor">
      <pre><code>.ws {
  display: grid;
  grid-template-columns: 52px var(--list-w) 1fr;
  height: 100vh;
}

.ws-rail   { /* fixed icon rail   */ }
.ws-list   { /* resizable pane    */ }
.ws-main   { /* fluid detail pane */ }</code></pre>
    </div>
    <div class="ws-statusbar">
      <span id="wsActivePanel">Files</span>
      <span class="ws-dot">·</span>
      <span id="wsListWidth">230px</span>
      <span class="ws-spacer"></span>
      <span>Ln 5, Col 12</span>
    </div>
  </main>
</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

Fixed-width icon rail, resizable list pane, and fluid main pane in one CSS grid declaration
Rail icons act as real view switchers — list pane heading and content change identity per panel, not just visibility
Pointer-capture drag resize on the list pane, matching the robust drag pattern used for resizable table columns
Resize is clamped between a minimum and maximum width so the pane can never be dragged unreadable or too wide
Live pixel-width readout mirrored to the status bar during a resize drag
Realistic main pane content — tabs with close affordances, a code preview, and a bottom status bar
Five pre-built panel datasets (files, search, source control, extensions, settings) driving the same list markup
Responsive: the list pane becomes an overlay drawer next to the rail on narrow viewports instead of squeezing the editor

About this UI Snippet

IDE Three-Pane Workspace Layout — Fixed Icon Rail, Resizable List Pane & Fluid Editor

Screenshot of the IDE Three-Pane Workspace Layout snippet rendered live

Tools like VS Code, Slack, and most modern IDEs share the same three-zone skeleton: a narrow fixed-width icon rail on the far left for switching top-level views, a resizable list pane next to it showing the content of whichever view is active, and a large fluid pane on the right doing the actual work. This snippet builds that exact shell — icon rail, resizable file/search/git list, and an editor pane with tabs and a status bar — entirely in HTML, CSS grid, and vanilla JavaScript.

Three zones, one grid declaration

The outer .ws container is a single grid-template-columns: 52px auto 1fr row filling 100vh: a fixed-width rail, an auto-sized list pane whose width is actually controlled by inline JavaScript, and a fluid 1fr main pane that absorbs whatever space is left. Because the rail's width never changes, it behaves as a stable anchor for the eye no matter how the middle pane is resized or which panel is active — exactly the property that makes an icon rail useful as permanent muscle-memory navigation.

The rail switches content, not just an active state

Clicking a rail button does two things: toggles the .active class for the pressed-icon visual state, and calls renderPanel(key), which looks up a PANELS object keyed by 'files', 'search', 'git', 'ext', or 'settings' and re-renders the list pane's heading and item rows from that panel's own data. This models the real behavior of a rail-driven workspace: the rail is not a static sidebar toggle, it is a *view switcher* — the list pane's entire content and heading change identity depending on which rail icon is active, not just its visibility.

Resizing the middle pane with pointer capture

The drag handle on the list pane's right edge uses the same pointerdown / pointermove / pointerup plus setPointerCapture() pattern used for any robust drag interaction — capturing the pointer means the resize keeps tracking correctly even if the cursor moves faster than the browser can repaint and briefly leaves the 8px hit area. The width is clamped between 160px and 480px so the pane can never be dragged small enough to make its own contents unreadable, or wide enough to swallow the entire editor pane, and a status-bar readout mirrors the live pixel width back to the user during the drag.

A realistic detail pane, not a placeholder

Rather than an empty gray box, the main pane includes tabs (with a close affordance), a monospace code preview, and a status bar — the same three sub-elements every code editor's main area has. This matters for a layout snippet specifically because a layout's true test is whether *realistic* content fits it correctly: tabs need to sit above a scrollable body, and a status bar needs to stick to the bottom of the pane regardless of how much code is above it, both of which this flex-column main pane handles.

Where this shell belongs

This is the right starting skeleton anywhere a product needs several distinct top-level "modes" (files, search, notifications, settings) that each drive a secondary list, next to one large primary work surface — internal admin tools, no-code builders, chat apps with channel/DM switching, and of course browser-based code editors.

Step by step

How to Use

  1. 1
    Click a rail iconSwitching between Files, Search, Source Control, Extensions, or Settings re-renders the list pane's heading and items for that view.
  2. 2
    Drag the divider to resize the list paneGrab the edge between the list pane and the editor and drag — the width is clamped between 160px and 480px and mirrored live in the status bar.
  3. 3
    Replace the PANELS dataEdit the PANELS object in the JS panel to swap in your own file tree, search results, or any other list content per rail icon.
  4. 4
    Add or remove rail iconsAdd a new button with a data-panel attribute matching a new key in PANELS, and it wires into the same click handler automatically.
  5. 5
    Fill in the editor paneReplace the tabs and code preview in .ws-main with your actual editor, preview, or detail content — the pane is a plain flex column.
  6. 6
    Export in your frameworkUse the HTML, JSX, Vue, or Tailwind export buttons to bring this shell into your own project as a layout component.

Real-world uses

Common Use Cases

Browser-based code editors and IDEs
The exact skeleton behind VS Code, StackBlitz, and CodeSandbox — file tree, search, and source control all sharing one resizable list pane next to the editor.
Internal admin and ops tools
Rail-driven navigation between distinct top-level modes (users, logs, deployments) each with their own list, next to a detail pane — pairs well with the Dashboard Layout.
CHAT
Chat and messaging apps
Rail for switching between DMs, channels, and threads, each rendering its own conversation list, next to the active conversation pane.
No-code and visual builder tools
Switch between a component library, layers panel, and asset manager in the same resizable list slot beside the canvas.
Related: Resizable Split Pane
See the Resizable Split Pane for the same pointer-capture drag technique applied to a simpler two-pane layout.

Got questions?

Frequently Asked Questions

The rail functions as permanent muscle-memory navigation — its icons should always land under the same pixel position regardless of what else on the page is resized, the same way a taskbar or dock stays fixed. Making the rail itself resizable would undermine that stability for very little benefit, since its content (a handful of icons) never needs more room.

Each rail button carries a data-panel attribute matching a key in a PANELS JavaScript object holding a label and item list per view. The click handler looks up that key and calls renderPanel(), which rewrites the list pane's heading and rebuilds its item rows from that panel's own data — so the list is not just shown or hidden, its actual content changes identity.

Pointer events cover mouse, touch, and stylus input with one code path, and setPointerCapture() redirects all subsequent pointer events to the resizer element until release — without it, a fast drag can outrun the 8px hit area and the resize stops mid-gesture the moment the cursor leaves the handle.

Below roughly 160px, file or list names become unreadable and start truncating aggressively; above roughly 480px, the list pane starts crowding out the primary editor pane it is supposed to be secondary to. The clamp keeps the pane useful at both extremes without needing the user to manually recover from an unreasonable drag.

Read list.style.width (or the numeric value tracked during the drag) on pointerup and write it to localStorage; on page load, read it back before first paint and apply it to the pane's inline width, the same pattern used in the Resizable Sidebar with Persisted Width snippet.

Yes. Keep the PANELS data as component state or props, render the rail buttons and list items from it, and track the pane width in a ref during the drag (committing to state only on pointerup) so dragging does not re-render the whole tree on every pointermove event.