Layout
Accent side-swap rail
Section navigation that is a sticky vertical rail on wide screens and a wrapping tab row on narrow ones — same markup, no JS, with the active accent border simply moving from border-left to border-bottom.
Reach for it when building
- settings page section nav
- long-form docs chapter rail
- detail page with many sections
- account or admin subnavigation
- filterable report sections
- navigation
- responsive
- css-only
- aria-current
- container-queries
<div class="assr-frame">
<p class="assr-hint">Drag the bottom-right corner narrower — the rail becomes tabs at 30rem.</p>
<div class="assr-body">
<nav class="assr-rail" aria-label="Sections">
<a href="#overview" aria-current="true">Overview</a>
<a href="#activity">Activity <span class="assr-count">96</span></a>
<a href="#members">Members <span class="assr-count">12</span></a>
<a href="#settings">Settings</a>
</nav>
<div class="assr-content">
<h4>Overview</h4>
<p>Each entry is a real link styled by <code>aria-current</code>, so sections are deep-linkable and the back button walks them. The layout change is pure CSS: a container query flips the grid and moves the accent border to the other side.</p>
</div>
</div>
</div>Colors come from shared theme tokens — --surface, --ink, --border, --accent and friends — so this CSS carries no palette
of its own. Use Runnable file to copy the tokens along with it.
.assr-frame {
container-type: inline-size;
/* The resize handle exists so the demo can be squeezed; in production the
container query responds to real layout width. */
resize: horizontal;
overflow: auto;
min-width: 300px;
max-width: 100%;
border: 1px dashed var(--border);
border-radius: var(--radius);
padding: 1rem;
background: var(--surface);
}
.assr-hint { margin: 0 0 0.75rem; font-size: 0.9375rem; color: var(--dim); font-style: italic; }
.assr-body {
display: grid;
grid-template-columns: 180px minmax(0, 1fr);
gap: 1.5rem;
align-items: start;
}
.assr-rail {
display: flex;
flex-direction: column;
gap: 2px;
position: sticky;
top: 1rem;
}
.assr-rail a {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.55rem 0.9rem;
border-radius: calc(var(--radius) - 2px);
color: var(--dim);
text-decoration: none;
border-left: 3px solid transparent;
}
.assr-rail a:hover { color: var(--ink); background: var(--surface-2); }
.assr-rail a[aria-current='true'] {
color: var(--ink);
font-weight: 650;
border-left-color: var(--accent);
background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.assr-count {
margin-left: auto;
color: var(--dim);
font-variant-numeric: tabular-nums;
font-size: 0.9375rem;
}
.assr-content {
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1rem 1.25rem;
min-height: 10rem;
}
.assr-content h4 { margin: 0 0 0.5rem; }
.assr-content p { margin: 0; color: var(--dim); }
@container (max-width: 30rem) {
.assr-body { grid-template-columns: minmax(0, 1fr); }
.assr-rail { position: static; flex-direction: row; flex-wrap: wrap; }
.assr-rail a { border-left: none; border-bottom: 3px solid transparent; }
.assr-rail a[aria-current='true'] {
border-left: none;
border-bottom-color: var(--accent);
}
}Paste this into an agent to rebuild the pattern from scratch.
Build section navigation with one markup and two layouts, switched entirely in CSS: a sticky vertical rail beside the content on wide screens, a wrapping horizontal tab row above it on narrow ones.
The rail is a flex column of real links — never buttons — so every section is deep-linkable, shareable, and the back button walks the reading history. The active entry is styled through `aria-current` alone, which keeps the accessibility signal and the visual signal the same attribute and makes it impossible for them to drift apart.
The active treatment is a 3px accent border plus a light accent-tinted background and a heavier font weight. The responsive move is the whole point of the pattern: inside the narrow media or container query, do not restyle the entries — just collapse the two-column grid to one, let the rail wrap horizontally, and move the 3px accent from `border-left` to `border-bottom`. The same accent on a different side is what turns a sidebar entry into a tab, and because nothing else changes, the two layouts stay visually related and there is zero JavaScript.
Details that matter: each entry may carry a right-aligned count pushed over with `margin-left: auto` in tabular figures; the rail is `position: sticky` with a small top offset in the wide layout and `static` in the narrow one; hover gets a neutral background so the accent stays reserved for the active state. Prefer a container query over a media query when the component can live at different widths on one page, and give the demo container `resize: horizontal` so the breakpoint can be exercised by hand.
Every color comes from theme custom properties, and both layouts have to read correctly in light and dark themes.