Collapsible sub-menus
The sub-menus layout for more pages than fit in view: every section is a
collapsible item that opens and closes its sub-menu, and the server
opens only the section that holds the current page (th:open="${#slNav.current('/build')}"). The sections are no
pages of their own: the whole row opens and closes. At the top, the application’s name and version and a search
field. 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.
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> <form class="docs-search" role="search" action="/search"> <input class="input" type="search" name="q" placeholder="Search the docs..." aria-label="Search the docs"> <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="m21 21-4.34-4.34"></path> <circle cx="11" cy="11" r="8"></circle> </svg> </form> </div> <div class="sidebar-content"> <div class="sidebar-group"> <ul class="sidebar-menu"> <li class="sidebar-menu-item"> <details class="sidebar-menu-collapsible"> <summary class="sidebar-menu-button"> <span class="sidebar-menu-button-label">Getting Started</span> <svg xmlns="http://www.w3.org/2000/svg" class="icon sidebar-menu-button-chevron" 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> </summary> <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> </details> </li> <li class="sidebar-menu-item"> <details class="sidebar-menu-collapsible" open> <summary class="sidebar-menu-button"> <span class="sidebar-menu-button-label">Build Your Application</span> <svg xmlns="http://www.w3.org/2000/svg" class="icon sidebar-menu-button-chevron" 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> </summary> <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> <li class="sidebar-menu-sub-item"> <a class="sidebar-menu-sub-button" href="/build/optimizing"> <span class="sidebar-menu-sub-button-label">Optimizing</span> </a> </li> <li class="sidebar-menu-sub-item"> <a class="sidebar-menu-sub-button" href="/build/configuring"> <span class="sidebar-menu-sub-button-label">Configuring</span> </a> </li> <li class="sidebar-menu-sub-item"> <a class="sidebar-menu-sub-button" href="/build/testing"> <span class="sidebar-menu-sub-button-label">Testing</span> </a> </li> <li class="sidebar-menu-sub-item"> <a class="sidebar-menu-sub-button" href="/build/authentication"> <span class="sidebar-menu-sub-button-label">Authentication</span> </a> </li> <li class="sidebar-menu-sub-item"> <a class="sidebar-menu-sub-button" href="/build/deploying"> <span class="sidebar-menu-sub-button-label">Deploying</span> </a> </li> <li class="sidebar-menu-sub-item"> <a class="sidebar-menu-sub-button" href="/build/upgrading"> <span class="sidebar-menu-sub-button-label">Upgrading</span> </a> </li> <li class="sidebar-menu-sub-item"> <a class="sidebar-menu-sub-button" href="/build/examples"> <span class="sidebar-menu-sub-button-label">Examples</span> </a> </li> </ul> </details> </li> <li class="sidebar-menu-item"> <details class="sidebar-menu-collapsible"> <summary class="sidebar-menu-button"> <span class="sidebar-menu-button-label">API Reference</span> <svg xmlns="http://www.w3.org/2000/svg" class="icon sidebar-menu-button-chevron" 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> </summary> <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> <li class="sidebar-menu-sub-item"> <a class="sidebar-menu-sub-button" href="/api/configuration-options"> <span class="sidebar-menu-sub-button-label">Configuration Options</span> </a> </li> <li class="sidebar-menu-sub-item"> <a class="sidebar-menu-sub-button" href="/api/cli"> <span class="sidebar-menu-sub-button-label">CLI</span> </a> </li> <li class="sidebar-menu-sub-item"> <a class="sidebar-menu-sub-button" href="/api/edge-runtime"> <span class="sidebar-menu-sub-button-label">Edge Runtime</span> </a> </li> </ul> </details> </li> <li class="sidebar-menu-item"> <details class="sidebar-menu-collapsible"> <summary class="sidebar-menu-button"> <span class="sidebar-menu-button-label">Architecture</span> <svg xmlns="http://www.w3.org/2000/svg" class="icon sidebar-menu-button-chevron" 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> </summary> <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> <li class="sidebar-menu-sub-item"> <a class="sidebar-menu-sub-button" href="/architecture/compiler"> <span class="sidebar-menu-sub-button-label">Compiler</span> </a> </li> <li class="sidebar-menu-sub-item"> <a class="sidebar-menu-sub-button" href="/architecture/supported-browsers"> <span class="sidebar-menu-sub-button-label">Supported Browsers</span> </a> </li> <li class="sidebar-menu-sub-item"> <a class="sidebar-menu-sub-button" href="/architecture/bundler"> <span class="sidebar-menu-sub-button-label">Bundler</span> </a> </li> </ul> </details> </li> <li class="sidebar-menu-item"> <details class="sidebar-menu-collapsible"> <summary class="sidebar-menu-button"> <span class="sidebar-menu-button-label">Community</span> <svg xmlns="http://www.w3.org/2000/svg" class="icon sidebar-menu-button-chevron" 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> </summary> <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> </details> </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. Its plus and minus are Shadleaf’s turning chevron here.
The layout
Section titled “The layout”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.
<!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> <form class="docs-search" role="search" th:action="@{/search}"> <sl:input type="search" name="q" placeholder="Search the docs..." aria-label="Search the docs"/> <sl:icon name="search"/> </form> </sl:sidebar-header> <sl:sidebar-content> <sl:sidebar-group> <sl:sidebar-menu> <sl:sidebar-menu-item collapsible th:open="${#slNav.current('/getting-started')}"> <sl:sidebar-menu-button>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 collapsible th:open="${#slNav.current('/build')}"> <sl:sidebar-menu-button>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-item> <sl:sidebar-menu-sub-button th:href="@{/build/optimizing}" th:active="${#slNav.current('/build/optimizing')}">Optimizing</sl:sidebar-menu-sub-button> </sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-button th:href="@{/build/configuring}" th:active="${#slNav.current('/build/configuring')}">Configuring</sl:sidebar-menu-sub-button> </sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-button th:href="@{/build/testing}" th:active="${#slNav.current('/build/testing')}">Testing</sl:sidebar-menu-sub-button> </sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-button th:href="@{/build/authentication}" th:active="${#slNav.current('/build/authentication')}">Authentication</sl:sidebar-menu-sub-button> </sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-button th:href="@{/build/deploying}" th:active="${#slNav.current('/build/deploying')}">Deploying</sl:sidebar-menu-sub-button> </sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-button th:href="@{/build/upgrading}" th:active="${#slNav.current('/build/upgrading')}">Upgrading</sl:sidebar-menu-sub-button> </sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-button th:href="@{/build/examples}" th:active="${#slNav.current('/build/examples')}">Examples</sl:sidebar-menu-sub-button> </sl:sidebar-menu-sub-item> </sl:sidebar-menu-sub> </sl:sidebar-menu-item> <sl:sidebar-menu-item collapsible th:open="${#slNav.current('/api')}"> <sl:sidebar-menu-button>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-item> <sl:sidebar-menu-sub-button th:href="@{/api/configuration-options}" th:active="${#slNav.current('/api/configuration-options')}">Configuration Options</sl:sidebar-menu-sub-button> </sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-button th:href="@{/api/cli}" th:active="${#slNav.current('/api/cli')}">CLI</sl:sidebar-menu-sub-button> </sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-button th:href="@{/api/edge-runtime}" th:active="${#slNav.current('/api/edge-runtime')}">Edge Runtime</sl:sidebar-menu-sub-button> </sl:sidebar-menu-sub-item> </sl:sidebar-menu-sub> </sl:sidebar-menu-item> <sl:sidebar-menu-item collapsible th:open="${#slNav.current('/architecture')}"> <sl:sidebar-menu-button>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-item> <sl:sidebar-menu-sub-button th:href="@{/architecture/compiler}" th:active="${#slNav.current('/architecture/compiler')}">Compiler</sl:sidebar-menu-sub-button> </sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-button th:href="@{/architecture/supported-browsers}" th:active="${#slNav.current('/architecture/supported-browsers')}">Supported Browsers</sl:sidebar-menu-sub-button> </sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-item> <sl:sidebar-menu-sub-button th:href="@{/architecture/bundler}" th:active="${#slNav.current('/architecture/bundler')}">Bundler</sl:sidebar-menu-sub-button> </sl:sidebar-menu-sub-item> </sl:sidebar-menu-sub> </sl:sidebar-menu-item> <sl:sidebar-menu-item collapsible th:open="${#slNav.current('/community')}"> <sl:sidebar-menu-button>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
Section titled “A page”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>.
<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>The CSS
Section titled “The CSS”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 magnifier in the search field, a border below the header, and the page’s padding.
/* 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 search field: a magnifier inside it, at the start. */.docs-search { position: relative; }.docs-search > .input { block-size: 2rem; padding-inline-start: 2rem; background: var(--background);}.docs-search > svg { position: absolute; inset-inline-start: 0.5rem; inset-block-start: 50%; translate: 0 -50%; inline-size: 1rem; block-size: 1rem; opacity: 0.5; pointer-events: none;}
/* 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; }