CSS and Assets
CSS entry file
Braised processes a single CSS entry file through the Tailwind CLI and writes the result to dist/assets/braised.css. The entry file is resolved in priority order:
css.inputinbraised.yaml— explicit path, relative to project root (deprecated; use the theme layer instead)theme/main.cssat the project root — site-level CSS override{themeDir}/theme/main.css— theme-supplied CSS
If none of these exist, the CSS pipeline is skipped and no braised.css is written. Your layout must supply its own stylesheet in that case.
Built-in CSS imports
The braised binary embeds two CSS files that any entry file can pull in:
@import "braised:theme/base"; /* layout, typography, color tokens */
@import "braised:theme/syntax"; /* syntax highlighting for code blocks */
These are not filesystem files — braised inlines them before handing the entry file to Tailwind. You can import them in a theme's theme/main.css or in a site-level override:
@import "braised:theme/base";
@import "braised:theme/syntax";
/* your additions follow */
Customization tiers
Tier 1 — Override CSS variables
The built-in theme is built on CSS custom properties. Override any of them after the import and the change propagates everywhere:
@import "braised:theme/base";
[data-theme="dark"] {
--bg-base: #1a1a2e;
--accent: #e94560;
--text-primary: #eaeaea;
}
[data-theme="light"] {
--bg-base: #ffffff;
--accent: #c0392b;
--text-primary: #1a1a2e;
}
See CSS variables reference for the full list.
Tier 2 — Add rules
@import "braised:theme/base";
.my-badge {
display: inline-block;
background: var(--accent-subtle);
color: var(--accent-text);
border-radius: 4px;
padding: 0.1em 0.5em;
font-size: 0.75em;
}
Tier 3 — Full replacement
Skip the braised imports entirely and provide your own styles. Braised compiles whatever is in the entry file — it does not require the built-in tokens.
/* no @import "braised:theme/base" */
:root { --brand: #0055cc; }
body { font-family: system-ui; background: #fff; color: #111; }
Static assets
Braised has two copy paths for files that should be served verbatim.
Site static directory
{siteDir}/static/ is copied to the output root after every build. The directory structure is preserved exactly, so the output URL mirrors the source path:
static/
favicon.ico → dist/favicon.ico → /favicon.ico
robots.txt → dist/robots.txt → /robots.txt
assets/
logo.svg → dist/assets/logo.svg → /assets/logo.svg
scripts/
search-worker.js → dist/scripts/search-worker.js → /scripts/search-worker.js
Use static/ for anything that belongs to the site but not to any specific theme — favicons, robots.txt, custom JS workers, og-images, and so on.
Theme asset directories
A theme has two copy directories with different destinations:
| Source | Destination | URL |
|---|---|---|
{themeDir}/assets/ |
dist/assets/ |
/assets/… |
{themeDir}/static/ |
dist/ (output root) |
/… |
assets/ is specifically for theme-level files that live alongside braised's generated bundle. A file named blocks.js here replaces braised's generated bundle entirely:
themes/default/
assets/
blocks.js ← replaces braised's generated bundle if present
logo.svg
font.woff2
static/
robots.txt ← copied to dist/ root
Copy order and precedence
Files are written in this order, with later writes winning on collisions:
- Braised JS bundle →
dist/assets/blocks.js - Theme
assets/→dist/assets/← themeblocks.jsreplaces the generated bundle here - Theme
static/→dist/ - Site
static/→dist/
Site-root static/ always wins over theme static/ on any filename collision.
Theme toggle
The built-in layout provides a two-state dark/light toggle. When you replace the layout, call:
document.documentElement.dataset.theme = 'light'; // or 'dark'
from your own JavaScript to drive the [data-theme] attribute. The initial theme is set by a small inline script — include this pattern in your layout's <head> to preserve the no-flash behavior:
<script>
(function() {
var stored = localStorage.getItem('braised-theme');
var preferred = window.matchMedia('(prefers-color-scheme: light)').matches
? 'light' : 'dark';
document.documentElement.dataset.theme = stored || preferred;
})();
</script>
Config-driven themes
When themes: is set in braised.yaml, .Site.Themes.Values and .Site.Themes.Default are available in layout templates. Use them to render a theme switcher dynamically and apply the configured default on cold load:
<script>
(function() {
var stored = localStorage.getItem('braised-theme');
{{if .Site.Themes.Default -}}
var preferred = '{{.Site.Themes.Default}}';
{{- else -}}
var preferred = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
{{- end}}
document.documentElement.dataset.theme = stored || preferred;
})();
</script>
Render the switcher options from the config list:
{{range .Site.Themes.Values}}
<button class="theme-opt" data-theme="{{.}}">{{.}}</button>
{{end}}
When themes: is absent, both fields are empty and the fallback branch runs — existing layouts are unaffected.
Sidebar breakpoints
The braised base CSS scopes all sidebar rules to .page-layout .sidebar. This means custom layouts that happen to use a .sidebar class are unaffected by braised's off-canvas behaviour.
The built-in breakpoints are tuned for the 3-column docs layout:
| Breakpoint | Behaviour |
|---|---|
| ≤ 1100px | Sidebar goes off-canvas (hamburger appears). TOC stays visible. |
| ≤ 768px | TOC collapses inline above content. |
For a 2-column layout (sidebar + main, no TOC), 1100px is too aggressive — a 220px sidebar alongside content is comfortable down to 768px. Use your own selector and breakpoint:
/* 2-column layout — sidebar stays sticky down to tablet width */
.page-shell .sidebar {
position: sticky;
top: var(--header-height);
height: calc(100vh - var(--header-height));
}
@media (max-width: 768px) {
.nav-toggle { display: flex; }
.page-shell {
grid-template-columns: 1fr;
}
.page-shell .sidebar {
position: fixed;
top: var(--header-height);
left: 0;
width: var(--sidebar-width);
height: calc(100vh - var(--header-height));
transform: translateX(-100%);
transition: transform 0.2s ease;
z-index: 100;
}
.page-shell .sidebar.is-open { transform: translateX(0); }
}
The hamburger toggle JS (#nav-toggle, .sidebar, #nav-overlay) is provided by blocks.js automatically — include those three elements in your layout and the open/close behaviour works without any additional script.