20 Tailwind CSS Sidebars

12 Pure Tailwind8 Tailwind + JSPublished

20 Tailwind CSS sidebar navigation patterns for admin dashboards, dev-tool shells, mail and chat two-pane apps, and mobile off-canvas drawers — persistent and collapsible rails, icon-only rails, drawers with focus trap, resizable sticky full-height layouts, nested accordion menus, and current-page state, plus a zero-JS <details> track. Scoped under .tsb-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.

Related20 CSS Sidebar Navigation28 Tailwind Hamburger Menus17 CSS Side Menus25 CSS Sticky Navigations17 Tailwind CSS Side Menus

Premium template pickPartner: Cruip

Affiliate link

Building the whole admin dashboard, not just this layout?

You just browsed 20 Tailwind sidebar navigation. Mosaic by Cruip is a complete Tailwind CSS admin dashboard template, coded in HTML, React, Next.js, Vue and Laravel, with a Figma version too. It costs $69.

View Mosaic dashboard

Mosaic by Cruip. We may earn a commission, at no extra cost to you.

01. Tailwind Accessible Sidebar Navigation ARIA Reference (opens in a new tab)

Published

Pure Tailwind
The reference build the other nineteen borrow from, ported to variants. A <nav> with an accessible name, a real <ul> of links, and the active item marked with aria-current=&quot;page&quot; — styled with aria-[current=page]:, so the visual state is generated from the announced state and cannot drift from it. The disclosure is native <details>, so the whole demo needs no script at all.

02. Tailwind Sidebar Focus Trap and Restore on Close (opens in a new tab)

Published

Tailwind + JS
A modal sidebar makes three promises to a keyboard user: focus moves in, focus stays in, focus comes back. The trap is hand-wired script — it has to be — but everything visual moves into the class list: the drawer states its closed and open transforms on itself and reads the open state through group-data-open:, so the whole [data-open] .drawer rule disappears.

03. Tailwind Inert Background Content Behind a Drawer (opens in a new tab)

Published

Tailwind + JS
The half of modal behaviour a focus trap cannot give you: everything behind the drawer is switched off with one attribute. inert takes the background out of the accessibility tree, out of the tab order and out of pointer events at once — and because Tailwind v4 ships an inert: variant, the visual consequence (a dimmed table) is declared on the element that goes inert, next to its normal state.

04. Tailwind Sidebar Navigation Is Not a Tab List (opens in a new tab)

Published

Pure Tailwind
The boundary marker. A sidebar is a list of links that navigate; a tab list is a set of controls that swap a panel in place, and the two have different roles, different attributes and different keyboard contracts. This is the correct build — <nav>, <ul>, <a>, aria-current=&quot;page&quot; — with every visual decision in utilities and not one line of script.

05. Tailwind Announcing Collapsed and Expanded Sidebar State (opens in a new tab)

Published

Tailwind + JS
A rail that collapses to icons has to say so. The control is a real button carrying aria-expanded, its accessible name changes with the state, and the labels become visually hidden rather than display:none — so the collapsed rail still announces &ldquo;Albums, link&rdquo;. In utilities the whole collapsed geometry reads off one data-state attribute through group-data-[state=collapsed]:.

06. Tailwind Roving Tabindex for Sidebar Keyboard Navigation (opens in a new tab)

Published

Tailwind + JS
Ten folders means ten tab stops before a keyboard user reaches the content — unless the list is one stop and the arrow keys move inside it. Roving tabindex is script by definition (it rewrites tabindex as focus moves), so the port's interesting half is the focus treatment: focus-visible: utilities give the moved-to item a ring AND a tint, because with one tab stop the focus indicator is the only thing telling the user where they are.
Newsletter

Enjoying these demos? Get new ones by email.

A short email when a new collection ships. Unsubscribe any time.

at

Almost there: check your inbox to confirm.Manage

07. Tailwind Persisting Sidebar Collapse State Across Reloads (opens in a new tab)

Published

Tailwind + JS
A collapse toggle the user has to re-click on every reload is a toggle that does not work. This one writes to localStorage and restores on load — and the detail that makes it feel native is the second attribute: data-noanim, read by group-data-noanim:transition-none, which suppresses the width transition for exactly one frame so the restored rail appears already collapsed instead of animating into place.

08. Tailwind Sidebar Scroll Containment with Overscroll Behavior (opens in a new tab)

Published

Pure Tailwind
Two panes, two scrollbars, and the bug everyone has felt: you reach the bottom of the channel list and the page starts scrolling instead. overscroll-behavior: contain fixes it in one declaration — which Tailwind has no utility for, correctly — so this demo is the collection's clearest case for arbitrary properties, alongside the min-h-0 that makes an overflowing flex child possible at all.

09. Tailwind Nested Submenu Keyboard and Screen Reader Behavior (opens in a new tab)

Published

Pure Tailwind
Two-level navigation done natively: each group is a <details>, so a collapsed submenu is genuinely removed from the tab order and the expanded state is exposed by the browser. The port's real find is group-has-[a[aria-current=page]]: — a group that contains the current page marks itself, both with a dot and with a visually hidden phrase, entirely from the class list.

10. Tailwind Resizable Sidebar with a Keyboard Accessible Handle (opens in a new tab)

Published

Tailwind + JS
A drag handle that only works with a mouse excludes every keyboard user, and a <div> with a col-resize cursor tells assistive technology nothing. This one is a real role=&quot;separator&quot; with aria-valuenow, driven by pointer events AND arrow keys — and in the port the live width stays where it has to: a custom property the panel reads through w-[var(--tsb10-w)].

11. Pure Tailwind Collapsible Sidebar with Checkbox State (opens in a new tab)

Published

Pure Tailwind
The zero-JS collapse. A visually hidden checkbox holds the state, a <label> is the control, and every responding element reads the state through group-has-checked: — which is precisely what the :has() era gave utility CSS. Compare demo 05, the scripted version: same product, and the honest trade is that a checkbox cannot carry aria-expanded.

12. Tailwind Responsive Sidebar Breakpoint Behavior (opens in a new tab)

Published

Tailwind + JS
The same sidebar is a persistent rail on a laptop and a modal drawer on a phone — which means its ROLE changes with the viewport, not just its width. The port's discipline is the interesting part: the breakpoint is declared once, in matchMedia, and the class list keys off the resulting data-mode attribute rather than repeating the number as a Tailwind variant.

13. Pure Tailwind Icon Rail Sidebar with Accessible Labels (opens in a new tab)

Published

Pure Tailwind
An icon-only rail has one hard requirement and one nice-to-have, and most implementations get them backwards. The requirement is an accessible name on every link — here a visually hidden <span>, never title. The nice-to-have is the sighted user's tooltip, and in utilities it is group-hover: and group-focus-visible: on a second span. Zero script.

14. Tailwind Dashboard Sidebar with Current Page State (opens in a new tab)

Published

Pure Tailwind
Where the user is has to be answered three times in an admin shell — in the rail, in the group that contains the page, and in the breadcrumb — and all three have to agree. This build derives every one of them from aria-current=&quot;page&quot; on a single link: the item marker through aria-[current=page]:, the group badge through group-has-[…]:, and the breadcrumb from the same word in the copy.
Newsletter

Enjoying these demos? Get new ones by email.

A short email when a new collection ships. Unsubscribe any time.

at

Almost there: check your inbox to confirm.Manage

15. Pure Tailwind Multi Level Sidebar Menu Disclosure Pattern (opens in a new tab)

Published

Pure Tailwind
Three levels deep, and the demo exists to document the one Tailwind trap that bites here: group-open: matches through EVERY open ancestor, so a nested tree rotates chevrons that are still closed. The fix is named groups — group/l1 and group/l2 with group-open/l1: and group-open/l2: — and once they are named, three levels cost no more than one.

16. Pure Tailwind Accordion Sidebar with Details and Summary (opens in a new tab)

Published

Pure Tailwind
One group open at a time, with no script and no state variable: the name attribute on <details> makes the browser enforce exclusivity, exactly like radio buttons. The port keeps every visual decision in utilities — chevron rotation through group-open:, the containing-group highlight through group-has-[…] — and the stylesheet holds nothing but tokens.

18. Pure Tailwind Floating Sidebar Stacking and Overlay Order (opens in a new tab)

Published

Pure Tailwind
A detached, glassy panel floating over the content — and the reason it is a machinery demo rather than a looks demo is isolate. The scene declares itself a stacking context on purpose, so the panel's z- values are resolved locally and cannot be outranked by something unrelated elsewhere on the page. The whole thing is utilities, including the blur.

19. Tailwind Off Canvas Sidebar Drawer with Focus Return (opens in a new tab)

Published

Tailwind + JS
The full contract in one demo, and the collection's closing argument for the port: focus in, focus trapped, background inert, <kbd>Esc</kbd> closes, focus returns — all in script, because all of it is behaviour — while every pixel of the drawer's two states lives in one class list on one element. Demos 02, 03 and 19 are the same lesson at increasing completeness.
FAQ

Frequently asked questions

How do I trap keyboard focus inside a Tailwind sidebar drawer and send it back when it closes?
A modal sidebar owes a keyboard user three things, and Demo 02 wires all three. On open, move focus into the drawer with element.focus() — usually the close button or first link. On Tab at the last focusable node, wrap to the first; on Shift+Tab at the first, wrap to the last. The trap is just a keydown handler that reads document.activeElement against the first and last items in your focusable set. The half everyone forgets is restore: before opening, save document.activeElement as the trigger, and on close call .focus() on it again so the user lands back on the hamburger, not at the top of the page. Demo 19 pairs this with an off-canvas drawer and Escape to close. Without restore, closing the menu dumps focus to <body> and the next Tab starts the whole page over.
What is the correct ARIA for a collapsible Tailwind sidebar rail?
The collapse control is a real <button>, not a clickable <div>, and it carries aria-expanded that flips between true and false as the rail widens and narrows (Demo 05). That single attribute is what tells a screen reader the rail has two states and which one it is in — a visual width change alone announces nothing. Give the button an aria-label like Collapse navigation since it is often icon-only. When the rail collapses to icons, the links still need accessible names: Demo 13 keeps a visually hidden text label next to each icon so the name survives even when the text is clipped, rather than leaning on a title tooltip that keyboard and screen-reader users never see. Do not toggle aria-hidden on the collapsed rail — it is still visible and operable, just narrower.
Why is my Tailwind sticky sidebar not sticking?
It is almost always one of two bugs, and Demo 17 is built to show both. First: position: sticky does nothing if any ancestor between the sidebar and the scroll container has overflow: hidden, auto, or scroll — the sticky element sticks to that clipped ancestor, which is not scrolling, so it looks dead. In Tailwind that is a stray overflow-hidden on a wrapper. Second: sticky needs a threshold, so top-0 (or some top value) must be set or there is nothing to stick against. The third gotcha is height: the sidebar's parent flex row has to be tall enough to scroll past, so the column needs self-start or the flex default stretch will force it full-height and it will never move. Check the ancestor chain for overflow first; that is the two-line fix ninety percent of the time.
How can I build a collapsible Tailwind sidebar with no JavaScript?
Two pure-CSS routes, both in the collection. Demo 11 uses a visually hidden <input type="checkbox"> as the state holder: a <label> is the toggle, and Tailwind's peer plus peer-checked: variants drive the width and the icon swap off the checkbox's checked state — no script, and the state is real DOM. For disclosure groups, Demo 16 uses <details> and <summary> with a shared name attribute so only one section opens at a time, giving you an accordion for free. Style the open state with the open: and group-open: variants. The tradeoff: checkbox state does not survive a reload — if you need persistence you have crossed into the JavaScript demos (Demo 07 writes the collapse state to storage and restores it on load). Pure CSS is for state that is fine to reset.
Why does group-open not work on my nested Tailwind submenu?
This is the trap Demo 15 exists to document. group-open: reacts to the nearest element marked with the group class, so once you nest a second <details> inside the first, an inner child styled with group-open: reads the inner group's open state — or, if you forgot to scope it, the wrong one entirely. The fix is Tailwind's named groups: mark the outer disclosure group/section and the inner one group/sub, then target them explicitly with group-open/section: and group-open/sub:. Without the names, three levels deep the rotation arrows and indent styles fire on the wrong level and submenus appear stuck open. Each <details> stays a native disclosure, so the keyboard and screen-reader behaviour is correct by default; the only work is keeping the group scopes distinct at every level.
Should sidebar navigation use tablist and tab roles?
No, and Demo 04 is the boundary marker for exactly this mistake. A sidebar is a set of links that navigate — they change the URL and load a new view — so they belong in a <nav> as an <ul> of <a> elements. A tab list is a set of controls that swap a panel in place without navigating, and it carries role="tablist", role="tab", aria-selected and arrow-key selection. Put tab semantics on nav links and you tell a screen reader the links are panel switchers, you inherit the tab keyboard model users do not expect from a menu, and aria-selected fights the correct signal. For the current page, use aria-current="page" instead (Demo 14 answers location in the rail, the open group, and the item). Reach for tabs only when the content genuinely stays on the same page.
How do I stop the page from scrolling when I reach the bottom of a Tailwind sidebar?
That is scroll chaining, and Demo 08 kills it with one property. When the sidebar's own scroll area hits its bottom or top, the browser passes the remaining wheel or touch momentum to the parent, so the whole page lurches — the classic two-scrollbar bug. Put overscroll-behavior: contain on the scrollable nav region (Tailwind's overscroll-contain) and the scroll stops at the boundary instead of bleeding through. Pair it with overflow-y-auto and a real height constraint so the region actually has its own scroll to contain. Demo 20 goes further for a pinned footer: a three-row flex column (flex flex-col) where the header and the user card are fixed rows and the middle nav takes flex-1 with overflow-y-auto and overscroll-contain, so only the nav scrolls and the footer card stays put.
How should a Tailwind sidebar behave differently on mobile versus desktop?
Its role changes, not just its width, which is the point of Demo 12. On a laptop the sidebar is a persistent rail — always visible, part of the layout, no overlay, and it should not trap focus or need a toggle to reach the content. On a phone the same component becomes a modal drawer: hidden until a hamburger opens it, rendered over the content, and now it owes the full modal contract — focus moves in, focus is trapped, Escape and a backdrop tap close it, and focus returns to the trigger (Demo 19). Drive the layout switch with a Tailwind breakpoint like lg:, but gate the focus-trap and inert logic on a matching matchMedia query so the trap is only active while the drawer is actually modal. Running the trap on the desktop rail is the common bug — it steals focus from a menu that was never supposed to be modal.

Related collections

32 CSS Accordions — Vertical & Horizontal preview

32 CSS Accordions — Vertical & Horizontal

32 free CSS accordions covering pure-CSS FAQ blocks with details/summary, mobile navigation menus, e-commerce filter sidebars, nested tree views, exclusive single-open groups and smoothly animated height:auto — plus 26 visual variations in vertical, horizontal and hybrid layouts. Copy-paste HTML and CSS, no JavaScript.

39 CSS Breadcrumbs preview

39 CSS Breadcrumbs

39 hand-coded CSS breadcrumbs — Schema.org BreadcrumbList Microdata + WCAG 2.2 accessible (Google Rich Results eligible), :has() responsive collapse, dark/light theme toggle with color-mix, Shopify/Amazon e-commerce filter chip removal, Vercel/Linear/Notion SaaS dashboard app-shell pattern, Docusaurus/Mintlify sticky scroll-shrink header, Amazon-style dropdown sibling menus, NFT/Web3 holographic shimmer (@property + conic-gradient), SaaS marketing gradient text, Airbnb filter chip with container queries, editorial blog wave underline, developer docs one-click path copy (Clipboard API), and 26 more. Semantic HTML: <nav aria-label='Breadcrumb'> + <ol> + aria-current='page'. Framework-agnostic, MIT-licensed.

26 CSS Circular Menus preview

26 CSS Circular Menus

26 hand-coded CSS circular and radial menu designs, from the floating-action FAB speed dial and dashboard radial hubs to game-style pie-slice wheels, SVG gooey metaball fans, hexagonal honeycombs, iris-aperture reveals, orbiting satellites on offset-path, compass-rose directional nav, sci-fi HUD rings, glassmorphism popovers built on the native Popover API, vinyl-record players, and full-takeover nebula overlays on native dialog. Every design uses CSS trigonometry (sin(), cos()) or the counter-rotation trick for positioning, ships real keyboard navigation, aria-labels on icon-only buttons, and a prefers-reduced-motion guard. Copy-paste HTML, CSS and vanilla JS with zero framework dependencies — MIT licensed.

Search CodeFronts

Loading…