Themes

On this page

Themes

Themes are native BoxLang .bxm templates - there's no separate template engine or build step involved.

Built in

ThemeBaseNotes
bootstrap (default)Bootstrap 5, vendoredPoppins font, brand gradient navbar
materialHand-rolled Material-style CSSCard layout, elevation shadows, Roboto font
tailwindTailwind Play CDNUtility-class driven, no build step

Every built-in theme's own CSS/JS (Bootstrap's CSS/JS bundle, highlight.js, Alpine.js, lunr.js for the default local search provider, and Mermaid when mermaid is turned on) ships vendored with this module and is copied straight into every built site/ - no CDN, no internet access needed to view a built site. The tailwind theme's own utility engine (a client-side JIT compiler, not a static stylesheet) and other optional features you turn on yourself (math, Algolia search, Google Analytics) still load from a CDN or a hosted API - see Air-gapped/offline sites below.

All three apply the same BoxLang brand palette: a #00FF78 -> #00DBFF gradient and a #FFF500 accent - and all three ship with the same set of page features:

  • An in-page "On this page" table of contents, generated from each page's own h2/h3 headings.
  • Breadcrumbs, showing a page's ancestor chain when it's nested more than one level deep under a linked ancestor.
  • Prev/next page links at the bottom of the article, following the nav's own reading order.
  • Syntax-highlighted code blocks, via highlight.js plus a BoxLang grammar of its own (```bx/```boxlang/```cfscript), each with a copy button - shown on hover on devices that support it, always visible on touch devices (there's no hover to reveal it there). See Markdown Extensions.
  • Self-hosted webfonts - no fonts.googleapis.com request at view time.
  • A dark/light mode toggle, powered by Alpine.js for reactivity. The visitor's choice is remembered in localStorage (falling back to their OS preference), and applied before first paint to avoid a flash of the wrong theme.
  • A responsive header that stays a single row at every width - a narrow viewport shrinks the search box rather than wrapping it onto its own line - plus a collapsible sidebar nav (a hamburger toggle in bootstrap/material/tailwind alike).
  • Keyboard shortcuts in the search box: / focuses search from anywhere on the page, and Escape closes the results. See Search.
  • A repo link and "Edit this page"/"Last updated" line, when bxsites.json's repo/lastUpdated options are set. See Configuration.
  • A "Download Markdown" link, next to "Edit this page" - every page's raw .md source is published alongside its built HTML (guides/themes.md sitting next to guides/themes/index.html), so it (or an LLM) can read the page as plain Markdown directly instead of parsing rendered HTML. Always on, no config needed. See Getting Started.
  • An opt-in footer (copyright, social links, a "Built with BX Sites" credit) when bxsites.json's footer is true. See Configuration.
  • A version switcher, appearing automatically once a project has a docs/versions/ folder with more than one version in it. See Configuration.
  • A themed 404.html, served automatically by most static hosts (including GitHub Pages) for any unmatched path.
  • A custom logo and favicon, when bxsites.json's theme.logo/ theme.favicon are set. See Configuration.
  • A collapsible sidebar nav, opt-in via theme.options.navCollapsible - every section with children (linked or not) gets a toggle button instead of always showing its children inline, and the section containing the current page always starts open. See Configuration.
  • Google Analytics, when bxsites.json's analytics is configured. See Configuration.
  • Social share cards (Open Graph + Twitter Card meta tags), sourced from each page's description frontmatter (or the site-wide description) and its own ogImage (or the site-wide one) - optionally auto-generated per page via bxsites.json's generateOgImages. See Configuration.
  • Page tags, an icon and a summary line, all opt-in via a page's own frontmatter - tags render as badges linking into a site-wide /tags/ index. See Getting Started.
  • An explicit nav override, in bxsites.json or its own docs/nav.json, replacing folder-inference for large sites. See Configuration.
  • Extra CSS/JS, injected via bxsites.json's extraCss/extraJs. See Configuration.
  • Admonition (note/warning/tip/...) callout boxes, on by default in any page's markdown, including collapsible variants - no config needed. See Markdown Extensions.
  • Footnotes and definition lists, opt-in via bxsites.json's markdown. See Markdown Extensions.
  • Content tabs, code line numbers/highlighted lines/titles, and diff markers/terminal-style frames for code blocks, no config needed. See Markdown Extensions.
  • Responsive images - resized + WebP variants and a <picture> rewrite for every eligible docs/assets/** image, on by default. See Responsive Images.
  • Mermaid diagrams, opt-in via bxsites.json's mermaid. See Markdown Extensions.
  • Math (KaTeX), opt-in via bxsites.json's math. See Markdown Extensions.

Set which one a project uses in bxsites.json:

{ "theme": { "name": "material" } }

Air-gapped/offline sites

A built site works with no internet access at all by default, for the bootstrap and material themes with the default local search provider: Bootstrap's own CSS/JS, highlight.js, Alpine.js, and lunr.js are all vendored with this module (resources/assets/vendor/) and copied straight into site/assets/vendor/ at build time - no CDN <script>/ <link> tag anywhere in the generated HTML for any of those. Turning on bxsites.json's mermaid key vendors Mermaid the same way - its mermaid.min.js bundle is copied into site/assets/vendor/mermaid/ and every built-in theme loads it from there, so diagrams still render with zero outbound requests.

A few things still reach out to the network, only when you turn them on yourself:

  • The tailwind theme's own utility engine is a client-side JIT compiler loaded from cdn.tailwindcss.com - it isn't a static stylesheet this module can vendor the same way, so this theme isn't air-gapped-capable yet.
  • Mermaid's own layout engine lazy-loads one extra chunk, elk-api.js, from jsDelivr - but only for diagram types that opt into the elk layout algorithm; the vendored mermaid.min.js renders every other diagram type entirely on its own.
  • bxsites.json's math option loads KaTeX (both its JS and its own font files) from a CDN when turned on.
  • searchProvider.provider: "algolia" and analytics.provider: "google" inherently talk to a hosted API/tracking endpoint - vendoring the JS file wouldn't remove that dependency.

If your deployment target genuinely has zero internet access, stick to bootstrap/material, the default local search provider, avoid elk-layout Mermaid diagrams if mermaid is on, and leave math/Algolia/ analytics off.

Icons

A page's own icon frontmatter (shown next to its title, and next to its entry in the sidebar nav) accepts either a plain emoji/short text - the original, still fully supported form - or a named icon from one of eight self-hosted libraries, all MIT/ISC-licensed and bundled with this module (~16,200 icons combined, no CDN, nothing added to a built page's own weight beyond the handful of icons it actually uses - see IconResolver.bx):

---
icon: rocket
---
---
icon: lucide:rocket
---
---
icon: phosphor-bold:rocket
---

Bare rocket defaults to Phosphor, regular weight. Phosphor ships all six of its own weights, each its own prefix: phosphor-thin:, phosphor-light:, phosphor: (regular, same as the bare name), phosphor-bold:, phosphor-fill: and phosphor-duotone:. Prefix with lucide: for Lucide, or tabler: for Tabler instead. Browse each site's own gallery for the exact name - it matches this module's own vendored filename exactly (lowercase, hyphenated, e.g. book-open, arrow-up-right; Phosphor's own site shows a weight switcher - each of its six options there is one of this module's six phosphor[-weight]: prefixes).

Font Awesome is deliberately not one of these - its Duotone style (and most of its icon set from v6 on) is Pro-only, not available under a license this module could bundle and redistribute for free.

A project's own SVG works too - drop it at docs/assets/icons/my-icon.svg and reference it as icon: custom:my-icon.

A nav.json entry can set its own icon too, overriding the target page's own frontmatter for that one entry:

{ "title": "Guides", "path": "guides/index.md", "icon": "lucide:book-open" }

The ThemeProvider contract

A theme is just a folder with:

  • layout.bxm (required) - the outer HTML shell + nav. Receives variables.page, variables.nav, variables.siteConfig, variables.themeDir and variables.basePath in scope, and includes the sibling page.bxm via #variables.themeDir#/page.bxm. variables.basePath is always a root-relative path ending in / (/ by default, /my-docs/ when bxsites.json's baseURL overrides it) - prefix every internal href/src with it, rather than hardcoding a leading /, so the theme still works when the site is served from a sub-path.
  • page.bxm (required) - the article body. Renders variables.page.contentHtml - the already-converted markdown.
  • search.bxm (optional) - the search box markup, included by layout.bxm only when bxsites.json's search is true. See Search.
  • assets/ (optional) - theme CSS/JS, copied to site/assets/theme/ at build time.

variables.page.editUrl/.lastUpdated (empty strings when unconfigured) and variables.siteConfig.repo/.social/.footer are always available too, backing the repo link/edit link/last-updated/footer features above - a custom theme decides for itself whether and how to render them, same as everything else. variables.versions ([ { label, url } ], "Latest" first) and variables.currentVersion (the label being rendered right now) back the version switcher - empty/"Latest" for a project that isn't versioned, so a theme only needs to render a switcher when variables.versions.len() gt 1. The three built-in themes get their repo/social icons from a small shared SVG lookup, <bx:include template="#variables.moduleAssetsDir#/icons.bxm"> (defines bxsitesIcon( name ), one of github, twitter/x, rss, youtube, linkedin, facebook, bluesky, threads, slack, patreon, email, edit, clock, falling back to a generic link glyph)

  • a custom theme can include it the same way, or supply its own icons entirely.

A theme folder missing either required file fails fast with a clear BxSites.InvalidTheme error at build time, rather than a confusing template error deep inside rendering.

Customizing colors without a theme override

For a color/font tweak, forking a whole theme is overkill - each built-in theme reads its palette from a handful of CSS custom properties on :root, re-declared under [data-theme="dark"] for dark mode. bxsites.json's extraCss loads after the theme's own stylesheet, so a same-specificity re-declaration in it wins without touching resources/themes/ at all:

{ "extraCss": [ "assets/brand.css" ] }
/* docs/assets/brand.css - copied to site/assets/brand.css at build time */
: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;
}

The bootstrap theme's own set (resources/themes/bootstrap/assets/style.css) is --bxsites-gradient-start/-end, --bxsites-accent, --bxsites-bg, --bxsites-text, --bxsites-sidebar-bg, --bxsites-sidebar-text, --bxsites-border, --bxsites-link, --bxsites-link-hover, --bxsites-code-bg, --bxsites-step-marker-bg, --bxsites-step-marker-text, --bxsites-step-line, --bxsites-step-success-bg/-text, --bxsites-step-warning-bg/-text and --bxsites-step-danger-bg/-text - material and tailwind follow the same --bxsites-* naming with their own small variations. Anything beyond color/font (layout, adding/removing chrome) needs a real override or a custom theme - see below.

The rest back the ::: stepper/::: step directive block - --bxsites-step-marker-bg/-text are the default numbered circle's background/text color (bootstrap/material default it to the theme's own --bxsites-accent; tailwind uses a dedicated teal/mint pair since it doesn't have a single shared accent token), --bxsites-step-line is the connecting line between steps, and the -success/-warning/-danger pairs back a step's own optional color="..." attribute - unlike the default marker, these three are the same fixed bg/text pair in both light and dark mode (a self-contained badge, not tied to the theme's own brand accent), so there's no [data-theme="dark"] override to redeclare:

:root {
	--bxsites-step-marker-bg: #7C3AED;
	--bxsites-step-marker-text: #fff;
	--bxsites-step-success-bg: #059669;
	--bxsites-step-success-text: #fff;
}

[data-theme="dark"] {
	--bxsites-step-marker-bg: #C4B5FD;
	--bxsites-step-marker-text: #1b1f21;
}

Homepage hero banner

Every built-in theme ships CSS for a full-width homepage banner with a headline image and call-to-action buttons - this very site's own docs/index.md uses it. There's no directive block or config for it, just plain HTML any page can drop in (a homepage is just a normal page, order: 1 or otherwise first in nav):

<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>

bxsites-hero__btn--primary/--secondary are the same two accent styles every theme already uses elsewhere - swap, drop, or add buttons freely, and resize/replace bxsites-hero__banner's own image via a docs/assets/-relative src the same way any other image resolves.

Overriding a theme

Drop your own layout.bxm + page.bxm (and optionally search.bxm / assets/) into a theme/ folder at your project root. BX Sites prefers a project-level theme/ override over any built-in theme, as long as it satisfies the contract above - the built-in themes under this module's own resources/themes/ are a good starting point to copy and adapt.

A worked example - start from bootstrap and swap its brand palette and heading font for your own, keeping everything else (nav, search, dark mode, code highlighting, ...) exactly as it already works:

my-project/
├── bxsites.yaml
├── docs/
└── theme/                    ← project-level override, checked before any built-in theme
    ├── layout.bxm             ← copied from resources/themes/bootstrap/layout.bxm
    ├── page.bxm                ← copied from resources/themes/bootstrap/page.bxm, unchanged
    ├── search.bxm               ← copied unchanged
    └── assets/
        └── style.css              ← copied from bootstrap's assets/style.css, then edited
  1. Copy the three .bxm files and assets/style.css out of this module's resources/themes/bootstrap/ into your project's theme/.
  2. Edit only what you need to change. To swap the brand palette and font, that's just the top of theme/assets/style.css:
   :root {
   	--bxsites-gradient-start: #7C3AED;  /* was #00FF78 */
   	--bxsites-gradient-end: #DB2777;    /* was #00DBFF */
   	--bxsites-accent: #FBBF24;          /* was #FFF500 */
   }

   body {
   	font-family: "Inter", system-ui, sans-serif;  /* was "Poppins" */
   }
  1. Run bxSites build (or serve while iterating) - BX Sites picks up theme/ automatically, no bxsites.json change needed (a project-level theme/ folder always takes precedence over the built-in theme named in theme.name). Everything you didn't touch - nav rendering, search, the dark-mode toggle, code annotations - keeps working exactly as it did in the original bootstrap theme, since it's still the exact same layout.bxm/page.bxm markup underneath.

A project theme/ folder is all-or-nothing, though - once BX Sites finds one, it's used instead of the built-in theme entirely, so it still needs its own layout.bxm + page.bxm even if all you changed is assets/style.css (a folder missing either fails fast with BxSites.InvalidTheme rather than silently falling back). For a CSS-only/no-.bxm tweak, use extraCss above instead - it layers on top of whichever theme bxsites.json names, no theme/ folder involved at all. theme/ is for when you also need to change the markup itself, covered next.

Writing a theme from scratch

A theme only needs the two required files, so here's a genuinely minimal one - no Bootstrap/Tailwind, no dark mode, no search UI - to show exactly what's required versus what the built-in themes add on top. Save both as theme/layout.bxm and theme/page.bxm in your project - a project-level theme/ folder is picked up automatically (as above), no bxsites.json change needed:

<!-- 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>

That's a complete, working theme - variables.page.contentHtml is the already-converted markdown (syntax highlighting, admonitions, tabs, math and all), so there's nothing left to parse, only to lay out. From here, add whatever the built-in themes have that you actually want: search.bxm (only included when bxsites.json's search is true - see Search), a dark-mode toggle (copy the x-data/x-init Alpine.js pair off resources/themes/bootstrap/layout.bxm's <body> tag and the matching [data-theme="dark"] CSS block), breadcrumbs/tags/prev-next links (page.bxm in any built-in theme shows the pattern - each is just an if around a small render function, all driven by fields already present on variables.page), or an assets/ folder for your own CSS/JS, copied to site/assets/theme/ automatically at build time.

Edit this page Download Markdown Last updated Aug 23, 2026, 2:17:28 AM