# Customize Themes URL: /bengal/docs/0.4.3/theming/themes/customize/ Section: themes Tags: themes, customization, templates, css -------------------------------------------------------------------------------- Customize Themes Customize Bengal themes without breaking theme updates. Use theme inheritance, template overrides, and CSS customization techniques. Understand Theme Resolution Bengal resolves themes in this order: Project themes - themes/your-theme/ (highest priority) Installed themes - Installed via pip/uv Bundled themes - Built into Bengal (e.g., default) Check Active Theme # List available themes bengal theme list # Get theme info bengal theme info --slug default # Debug theme resolution bengal theme debug Create a Project Theme Option 1: Start from Scratch bengal theme new --slug my-custom-theme This creates: themes/my-custom-theme/ ├── theme.toml ├── README.md ├── templates/ │ ├── base.html │ ├── home.html │ ├── page.html │ └── partials/ └── assets/ └── css/ └── style.css Configure Your Theme Edit bengal.toml: [build] theme = "my-custom-theme" Override Templates Selectively Template Inheritance You don't need to copy all templates. Override only what you need: themes/my-custom-theme/templates/base.html: {# Extend default theme's base template #} {% extends "default/base.html" %} {# Override only the header block #} {% block header %} <header class="custom-header"> <h1>{{ site.title }}</h1> <nav> {% for item in menu.main %} <a href="{{ item.href }}">{{ item.name }}</a> {% end %} </nav> </header> {% end %} {# Everything else inherits from default theme #} Partial Overrides Override specific partials: themes/my-custom-theme/templates/partials/footer.html: <footer class="custom-footer"> <p>&copy; {{ site.author }} {{ "now" | date("%Y") }}</p> <p>Custom footer content</p> </footer> Bengal will use your partial instead of the theme's default. Page Hero Templates The page hero templates display the header section of content pages including title, breadcrumbs, description, and stats. Template Structure The page-hero/ directory contains separated hero templates: templates/partials/ ├── page-hero.html # Dispatcher (routes by hero_style) └── page-hero/ # Separated hero templates ├── _macros.html # Shared macros and helpers ├── index.html # Index page hero template ├── element.html # DocElement pages (modules, classes, commands) └── section.html # Section-index pages (packages, CLI groups) The dispatcher (page-hero.html) routes to the appropriate template based on page type and hero_style configuration. Override Page Hero for API Pages To customize the hero for API documentation pages: For element pages (modules, classes, functions, commands): Override partials/page-hero/element.html to customize the hero for API documentation elements: {# themes/my-theme/templates/partials/page-hero/element.html #} {% from 'partials/page-hero/_macros.html' import hero_element %} {# Use the macro with custom parameters #} {{ hero_element(element, params, theme) }} {# Or create a completely custom hero #} <div class="page-hero"> <div class="page-hero__badges"> {% include 'autodoc/python/partials/badges.html' %} </div> <h1 class="page-hero__title page-hero__title--code"> <code>{{ element.qualified_name }}</code> </h1> {% if element.description %} <div class="page-hero__description"> {{ element.description | markdownify | safe }} </div> {% end %} </div> For section-index pages: Override partials/page-hero/section.html to customize section index heroes: {# themes/my-theme/templates/partials/page-hero/section.html #} {% from 'partials/page-hero/_macros.html' import hero_section %} {# Use the macro #} {{ hero_section(section, params, theme) }} {# Or create a custom section hero #} <div class="page-hero"> <h1 class="page-hero__title">{{ section.title }}</h1> {% let desc = section.metadata.description %} {% if desc %} <div class="page-hero__description"> {{ desc | markdownify | safe }} </div> {% end %} </div> Using hero_context For CLI reference sections, pass explicit context to avoid URL sniffing: {# In autodoc/cli/section-index.html #} {% let hero_context = {'is_cli': true} %} {% include 'partials/page-hero/section.html' %} The hero_context.is_cli flag controls whether stats display: true: "X Groups, Y Commands" false: "X Packages, Y Modules" Template Data Access Patterns Element templates receive a DocElement dataclass—use attribute access: element.qualified_name element.description element.children element.source_file Section templates receive a Section object—use attribute access: section.title (section title) section.metadata.description (safe access, returns empty string if missing) section.sorted_pages (sorted child pages) section.sorted_subsections (sorted child sections) Customize CSS Method 1: Override Theme CSS Create your own CSS file that overrides theme styles: themes/my-custom-theme/static/css/custom.css: /* Override theme colors */ :root { --color-primary: #3498db; --color-text: #2c3e50; } /* Custom styles */ .custom-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 2rem; } Include in your base template: themes/my-custom-theme/templates/base.html: {% extends "default/base.html" %} {% block extra_head %} <link rel="stylesheet" href="{{ asset_url('css/custom.css') }}"> {% end %} Method 2: Use CSS Variables Many themes support CSS variables. Override them: themes/my-custom-theme/static/css/overrides.css: :root { /* Override default theme variables */ --theme-primary: #3498db; --theme-secondary: #2ecc71; --theme-font-sans: 'Inter', sans-serif; } Theme Configuration Options Themes can expose configuration options in two ways: Theme-Level Configuration (theme.yaml) Define default configuration in your theme's theme.yaml file: themes/my-custom-theme/theme.yaml: name: my-custom-theme version: 1.0.0 # Custom configuration options show_author: true show_date: true sidebar_position: left color_scheme: light Site-Level Overrides (bengal.toml) Override theme defaults in your site's bengal.toml: [build] theme = "my-custom-theme" [theme] show_author = true sidebar_position = "right" color_scheme = "dark" Accessing Configuration in Templates Access theme configuration using theme.get() or direct property access: {# Using theme.get() method (recommended) #} {% if theme.get('show_author', false) %} <p>By {{ page.author or site.author }}</p> {% end %} {# Direct config key access #} {% if theme.config.show_author %} <p>By {{ page.author or site.author }}</p> {% end %} Note: All keys in the [theme] section are accessible via theme.get('key') (with default) or theme.config.key (direct access). Best Practices Don't Modify Installed Themes ❌ Bad: # Don't edit installed theme directly vim $(python -m site --user-site)/bengal/themes/default/templates/base.html ✅ Good: # Create project theme that extends default bengal theme new --slug my-theme # Override only what you need Use Theme Inheritance ✅ Good: {% extends "default/base.html" %} {% block header %} {# Only override header #} {% end %} ❌ Bad: {# Copying entire base.html #} <!DOCTYPE html> <html> {# ... hundreds of lines ... #} </html> Troubleshooting Warning Theme Not Found Issue: Theme 'my-theme' not found Solutions: Verify theme directory exists: themes/my-theme/ Check theme.toml has correct name field Run bengal theme list to see available themes Warning Template Inheritance Not Working Issue: Changes to parent theme not reflected Solutions: Verify extends path is correct: "default/base.html" Check theme chain: bengal theme debug Clear cache: bengal clean --cache Warning CSS Not Loading Issue: Custom CSS not applying Solutions: Use asset_url() filter: {{ asset_url('css/style.css') }} Check file location: themes/your-theme/static/css/ Hard refresh: Cmd+Shift+R Navigation with NavTree Bengal provides a pre-computed navigation tree for efficient template rendering. Use get_nav_tree(page) to access the navigation structure. Basic Usage <nav class="sidebar"> {% for item in get_nav_tree(page) %} <a href="{{ item.href }}" {% if item.is_current %}class="active"{% end %} {% if item.is_in_trail %}class="in-trail"{% end %}> {{ item.title }} </a> {% if item.has_children %} <ul> {% for child in item.children %} <li> <a href="{{ child.href }}">{{ child.title }}</a> </li> {% end %} </ul> {% end %} {% end %} </nav> NavNode Properties Each navigation node provides: Property Type Description item.title str Display title item.href str Page URL with baseurl applied item._path str Site-relative URL without baseurl item.icon str | None Icon identifier item.weight int Sort weight item.children list[NavNode] Child navigation items item.is_current bool True if this is the current page item.is_in_trail bool True if in active trail to current page item.is_expanded bool True if node should be expanded item.has_children bool True if node has children item.depth int Nesting level (0 = top level) Scoped Navigation For section-specific navigation (e.g., docs-only sidebar): {% let root = page._section.root if page._section else none %} {% for item in get_nav_tree(page, root_section=root) %} <a href="{{ item.href }}">{{ item.title }}</a> {% end %} Benefits Performance: O(1) lookup via cached structure (<1ms render overhead) Simplicity: Single function call replaces version-filtering boilerplate Consistency: Pre-computed structure ensures consistent navigation across pages Version-aware: Automatic version filtering and shared content injection Info Seealso Templating — Template basics Assets — Asset pipeline Icon Reference — SVG icons and customization Variables Reference — Available template variables -------------------------------------------------------------------------------- Metadata: - Author: lbliii - Word Count: 1207 - Reading Time: 6 minutes