Builders Reference: Shell, Navigation, Banner, Account Bar
ShellBuilder
ShellBuilder creates full app shell HTML.
Core options:
withTopBanner(...)withTopNav(...)withAccountBar(...)(deprecated alias forwithTopNav(...))withSideNav(...)withSideNav(..., boolean collapsible)withCollapsibleSideNav(boolean)withContentTarget(...)withContentTargetId(...)withContentTargetClass(...)withContentWrapper(Function<Component, Component>)withContent(...)buildTemplate()withPageTitle(...)withHtmx(boolean)withFrameworkCss(boolean)withFrameworkCssPath(String)withCustomCss(String)withCustomCss(List<String>)addCustomCss(String)withCustomJs(String)withCustomJs(List<String>)addCustomJs(String)buildBody()
Returns full HTML document string from build().
buildTemplate() returns a reusable ShellTemplate (compiled template wrapper) with a dedicated content slot for request-time content injection.
When a sidebar is configured, full/template/body output includes a named Toggle navigation
button with aria-controls="main-sidebar" and synchronized expanded state. At widths through
768px, closed off-canvas navigation is inert and hidden from assistive technology. Opening focuses
a usable navigation control; Escape closes and returns to the toggle, and outside clicks dismiss.
Resize restores desktop usability while preserving desktop collapsed state; mobile opening ignores
that collapse styling.
Collapsible sidebars also include a named native Toggle sidebar button with type="button" in
full, template, and body-only output. Shared framework JS provides the existing toggleSidebar()
global and initializes existing and later direct/HTMX fragments once, using the sidebar's owning
main container. Desktop preference uses the existing sidebarCollapsed key; denied storage leaves
document-local toggling functional. Desktop collapse cannot activate behind a modal and does not
change the mobile drawer's open state.
buildBody() emits only the .shell-body fragment, optional sidebar/mobile toggle, and main content
target. It omits document tags, header/banner/top navigation, assets, and all inline scripts. The
host loads framework CSS/JS once, plus HTMX when used; do not include assets in each fragment.
Content target, wrapper, and initial-content/HTMX auto-load behavior remain available. Full/template
shells keep their inline HTMX active-navigation script; desktop initialization is no longer inline.
The mobile toggle is unavailable behind a modal. Modal stack offsets are managed by JS, but CSS
owns the base layer through --sp-modal-layer (default 1100); theme overrides should keep dialogs
above navigation. See mobile behavior.
Stylesheet load order in build():
- framework CSS (if enabled)
- custom CSS files in configured order
Script load order in build():
- HTMX script (if enabled)
- framework JS (
/js/framework.js) - custom JS files in configured order
These deferred-script rules also apply to compiled templates. When HTMX is included, the existing
framework script privately initializes the bundled 2.0.11 client with the legacy request-origin,
DELETE-body, and smooth-scroll defaults before custom JS. Explicit upstream htmx-config meta,
immediate custom-script assignments, and custom ready handlers can override them. Initialization
is not repeated after swaps or fragment insertion.
withHtmx(false) omits the HTMX asset and private initialization marker. Body-only output always
omits assets, scripts, and that marker; manual hosts own their HTMX configuration. The marker is
not a public option, and there is no new fluent API or automatically loaded compatibility asset.
See build requirements and assets for manual-host configuration, bounded
Form-header compatibility, and application-owned destination/CORS/CSRF boundaries.
Custom JS setup example:
String shell = ShellBuilder.create()
.withPageTitle("Admin Portal")
.withCustomJs("/js/app.js")
.addCustomJs("/js/pages/admin-dashboard.js")
.withContentWrapper(content -> new HtmlTag("section")
.withClass("content-shell")
.withChild(content))
.build();
Template Shell Reuse
Use buildTemplate() when shell chrome is stable and page content changes per request.
ShellTemplate shellTemplate = ShellBuilder.create()
.withPageTitle("Admin Portal")
.withTopNav(topNav)
.buildTemplate();
String html = shellTemplate.renderWithContent(pageComponent);
Notes:
ShellTemplate.renderWithContent(...)renders full document HTML (with doctype).- If
withContent(...)was configured beforebuildTemplate(), that content becomes default slot content. - Template mode does not emit shell content auto-load attributes (
hx-get="/home",hx-trigger="load"). - Template heads use the same HTMX inclusion, private early defaults, deferred order, and application-override rules as full builds; request content does not change that ownership.
SideNavBuilder and TopNavBuilder
Use nav builders to produce stable navigation components.
Guidance:
- Keep labels and URLs as data, not hardcoded in controllers.
- Keep active-state logic deterministic.
- Prefer
TopNavBuilderfor header-level nav composition (primary links, utility links/dropdowns, account widgets).
Generated navigation link URLs are HTML-encoded in attributes, but these builders do not
validate their schemes or destinations. Supply application-controlled URLs or validate them for
navigation and, where used as hx-get, for the intended endpoint. Encoding does not authorize
requests. This differs from AccountBarBuilder link helpers, which apply SafeUrl validation.
BannerBuilder
Use for app or area-level brand/title/banner composition.
withBackgroundColor(...) and withTextColor(...) are assembled into raw inline CSS, not
through addStyle(...); declaration separators are not rejected. For IMAGE_OVERLAY,
withBackgroundImage(...) is interpolated into a single-quoted CSS url(...) without CSS
escaping or image URL validation. HTML attribute encoding does not prevent CSS declaration
breakout. Supply application-controlled values or validate them for their CSS context.
AccountBarBuilder
Legacy compatibility builder. Prefer TopNavBuilder for new work.
withBackgroundColor(...) is assembled into a raw inline style attribute, not through the
hardened addStyle(...) path. Declaration separators are retained; HTML attribute encoding does
not prevent CSS declaration breakout. Supply an application-controlled value or validate it for
its CSS context.
addLeftLink(...) and addRightLink(...) use SafeUrl href validation. This does not authorize
destinations or validate arbitrary supplied components or dynamic account widget endpoints.
Minimal Shell Example
String shell = ShellBuilder.create()
.withPageTitle("Admin Portal")
.withTopBanner(BannerBuilder.create()
.withTitle("Admin Portal")
.build())
.withSideNav(SideNavBuilder.create()
.addSection("Main")
.addLink("Dashboard", "/dashboard", "")
.build(), true)
.withCustomCss("/css/app.css")
.addCustomCss("/css/pages/dashboard.css")
.build();