Source Code

<div class="demo">
  <div class="split-layout">
    <aside class="split-side">
      <div class="side-sticky">
        <h3>getUserSettings()</h3>
        <p>Fetches the current user's saved preferences from local storage, falling back to sensible defaults for any key that hasn't been set yet.</p>
        <div class="sig-block">
          <span class="sig-label">Signature</span>
          <code>getUserSettings(defaults?: object): Settings</code>
        </div>
        <div class="sig-block">
          <span class="sig-label">Returns</span>
          <code>Settings</code> — a merged object of stored and default values
        </div>
      </div>
    </aside>

    <main class="split-main">
      <section>
        <h4>Basic usage</h4>
        <p>Call with no arguments to read whatever is currently stored, merged over the library's built-in defaults.</p>
        <pre>const settings = getUserSettings();
console.log(settings.theme); // "light"</pre>
      </section>
      <section>
        <h4>Custom defaults</h4>
        <p>Pass a defaults object to override the library's built-in fallback values for any key.</p>
        <pre>const settings = getUserSettings({ theme: 'dark', fontSize: 16 });</pre>
      </section>
      <section>
        <h4>Reacting to changes</h4>
        <p>Settings updated elsewhere in the same tab are reflected immediately; updates from other tabs sync on the next call.</p>
        <pre>window.addEventListener('storage', () => {
  const fresh = getUserSettings();
  applyTheme(fresh.theme);
});</pre>
      </section>
      <section>
        <h4>Error handling</h4>
        <p>If localStorage is unavailable (private browsing, storage quota exceeded), getUserSettings silently falls back to defaults rather than throwing.</p>
        <pre>const settings = getUserSettings(); // never throws</pre>
      </section>
      <section>
        <h4>TypeScript usage</h4>
        <p>The Settings type is exported for consumers who want to extend or reference the shape directly.</p>
        <pre>import type { Settings } from './settings';
function applyTheme(s: Settings) { /* ... */ }</pre>
      </section>
    </main>
  </div>
</div>

Sticky Split-Pane Documentation Layout — Independently Scrolling Panes with a Pinned Sidebar

Sticky Split-Pane Documentation Layout · Layouts · Plain HTML & CSS · Live preview

What's included

Features

True independent scrolling — the sidebar stays pinned while only the main content pane scrolls
Correct position: sticky setup avoiding the common "an ancestor's overflow silently breaks sticky" pitfall
CSS Grid row height is the single shared constraint both panes compute their scroll/sticky behavior against
Sidebar clips its own overflow without becoming a second, competing scroll container
No JavaScript required — the entire layout and scroll behavior is pure CSS
Realistic API-documentation content structure (signature card plus usage sections) demonstrates the pattern in context
Scrollbar gutter reserved via padding-right on the main pane to avoid content shifting under a scrollbar
Responsive-ready structure — the grid can collapse to a single column with a media query for narrow viewports

About this UI Snippet

Sticky Split-Pane Layout — Two Independently Scrolling Regions, One Pinned

Screenshot of the Sticky Split-Pane Documentation Layout snippet rendered live

API documentation, reference pages, and settings screens often pair a short summary (a function signature, a field's constraints) with much longer detail content (usage examples, edge cases, related notes). This layout keeps the summary always visible while the longer content scrolls past it, using a combination that's easy to get subtly wrong: position: sticky only works correctly when its scroll container and containing block are set up in a specific way.

Why the two panes need separate scroll containers

.split-main has its own overflow-y: auto and a fixed height inherited from the parent .split-layout grid row — this makes it independently scrollable, distinct from the page's own scroll. .split-side deliberately has overflow: hidden (not auto) and no explicit height constraint of its own, so it doesn't create a second competing scroll context; instead, its child .side-sticky uses position: sticky; top: 0 to stay pinned *within* the grid row's available height as the row itself doesn't scroll but the sibling .split-main does.

The CSS Grid row height is what makes both panes agree on "the same space"

.split-layout is a two-column CSS Grid with an explicit height: 380px — this single height applies to both grid tracks simultaneously, giving .split-main a concrete height to compute overflow-y: auto against, and giving .split-side's sticky child a bounded area to stay pinned within. Without a shared explicit height on the grid container, position: sticky on the sidebar would have no meaningful "container" to stick relative to, since sticky positioning is scoped to the nearest scrolling ancestor and its own containing block — both of which need this grid row to actually constrain them.

A common failure mode this layout avoids

A frequent bug in split-pane layouts is applying overflow: hidden or auto somewhere in the ancestor chain *above* the sticky element without realizing it — position: sticky stops working (silently, with no error) if any ancestor between the sticky element and the actual scrolling container has its own overflow clipping that isn't the intended scroll context. This snippet keeps the ancestor chain deliberately simple: .split-side clips overflow (so its content can't spill into .split-main's column) without becoming its own scroll container, and .side-sticky's sticky positioning resolves against .split-layout's grid row height correctly as a result.

Where this genuinely earns its complexity

This pattern is worth the setup specifically when a page has a short, glanceable summary that a reader wants to reference *while* scrolling through much longer supporting content — API reference docs, a product spec sheet next to detailed reviews, or a form field's constraints next to a long list of validation rules — rather than as a general-purpose two-column layout for content of roughly equal length, where independent scrolling adds complexity without a corresponding benefit.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant to explain exactly why position:sticky requires both a correctly-scoped containing block and an unbroken chain of non-clipping ancestors up to its scroll container, and to help debug a specific case where sticky positioning silently isn't working. It's also worth asking for a responsive variant that collapses to a single stacked column with the sidebar becoming non-sticky below a chosen breakpoint, or a three-pane variant (sticky sidebar, scrollable main content, sticky right-hand table of contents).

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 two-pane documentation-style layout in HTML and CSS where a summary sidebar stays pinned in view while a separate main content pane scrolls independently beside it — no JavaScript, no external libraries.

Requirements:
- A CSS Grid container with two columns (a narrower sidebar and a wider main content area) and an explicit height, since both the sidebar's sticky behavior and the main pane's independent scrolling need a shared, bounded height to work against.
- The sidebar column must clip its own overflow without becoming a second independently-scrollable region — its inner content block should use position: sticky with top: 0 so it stays pinned at the top of its column as the page's focus content scrolls.
- The main content pane must scroll independently via its own overflow-y: auto, containing several longer sections of content (e.g. multiple usage examples) so there's genuinely more content than fits in the visible area.
- Structure the ancestor elements carefully so nothing between the sticky sidebar content and its intended scroll boundary accidentally clips or creates a competing scroll context, which is the most common way this kind of layout silently breaks.
- Include realistic example content (like an API reference: a function signature and description in the sidebar, several usage sections with code examples in the main pane) to demonstrate the pattern in a believable context.

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
    Set an explicit height on the grid containerThe .split-layout height is what both the sticky sidebar and the scrollable main pane compute their behavior against — remove it and sticky positioning breaks.
  2. 2
    Keep .split-side as overflow:hidden, not overflow:autoGiving the sidebar its own scrollbar would create a second competing scroll context instead of one clean sticky-within-the-grid-row behavior.
  3. 3
    Put position:sticky on the inner content, not the column itselfThe sticky behavior belongs on .side-sticky (the actual content block) so it can stick to top:0 within its parent column's bounds.
  4. 4
    Add or remove sections in .split-main freelyThe main pane scrolls independently regardless of how much content is inside it — the sidebar stays pinned throughout.
  5. 5
    Adjust the grid-template-columns splitChange the 220px sidebar width or the 1fr main content ratio to fit your content's needs.

Real-world uses

Common Use Cases

DOCS
API Reference Documentation
Keep a function's signature and summary visible while a reader scrolls through usage examples and notes.
ECOM
Product Spec Sheets
Pin key product specs while long-form reviews or detailed descriptions scroll alongside them.
Form Field Reference Panels
Keep a field's validation rules visible while scrolling through a long list of examples or edge cases.
LEGAL
Annotated Legal / Contract Documents
Pin a clause summary while the full legal text scrolls in the adjacent pane.

Got questions?

Frequently Asked Questions

Sticky positioning requires a bounded containing block and needs to be scoped correctly relative to its nearest scrolling ancestor — if any element between the sticky element and the intended scroll container has its own overflow clipping (even accidentally), sticky positioning silently stops working with no console warning, which is a very common real-world bug.

overflow:auto would make the sidebar its own independent scroll container, which is not what's wanted here — the sidebar's content is meant to stay pinned in place via sticky positioning, not to scroll on its own. overflow:hidden simply prevents its content from visually spilling outside its column without creating a second scrollbar.

Both position:sticky on the sidebar and overflow-y:auto on the main pane need a concrete height to compute their behavior against. Without an explicit height on the shared grid container, the main pane would simply grow to fit its content instead of scrolling, and the sidebar would have no bounded row height to stick within.

No — with top: 0 and no height constraint of its own beyond the grid row, .side-sticky stays pinned to the top of its column for as long as .split-main has more content to scroll through, which is the intended "always-visible summary" behavior.

Yes — add a media query that switches .split-layout to a single grid-template-columns: 1fr with auto height, and remove the sticky positioning and independent overflow at that breakpoint so the whole page scrolls normally with the sidebar content stacked above the main content.

No — the entire independent-scroll-plus-sticky-sidebar behavior is achieved purely through CSS Grid, position: sticky, and overflow properties, with zero JavaScript involved.