bx-sites-themes
Choose, customize, override, install, or write a theme for a bx-sites (ortus-boxlang/bx-sites) site - the 10 built-in themes, air-gapped/offline considerations, the ThemeProvider contract (layout.bxm/page.bxm), color-only customization via extraCss, ejecting/overriding a theme, writing one from scratch, installing a published theme from ForgeBox, importing an mkdocs/jekyll/hugo theme, and the homepage hero banner. Use this whenever a user wants to change a bx-sites site's look, brand colors, or write/override its templates.
BxSites Themes Reference
Themes are native BoxLang .bxm templates - no separate template engine or
build step.
Built in
| Theme | Base | Notes |
|---|---|---|
bootstrap (default) | Bootstrap 5, vendored | Poppins font, brand gradient navbar |
material | Hand-rolled Material-style CSS | Card layout, elevation shadows, Roboto |
tailwind | Tailwind Play CDN | Utility-class driven, no build step, not air-gapped |
docsy | Forked from material | Read the Docs/Docsy-inspired navy reference-manual look |
slate | Forked from material | Stripe/Slate-inspired - permanently dark sidebar |
docusaurus | Forked from material | Bold full-width colored navbar, rounded cards |
justthedocs | Forked from material | Minimalist; search box at top of sidebar |
vuepress | Forked from material | Green accent, soft rounded corners |
gitbook | Forked from material | Centered reading column, serif headings |
notion | Forked from material | Borderless sidebar, near-grayscale, generous whitespace |
The seven material-forked themes reuse material's exact .bxm templates
unchanged except a scoped CSS-class-prefix rename (and, for justthedocs,
one relocated <bx:include> line) - same full feature set, same
air-gapped-capable behavior. bootstrap/material/tailwind share the
BoxLang brand palette (#00FF78 -> #00DBFF gradient, #FFF500 accent); the
seven gallery themes each use their own distinct palette.
Every theme ships the same feature set regardless of palette: in-page TOC,
breadcrumbs, prev/next links, highlight.js code blocks with copy buttons,
self-hosted webfonts, dark/light toggle (Alpine.js, localStorage
remembered), responsive header + collapsible sidebar, / and Cmd/Ctrl+K
search shortcuts, repo/edit-page/last-updated line, Download-Markdown link,
opt-in footer, version switcher, themed 404.html (override with
docs/404.md), custom logo/favicon, collapsible nav, Google Analytics,
social share cards, page tags/icon/summary, nav override support, extra
CSS/JS injection, admonitions/footnotes/definition lists, content tabs, code
annotations, responsive images, Mermaid, math - see bx-sites-markdown and
bx-sites-content-blocks for that content-side syntax, and
bx-sites-configuration for every key mentioned here.
theme: { name: material }
Installing a published theme
bxSites install:theme --name=bx-sites-theme-blog1 [--version=1.0.0]
Downloads from ForgeBox into themes/bx-sites-theme-blog1/ at the project
root, validating the ThemeProvider contract before finishing (a broken
package fails at install time, not at the next build). No separate
activation step (unlike a plugin - see bx-sites-plugins) - just set
theme.name to match. Browse published themes under ForgeBox's
bxsites-themes category.
Air-gapped / offline sites
Works with zero internet access by default for bootstrap, material, and
the seven gallery themes, with the default local search provider (see
bx-sites-search) - Bootstrap CSS/JS, highlight.js, Alpine.js, MiniSearch
all vendored (resources/assets/vendor/), no CDN tag anywhere. Turning on
mermaid vendors it the same way.
Still reach the network only when turned on: tailwind's utility engine
(CDN JIT compiler - not air-gapped-capable); Mermaid's elk-layout diagrams
lazy-load one chunk from jsDelivr; math loads KaTeX from a CDN;
searchProvider.provider: algolia and analytics.provider: google
inherently talk to a hosted endpoint. For a genuinely air-gapped deployment:
stick to bootstrap/material/a gallery theme, the local search
provider, avoid elk-layout Mermaid, leave math/Algolia/analytics off.
The ThemeProvider contract
A theme is a folder with:
layout.bxm(required) - outer HTML shell + nav. Receivesvariables.page,variables.nav,variables.siteConfig,variables.themeDir,variables.basePath(root-relative, ends/- prefix every internalhref/srcwith it rather than hardcoding a leading/). Includespage.bxmvia#variables.themeDir#/page.bxm.page.bxm(required) - article body. Rendersvariables.page.contentHtml(already-converted markdown).search.bxm(optional) - search box markup, included only whensearch: true.assets/(optional) - theme CSS/JS, copied tosite/assets/theme/.
Also available: variables.page.editUrl/.lastUpdated (empty strings if
unconfigured), variables.siteConfig.repo/.social/.footer,
variables.versions ([ { label, url } ], "Latest" first) +
variables.currentVersion, and a shared icon include
(<bx:include template="#variables.moduleAssetsDir#/icons.bxm">, defines
bxsitesIcon( name )).
A theme folder missing either required file fails fast with
BxSites.InvalidTheme at build time.
Resolution order: project theme/ override โ installed
themes/<name>/ matching theme.name โ a built-in theme named theme.name.
Customizing colors without a full override
Each built-in theme reads its palette from CSS custom properties on :root
(re-declared under [data-theme="dark"]). extraCss (see
bx-sites-configuration) loads after the theme's own stylesheet, so a
same-specificity re-declaration wins:
extraCss: [ assets/brand.css ]
:root {
--bxsites-gradient-start: #7C3AED;
--bxsites-gradient-end: #DB2777;
--bxsites-accent: #FBBF24;
--bxsites-link: #7C3AED;
--bxsites-link-hover: #9F5AF0;
}
[data-theme="dark"] {
--bxsites-link: #C4B5FD;
--bxsites-link-hover: #DDD6FE;
}
Every built-in theme guarantees --bxsites-gradient-start/-end,
--bxsites-accent, and the --bxsites-step-* set (backing the
::: stepper block - see bx-sites-content-blocks) under those exact
names. Only bootstrap, slate, and notion also expose
--bxsites-bg/-text/-sidebar-bg/-sidebar-text/-border/-link/
-link-hover/-code-bg under those names (justthedocs aliases all but
the two -sidebar-*); every other theme (material, tailwind, docsy,
docusaurus, vuepress, gitbook) uses its own internal custom-property
names for that group - open that theme's own assets/style.css to find its
real names first. Anything beyond color/font needs a real override.
Overriding a theme
Drop layout.bxm + page.bxm (and optionally search.bxm/assets/) into
a theme/ folder at the project root - the built-in themes under this
module's resources/themes/ are good starting points to copy:
bxSites theme:new --theme=bootstrap
then edit only what's needed - e.g. swap the brand palette/font in
theme/assets/style.css. bxSites build/serve pick up theme/
automatically, no config change needed - it takes precedence over
theme.name entirely. All-or-nothing: once a project theme/ exists it
needs its own layout.bxm + page.bxm even for a CSS-only change (missing
either fails with BxSites.InvalidTheme) - for CSS-only, prefer extraCss
above instead.
Writing a theme from scratch
The absolute minimum - no Bootstrap/Tailwind, no dark mode, no search UI:
<!-- theme/layout.bxm -->
<bx:script>
function renderNav( required array nodes ) {
var html = "<ul>"
for ( var node in arguments.nodes ) {
html &= "<li>"
html &= len( node.url )
? '<a href="' & variables.basePath & node.url & '">' & encodeForHTML( node.title ) & '</a>'
: encodeForHTML( node.title )
if ( node.children.len() ) {
html &= renderNav( node.children )
}
html &= "</li>"
}
return html & "</ul>"
}
</bx:script>
<bx:output>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>#encodeForHTML( variables.page.title )# - #encodeForHTML( variables.siteConfig.name )#</title>
<link rel="stylesheet" href="#variables.basePath#assets/theme/style.css">
</head>
<body>
<header><a href="#variables.basePath#">#encodeForHTML( variables.siteConfig.name )#</a></header>
<nav>#renderNav( variables.nav )#</nav>
<main>
</bx:output>
<bx:include template="#variables.themeDir#/page.bxm">
<bx:output>
</main>
</body>
</html>
</bx:output>
<!-- theme/page.bxm -->
<bx:output>
<article>
<h1>#encodeForHTML( variables.page.title )#</h1>
#variables.page.contentHtml#
</article>
</bx:output>
variables.page.contentHtml is already fully converted (syntax
highlighting, admonitions, tabs, math, all of it) - there's nothing left to
parse, only lay out. Add breadcrumbs/tags/prev-next/dark-mode/search by
copying the pattern from a built-in theme's own page.bxm/layout.bxm (a
built-in search.bxm is only included when search: true).
Importing a theme from another SSG
bxSites theme:import --source=mkdocs --path=/path/to/theme --name=my-imported-theme
--source is mkdocs, jekyll, or hugo. Best-effort conversion into a
themes/<name>/ scaffold - a starting point, not lossless. Safe to re-run
against the same --name (.bxm files overwritten, new asset folders
merged in).
Homepage hero banner
No directive/config needed - plain HTML any page (typically index.md) can
drop in, using CSS every built-in theme already ships:
<div class="bxsites-hero">
<img class="bxsites-hero__banner" src="assets/home-banner.jpg" alt="...">
<div class="bxsites-hero__actions">
<a class="bxsites-hero__btn bxsites-hero__btn--primary" href="getting-started.md">Get Started</a>
<a class="bxsites-hero__btn bxsites-hero__btn--secondary" href="https://github.com/your/repo">View on GitHub</a>
</div>
</div>