Skip to content

Sub-menus

A sidebar for an application whose pages fall into a handful of sections, such as documentation: each section is a link of its own with its pages in a sub-menu below it, always open, so the whole structure is in view. The application’s name and version are at the top. The page’s header has the trigger and the breadcrumb. On a desktop the trigger moves the sidebar out of view; on a phone it opens the same sidebar as a panel. Switch the preview to Phone to see it.

Sub-menusA sidebar of sections, each with its pages in a sub-menu that is always open, as in documentation. The current page is a sub-button; the trigger moves the sidebar out of view on a desktop and opens it as a panel on a phone.
Rendered HTML
<a class="skip-link" href="#main">Skip to main content</a>
<div class="sidebar-provider" x-data="slSidebar" data-state="expanded">
<nav class="sidebar" id="sidebar" popover aria-label="Main">
<div class="sidebar-header">
<ul class="sidebar-menu">
<li class="sidebar-menu-item">
<a class="sidebar-menu-button" data-size="lg" href="/">
<span class="app-logo">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M7 2h10"></path>
<path d="M5 6h14"></path>
<rect width="18" height="12" x="3" y="10" rx="2"></rect>
</svg>
</span>
<span class="sidebar-menu-button-label">
<strong>Documentation</strong>
<small>v1.0.0</small>
</span>
</a>
</li>
</ul>
</div>
<div class="sidebar-content">
<div class="sidebar-group">
<ul class="sidebar-menu docs-nav">
<li class="sidebar-menu-item">
<a class="sidebar-menu-button" href="/getting-started">
<span class="sidebar-menu-button-label">Getting Started</span>
</a>
<ul class="sidebar-menu-sub">
<li class="sidebar-menu-sub-item">
<a class="sidebar-menu-sub-button" href="/getting-started/installation">
<span class="sidebar-menu-sub-button-label">Installation</span>
</a>
</li>
<li class="sidebar-menu-sub-item">
<a class="sidebar-menu-sub-button" href="/getting-started/project-structure">
<span class="sidebar-menu-sub-button-label">Project Structure</span>
</a>
</li>
</ul>
</li>
<li class="sidebar-menu-item">
<a class="sidebar-menu-button" href="/build">
<span class="sidebar-menu-button-label">Build Your Application</span>
</a>
<ul class="sidebar-menu-sub">
<li class="sidebar-menu-sub-item">
<a class="sidebar-menu-sub-button" href="/build/routing">
<span class="sidebar-menu-sub-button-label">Routing</span>
</a>
</li>
<li class="sidebar-menu-sub-item">
<a class="sidebar-menu-sub-button" aria-current="page" href="/build/data-fetching">
<span class="sidebar-menu-sub-button-label">Data Fetching</span>
</a>
</li>
<li class="sidebar-menu-sub-item">
<a class="sidebar-menu-sub-button" href="/build/rendering">
<span class="sidebar-menu-sub-button-label">Rendering</span>
</a>
</li>
<li class="sidebar-menu-sub-item">
<a class="sidebar-menu-sub-button" href="/build/caching">
<span class="sidebar-menu-sub-button-label">Caching</span>
</a>
</li>
<li class="sidebar-menu-sub-item">
<a class="sidebar-menu-sub-button" href="/build/styling">
<span class="sidebar-menu-sub-button-label">Styling</span>
</a>
</li>
</ul>
</li>
<li class="sidebar-menu-item">
<a class="sidebar-menu-button" href="/api">
<span class="sidebar-menu-button-label">API Reference</span>
</a>
<ul class="sidebar-menu-sub">
<li class="sidebar-menu-sub-item">
<a class="sidebar-menu-sub-button" href="/api/components">
<span class="sidebar-menu-sub-button-label">Components</span>
</a>
</li>
<li class="sidebar-menu-sub-item">
<a class="sidebar-menu-sub-button" href="/api/file-conventions">
<span class="sidebar-menu-sub-button-label">File Conventions</span>
</a>
</li>
<li class="sidebar-menu-sub-item">
<a class="sidebar-menu-sub-button" href="/api/functions">
<span class="sidebar-menu-sub-button-label">Functions</span>
</a>
</li>
</ul>
</li>
<li class="sidebar-menu-item">
<a class="sidebar-menu-button" href="/architecture">
<span class="sidebar-menu-button-label">Architecture</span>
</a>
<ul class="sidebar-menu-sub">
<li class="sidebar-menu-sub-item">
<a class="sidebar-menu-sub-button" href="/architecture/accessibility">
<span class="sidebar-menu-sub-button-label">Accessibility</span>
</a>
</li>
<li class="sidebar-menu-sub-item">
<a class="sidebar-menu-sub-button" href="/architecture/fast-refresh">
<span class="sidebar-menu-sub-button-label">Fast Refresh</span>
</a>
</li>
</ul>
</li>
<li class="sidebar-menu-item">
<a class="sidebar-menu-button" href="/community">
<span class="sidebar-menu-button-label">Community</span>
</a>
<ul class="sidebar-menu-sub">
<li class="sidebar-menu-sub-item">
<a class="sidebar-menu-sub-button" href="/community/contribution-guide">
<span class="sidebar-menu-sub-button-label">Contribution Guide</span>
</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<main class="sidebar-inset" id="main" tabindex="-1">
<header class="site-header">
<button class="button sidebar-trigger" type="button" data-variant="ghost" data-size="icon-sm" popovertarget="sidebar" aria-controls="sidebar" aria-label="Toggle sidebar">
<svg xmlns="http://www.w3.org/2000/svg" class="icon sidebar-trigger-icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<rect width="18" height="18" x="3" y="3" rx="2"></rect>
<path d="M9 3v18"></path>
</svg>
</button>
<div class="separator" data-orientation="vertical" role="none"></div>
<nav class="breadcrumb" aria-label="Breadcrumb">
<ol class="breadcrumb-list">
<li class="breadcrumb-item">
<a class="breadcrumb-link" href="/build">Build Your Application</a>
</li>
<li class="breadcrumb-separator" role="presentation" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="icon breadcrumb-separator-icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="m9 18 6-6-6-6"></path>
</svg>
</li>
<li class="breadcrumb-item">
<span class="breadcrumb-page" aria-current="page">Data Fetching</span>
</li>
</ol>
</nav>
</header>
<div class="page">
<h1>Data Fetching</h1>
<div class="placeholder-grid">
<div class="placeholder"></div>
<div class="placeholder"></div>
<div class="placeholder"></div>
</div>
<div class="placeholder placeholder-tall"></div>
</div>
</main>
</div>

For reference: the shadcn/ui block this layout is based on.

The floating variant has the same menus in a panel with a margin round it.

The whole layout file. It has what every layout needs: the theme script and Shadleaf’s assets in the head, the skip link first, one main (<sl:sidebar-inset>), the page’s title, and the toaster. #slNav.current marks the current page from the request: the sections compare the exact path (true), so a section is not marked as well when one of its pages is shown.

templates/layout/main.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sl="https://shadleaf.dev/sl"
th:fragment="layout(pageTitle, breadcrumb, content)">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title th:text="${pageTitle}">Page</title>
<th:block th:replace="~{sl/layout :: theme-script}"></th:block>
<th:block th:replace="~{sl/layout :: assets}"></th:block>
<link rel="stylesheet" th:href="@{/css/application.css}">
</head>
<body>
<sl:skip-link/>
<sl:sidebar-provider>
<sl:sidebar>
<sl:sidebar-header>
<sl:sidebar-menu>
<sl:sidebar-menu-item>
<sl:sidebar-menu-button th:href="@{/}" size="lg">
<sl:slot name="icon-start"><span class="app-logo"><sl:icon name="gallery-vertical-end"/></span></sl:slot>
<strong>Documentation</strong>
<small>v1.0.0</small>
</sl:sidebar-menu-button>
</sl:sidebar-menu-item>
</sl:sidebar-menu>
</sl:sidebar-header>
<sl:sidebar-content>
<sl:sidebar-group>
<sl:sidebar-menu class="docs-nav">
<sl:sidebar-menu-item>
<sl:sidebar-menu-button th:href="@{/getting-started}"
th:active="${#slNav.current('/getting-started', true)}">
Getting Started
</sl:sidebar-menu-button>
<sl:sidebar-menu-sub>
<sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-button th:href="@{/getting-started/installation}"
th:active="${#slNav.current('/getting-started/installation')}">
Installation
</sl:sidebar-menu-sub-button>
</sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-button th:href="@{/getting-started/project-structure}"
th:active="${#slNav.current('/getting-started/project-structure')}">
Project Structure
</sl:sidebar-menu-sub-button>
</sl:sidebar-menu-sub-item>
</sl:sidebar-menu-sub>
</sl:sidebar-menu-item>
<sl:sidebar-menu-item>
<sl:sidebar-menu-button th:href="@{/build}"
th:active="${#slNav.current('/build', true)}">
Build Your Application
</sl:sidebar-menu-button>
<sl:sidebar-menu-sub>
<sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-button th:href="@{/build/routing}"
th:active="${#slNav.current('/build/routing')}">
Routing
</sl:sidebar-menu-sub-button>
</sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-button th:href="@{/build/data-fetching}"
th:active="${#slNav.current('/build/data-fetching')}">
Data Fetching
</sl:sidebar-menu-sub-button>
</sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-button th:href="@{/build/rendering}"
th:active="${#slNav.current('/build/rendering')}">
Rendering
</sl:sidebar-menu-sub-button>
</sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-button th:href="@{/build/caching}"
th:active="${#slNav.current('/build/caching')}">
Caching
</sl:sidebar-menu-sub-button>
</sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-button th:href="@{/build/styling}"
th:active="${#slNav.current('/build/styling')}">
Styling
</sl:sidebar-menu-sub-button>
</sl:sidebar-menu-sub-item>
</sl:sidebar-menu-sub>
</sl:sidebar-menu-item>
<sl:sidebar-menu-item>
<sl:sidebar-menu-button th:href="@{/api}"
th:active="${#slNav.current('/api', true)}">
API Reference
</sl:sidebar-menu-button>
<sl:sidebar-menu-sub>
<sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-button th:href="@{/api/components}"
th:active="${#slNav.current('/api/components')}">
Components
</sl:sidebar-menu-sub-button>
</sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-button th:href="@{/api/file-conventions}"
th:active="${#slNav.current('/api/file-conventions')}">
File Conventions
</sl:sidebar-menu-sub-button>
</sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-button th:href="@{/api/functions}"
th:active="${#slNav.current('/api/functions')}">
Functions
</sl:sidebar-menu-sub-button>
</sl:sidebar-menu-sub-item>
</sl:sidebar-menu-sub>
</sl:sidebar-menu-item>
<sl:sidebar-menu-item>
<sl:sidebar-menu-button th:href="@{/architecture}"
th:active="${#slNav.current('/architecture', true)}">
Architecture
</sl:sidebar-menu-button>
<sl:sidebar-menu-sub>
<sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-button th:href="@{/architecture/accessibility}"
th:active="${#slNav.current('/architecture/accessibility')}">
Accessibility
</sl:sidebar-menu-sub-button>
</sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-button th:href="@{/architecture/fast-refresh}"
th:active="${#slNav.current('/architecture/fast-refresh')}">
Fast Refresh
</sl:sidebar-menu-sub-button>
</sl:sidebar-menu-sub-item>
</sl:sidebar-menu-sub>
</sl:sidebar-menu-item>
<sl:sidebar-menu-item>
<sl:sidebar-menu-button th:href="@{/community}"
th:active="${#slNav.current('/community', true)}">
Community
</sl:sidebar-menu-button>
<sl:sidebar-menu-sub>
<sl:sidebar-menu-sub-item>
<sl:sidebar-menu-sub-button th:href="@{/community/contribution-guide}"
th:active="${#slNav.current('/community/contribution-guide')}">
Contribution Guide
</sl:sidebar-menu-sub-button>
</sl:sidebar-menu-sub-item>
</sl:sidebar-menu-sub>
</sl:sidebar-menu-item>
</sl:sidebar-menu>
</sl:sidebar-group>
</sl:sidebar-content>
</sl:sidebar>
<sl:sidebar-inset>
<sl:site-header>
<sl:sidebar-trigger/>
<sl:separator orientation="vertical"/>
<sl:breadcrumb>
<sl:breadcrumb-list>
<th:block th:replace="${breadcrumb}"></th:block>
</sl:breadcrumb-list>
</sl:breadcrumb>
</sl:site-header>
<div class="page">
<h1 th:text="${pageTitle}"></h1>
<div th:replace="${content}"></div>
</div>
</sl:sidebar-inset>
</sl:sidebar-provider>
<div id="modal-root"></div>
<sl:toaster>
<sl:toast th:each="toast : ${toasts}" th:title="${toast.title}" th:description="${toast.description}"
th:variant="${toast.variant}" th:duration="${toast.duration}"/>
</sl:toaster>
</body>
</html>

A page passes its title, its breadcrumb’s levels and its content. The levels are a th:block with th:ref, so nothing of it is rendered where it stands; the layout puts them in its <sl:breadcrumb-list>.

templates/data-fetching.html
<html xmlns:th="http://www.thymeleaf.org" xmlns:sl="https://shadleaf.dev/sl"
th:replace="~{layout/main :: layout('Data Fetching', ~{::breadcrumb}, ~{::#page-content})}">
<body>
<th:block th:ref="breadcrumb">
<sl:breadcrumb-item>
<sl:breadcrumb-link th:href="@{/build}">Build Your Application</sl:breadcrumb-link>
</sl:breadcrumb-item>
<sl:breadcrumb-separator/>
<sl:breadcrumb-item>
<sl:breadcrumb-page>Data Fetching</sl:breadcrumb-page>
</sl:breadcrumb-item>
</th:block>
<div id="page-content">
...
</div>
</body>
</html>

Shadleaf’s CSS styles the components, not the page around them, and has no utility classes. The rest is the application’s CSS, here without Tailwind: the square behind the logo, the weight of the sections’ links (the docs-nav class on the menu), a border below the header, and the page’s padding.

static/css/application.css
/* The square behind the logo in the sidebar's header. */
.app-logo {
display: grid;
place-items: center;
flex-shrink: 0;
inline-size: 2rem;
block-size: 2rem;
border-radius: calc(var(--radius) - 2px);
background: var(--sidebar-primary);
color: var(--sidebar-primary-foreground);
}
.app-logo > svg { inline-size: 1rem; block-size: 1rem; }
/* The sections' links in a medium weight, their pages' in the normal one. */
.docs-nav > .sidebar-menu-item > .sidebar-menu-button { font-weight: 500; }
/* A border below the page's header, and the page's padding. */
.sidebar-inset > .site-header { border-block-end: 1px solid var(--border); }
.page { padding: 1rem; }
.page > h1 { margin-block-end: 1rem; font-size: 1.5rem; font-weight: 600; }