Create a Theme

Build a custom Bengal theme from scaffold to deployment

5 min read 935 words
Edit this page

Was this page helpful?

Bengal's theme system supports inheritance, so you can build on the default theme and override only what you need. This guide walks through creating, customizing, and testing a theme.

Scaffold a Theme

BASH
# Create a site-local theme (lives in your project)
bengal theme new --slug my-theme

# Create an installable package (publishable to PyPI)
bengal theme new --slug my-theme --mode package

# Extend a specific parent theme
bengal theme new --slug my-theme --extends default

Site mode creates this structure:

themes/my-theme/
├── theme.toml              # Name and inheritance
├── README.md
├── templates/
│   ├── base.html
│   ├── home.html
│   ├── page.html
│   └── partials/
├── assets/
│   └── css/
│       └── style.css       # Your styles (inherits default theme's directive CSS when you extend it)

Configure Your Site

Point your site config at the new theme:

TOML
# bengal.toml
[build]
theme = "my-theme"

Bengal resolves templates in priority order: your theme first, then its parent (via extends), then the default theme. You only need to override the templates you want to change.

Override Templates

Copy a template from the active theme to customize it:

BASH
# See what's available
bengal theme discover

# Copy a specific template
bengal theme swizzle --template-path partials/header.html

Swizzled templates are tracked with checksums. When the parent theme updates, bengal theme swizzle-updaterefreshes unchanged templates while preserving your modifications.

Template Inheritance

Your templates can extend parent templates:

HTML
{# templates/page.html #}
{% extends "page.html" %}

{% block content %}
<article class="my-theme-content">
  {{ content | safe }}
</article>
{% endblock %}

Bengal resolves the extends to the parent theme's page.htmlautomatically.

Add Styles

Yourassets/css/style.cssis loaded after the parent theme's CSS, so your styles override naturally via the cascade.

Directive base CSS is not bundled by the engine — it lives in the default theme atbengal/themes/default/assets/css/components/ (admonitions.css, tabs.css, dropdowns.css, steps.css, cards.css, checklist.css, code.css). So it is automatic only when your theme extends default (or is a site-local theme layered over it): in that case you inherit the functional show/hide, accessibility, and prose-contamination fixes for free and only add aesthetic styles. A theme that does not extend defaultmust supply its own directive CSS. See What Bengal Provides vs What Your Theme Provides for the full capability-vs-presentation boundary.

CSS
/* assets/css/style.css */

/* Override the primary color */
:root {
  --color-primary: #2563eb;
  --color-primary-light: #3b82f6;
}

/* Custom typography */
body {
  font-family: "Inter", system-ui, sans-serif;
  line-height: 1.7;
}

/* Style code blocks */
pre code {
  border-radius: 0.5rem;
  font-size: 0.875rem;
}

Test Your Theme

BASH
# Start dev server with live reload
bengal serve

# Start theme-focused preview with active theme preflight
bengal theme preview

# Validate theme structure
bengal theme validate --theme-path themes/my-theme

# Debug template resolution
bengal theme debug
bengal theme debug --template page.html

bengal theme previewresolves the active theme, shows whether it came from the site, Bengal's bundled themes, or an installed package, validates its metadata and required templates, then starts the live-reload server. It also prints the content, template, and theme paths the preview workflow watches.

Theme Configuration

Yourtheme.tomldeclares theme metadata:

TOML
name = "my-theme"
version = "1.0.0"
description = "Documentation theme for the project"
author = "Docs Team"
license = "MIT"
extends = "default"
libraries = ["chirp_ui"]

Supported metadata fields:

Field Type Purpose
name string Display name forbengal theme list and bengal theme info
version string Theme version shown by theme tooling
description string Human-readable package or project description
author string Theme author or owning team
license string Theme license identifier
extends string Parent theme slug
parent string Legacy parent theme slug, used whenextendsis absent
libraries list of strings Python theme libraries to load, such aschirp_ui

bengal theme validate --theme-path themes/my-themechecks this metadata before checking templates and assets, so malformed fields fail with direct messages.

For richer configuration (feature flags, appearance, icons), usetheme.yaml:

YAML
name: my-theme
version: "1.0.0"
parent: default

features:
  navigation:
    breadcrumbs: true
    toc: true
  content:
    code.copy: true
    lightbox: true
  search:
    suggest: true

appearance:
  default_mode: system
  default_palette: snow-lynx

Publish as a Package

If you scaffolded with--mode package, your theme is a pip-installable Python package:

BASH
cd bengal-theme-my-theme
pip install -e .    # Install locally for testing

The pyproject.tomlregisters your theme via entry points:

TOML
[project.entry-points.'bengal.themes']
my-theme = "bengal_themes.my_theme"

Once installed, any Bengal site can use it:

TOML
# bengal.toml
[build]
theme = "my-theme"

Publish to PyPI with uv publish or python -m build && twine upload dist/*.

Useful Commands

Command Purpose
bengal theme new --slug <slug> Scaffold a new theme
bengal theme validate --theme-path <path> Check required files and structure
bengal theme list Show available themes
bengal theme info --slug <slug> Theme details and paths
bengal theme discover List swizzlable templates
bengal theme swizzle --template-path <template> Copy template for customization
bengal theme swizzle-list List swizzled templates
bengal theme swizzle-update Update unchanged swizzled templates
bengal theme preview Start a theme-focused live preview server
bengal theme debug Debug theme resolution chain

Seealso