Sticky sidebar
Stable Available in release assets
Pair supporting navigation or controls with primary content, enabling bounded sticky positioning only when the containing block is wide enough.
Sticky sidebar primary example
<div class="thin-container">
<div class="thin-sticky-sidebar">
<aside aria-labelledby="sidebar-title">
<h2 id="sidebar-title">On this page</h2>
<nav aria-label="Section navigation">
<a href="#overview">Overview</a>
<a href="#details">Details</a>
</nav>
</aside>
<main>
<section id="overview">
<h1>Long-form content</h1>
<p>The sidebar becomes sticky only when its ancestor query container has enough inline space.</p>
</section>
<section id="details">
<h2>Details</h2>
<p>Main content keeps a zero minimum so long text and code can scroll without widening the page.</p>
</section>
</main>
</div>
</div>Show code
<div class="thin-container">
<div class="thin-sticky-sidebar">
<aside aria-labelledby="sidebar-title">
<h2 id="sidebar-title">On this page</h2>
<nav aria-label="Section navigation">
<a href="#overview">Overview</a>
<a href="#details">Details</a>
</nav>
</aside>
<main>
<section id="overview">
<h1>Long-form content</h1>
<p>The sidebar becomes sticky only when its ancestor query container has enough inline space.</p>
</section>
<section id="details">
<h2>Details</h2>
<p>Main content keeps a zero minimum so long text and code can scroll without widening the page.</p>
</section>
</main>
</div>
</div>Variant gallery
Each authored example uses the production assets and includes copyable markup.
Leading sidebar
Supporting navigation precedes primary content in source order.
<div class="thin-container">
<div class="thin-sticky-sidebar">
<aside aria-labelledby="sidebar-title">
<h2 id="sidebar-title">On this page</h2>
<nav aria-label="Section navigation">
<a href="#overview">Overview</a>
<a href="#details">Details</a>
</nav>
</aside>
<main>
<section id="overview">
<h1>Long-form content</h1>
<p>The sidebar becomes sticky only when its ancestor query container has enough inline space.</p>
</section>
<section id="details">
<h2>Details</h2>
<p>Main content keeps a zero minimum so long text and code can scroll without widening the page.</p>
</section>
</main>
</div>
</div>Show code
<div class="thin-container">
<div class="thin-sticky-sidebar">
<aside aria-labelledby="sidebar-title">
<h2 id="sidebar-title">On this page</h2>
<nav aria-label="Section navigation">
<a href="#overview">Overview</a>
<a href="#details">Details</a>
</nav>
</aside>
<main>
<section id="overview">
<h1>Long-form content</h1>
<p>The sidebar becomes sticky only when its ancestor query container has enough inline space.</p>
</section>
<section id="details">
<h2>Details</h2>
<p>Main content keeps a zero minimum so long text and code can scroll without widening the page.</p>
</section>
</main>
</div>
</div>Custom width
Override sidebar width locally when the default measure is too narrow or wide.
<div class="thin-container">
<div class="thin-sticky-sidebar" style="--thin-sticky-sidebar-width: 18rem">
<aside aria-labelledby="wide-sidebar">
<h2 id="wide-sidebar">Contents</h2>
<nav aria-label="Section navigation">
<a href="#overview">Overview</a>
</nav>
</aside>
<main>
<h1 id="overview">Article</h1>
<p>Primary content follows the aside in source order.</p>
</main>
</div>
</div>Show code
<div class="thin-container">
<div class="thin-sticky-sidebar" style="--thin-sticky-sidebar-width: 18rem">
<aside aria-labelledby="wide-sidebar">
<h2 id="wide-sidebar">Contents</h2>
<nav aria-label="Section navigation">
<a href="#overview">Overview</a>
</nav>
</aside>
<main>
<h1 id="overview">Article</h1>
<p>Primary content follows the aside in source order.</p>
</main>
</div>
</div>Custom sticky offset
Adjust sticky offset when a fixed site header consumes block-start space.
<div class="thin-container">
<div class="thin-sticky-sidebar" style="--thin-sticky-sidebar-offset: 4rem">
<aside aria-labelledby="offset-sidebar">
<h2 id="offset-sidebar">Contents</h2>
<nav aria-label="Section navigation">
<a href="#overview">Overview</a>
</nav>
</aside>
<main>
<h1 id="overview">Article</h1>
<p>Sticky offset clears the site header.</p>
</main>
</div>
</div>Show code
<div class="thin-container">
<div class="thin-sticky-sidebar" style="--thin-sticky-sidebar-offset: 4rem">
<aside aria-labelledby="offset-sidebar">
<h2 id="offset-sidebar">Contents</h2>
<nav aria-label="Section navigation">
<a href="#overview">Overview</a>
</nav>
</aside>
<main>
<h1 id="overview">Article</h1>
<p>Sticky offset clears the site header.</p>
</main>
</div>
</div>Non-sticky narrow container
The sidebar remains in normal flow when the query container is too narrow for sticky positioning.
<div class="thin-container" style="--thin-container-max: 20rem">
<div class="thin-sticky-sidebar">
<aside aria-labelledby="narrow-sidebar">
<h2 id="narrow-sidebar">Contents</h2>
<nav aria-label="Section navigation">
<a href="#overview">Overview</a>
</nav>
</aside>
<main>
<h1 id="overview">Article</h1>
<p>In narrow containers the aside scrolls with the page.</p>
</main>
</div>
</div>Show code
<div class="thin-container" style="--thin-container-max: 20rem">
<div class="thin-sticky-sidebar">
<aside aria-labelledby="narrow-sidebar">
<h2 id="narrow-sidebar">Contents</h2>
<nav aria-label="Section navigation">
<a href="#overview">Overview</a>
</nav>
</aside>
<main>
<h1 id="overview">Article</h1>
<p>In narrow containers the aside scrolls with the page.</p>
</main>
</div>
</div>Anatomy
- Ancestor inline-size query container
- Intrinsic layout wrapper
- Aside landmark
- Primary content
- Optional labelled navigation
Variants and sizes
- Leading sidebar — shown above
- Custom width — shown above
- Custom sticky offset — shown above
- Non-sticky narrow container — shown above
States and difficult content
Rendered fixtures exercise authored content and constrained layouts with the same production assets.
Sidebar taller than viewport
Bounded sticky positioning keeps keyboard scrolling inside the aside when content exceeds the viewport.
<div class="thin-container">
<div class="thin-sticky-sidebar">
<aside aria-labelledby="tall-sidebar">
<h2 id="tall-sidebar">Contents</h2>
<nav aria-label="Section navigation">
<a href="#one">One</a>
<a href="#two">Two</a>
<a href="#three">Three</a>
<a href="#four">Four</a>
<a href="#five">Five</a>
<a href="#six">Six</a>
</nav>
</aside>
<main>
<h1>Article</h1>
<p>Primary content remains independently scrollable.</p>
</main>
</div>
</div>Show code
<div class="thin-container">
<div class="thin-sticky-sidebar">
<aside aria-labelledby="tall-sidebar">
<h2 id="tall-sidebar">Contents</h2>
<nav aria-label="Section navigation">
<a href="#one">One</a>
<a href="#two">Two</a>
<a href="#three">Three</a>
<a href="#four">Four</a>
<a href="#five">Five</a>
<a href="#six">Six</a>
</nav>
</aside>
<main>
<h1>Article</h1>
<p>Primary content remains independently scrollable.</p>
</main>
</div>
</div>Long unbreakable content
Main content keeps min-inline-size: 0 so code and URLs do not widen the page.
<div class="thin-container">
<div class="thin-sticky-sidebar">
<aside aria-labelledby="code-sidebar">
<h2 id="code-sidebar">Contents</h2>
</aside>
<main>
<pre>
<code>const identifier = "a-deliberately-unbroken-token-that-must-wrap-without-widening-the-page";</code>
</pre>
</main>
</div>
</div>Show code
<div class="thin-container">
<div class="thin-sticky-sidebar">
<aside aria-labelledby="code-sidebar">
<h2 id="code-sidebar">Contents</h2>
</aside>
<main>
<pre>
<code>const identifier = "a-deliberately-unbroken-token-that-must-wrap-without-widening-the-page";</code>
</pre>
</main>
</div>
</div>- Narrow container — documented, example pending
- Wide container — documented, example pending
- Sidebar taller than viewport — shown above
- Long unbreakable content — shown above
- Right-to-left content — documented, example pending
- Keyboard scrolling — documented, example pending
Interaction and keyboard behavior
This composition owns no JavaScript behavior. Links, controls, scrolling, landmarks, and focus retain native behavior.
Accessibility
Use the appropriate landmarks and heading hierarchy, preserve logical source order, and ensure any independently scrolling sidebar remains keyboard reachable through its native interactive content.
- Contract
COMPOSE- Basis
- Composition of native landmarks, headings, lists, links, and forms; no APG widget
API
Place .thin-sticky-sidebar inside an ancestor inline-size query container such as thin-container, with a direct aside followed by primary content. Override --thin-sticky-sidebar-width, --thin-sticky-sidebar-space, or --thin-sticky-sidebar-offset locally.
Behavior tier: css-only
Custom properties
--thin-sticky-sidebar-width18remPreferred inline size of the sidebar column.--thin-sticky-sidebar-spacevar(--thin-space-l)Gap between sidebar and main content.--thin-sticky-sidebar-offsetvar(--thin-space-m)Sticky block-start offset and viewport inset.Fallback
Landmarks and content remain in ordinary document order when layout styles are unavailable.
Assets and dependencies
/layouts.css
Dependencies: None
Source: packages/ui/src/style/layouts.ts
Stability evidence
- Aside landmark, bounded sticky positioning, and narrow-container fallback without scripts:
test:tests/unit/library/page-shell-patterns.test.ts - Complete item-specific API, fallback, variant coverage, and difficult-state documentation:
test:tests/unit/library/page-shell-patterns.test.ts - Recorded cross-theme and environmental review protocol:
review:site/src/pages/quality/foundation-layout.astro#review-protocol
Composition
Derived from the examples and style specs, not authored.