Go beyond basic Markdown. This track covers directives (callouts, tabs, cards), content reuse (snippets, includes), and variable substitution.

Tip

Duration: ~60 min | Prerequisite: Zero to Deployed or equivalent

1

Content Authoring

Markdown, MyST directives, and rich content

Writing Content

Bengal uses CommonMark Markdown with MyST-style directive extensions for rich documentation. Write in standard Markdown and extend with 60+ built-in directives.

What Do You Need?

Quick Reference

**bold** and *italic*
~~strikethrough~~
`inline code`
[External](https://example.com)
[Internal](/docs/get-started/)
[[Cross-reference]] docs/page
[[#heading]] Anchor link
[[ext:python:pathlib.Path]]
```python
def hello():
    print("Hello!")
```

With line highlighting:

```python {hl_lines="2"}
def hello():
    print("Highlighted!")
```
:::{note}
Informational callout.
:::

:::{warning}
Important warning!
:::

:::{tip}
Helpful suggestion.
:::

How Content Flows

flowchart LR A[Markdown] --> B[MyST Parser] B --> C{Directive?} C -->|Yes| D[Render Component] C -->|No| E[Render HTML] D --> F[Final Page] E --> F

Variable Substitution

Use{{ variable }}syntax to insert frontmatter values directly into your content:

---
product_name: Bengal
version: 1.0.0
beta: true
---

Welcome to **{{ product_name }}** version {{ version }}.

{% if beta %}
:::{warning}
This is a beta release.
:::
{% end %}

Note

Bengal uses Kida templating which supports both{% end %} (unified syntax) and {% endif %}(Jinja-compatible). Use whichever you prefer.

Available Variables

Variable Source Example
Content Authoring Current page Content Authoring
Markdown, MyST directives, and rich content Current page Markdown, MyST directives, and rich content
{{ product_name }} Frontmatter Direct access to any frontmatter key
`` Frontmatter Hugo-style access viaparams
Bengal Site config Bengal
/bengal Site config /bengal

Cascaded Variables

Variables cascade from parent sections. Set them once in a section's_index.md:

# docs/api/_index.md
---
title: API Reference
cascade:
  api_version: v2
  deprecated: false
---

Then use in any child page:

# docs/api/users.md
This endpoint uses API {{ api_version }}.

Tip

Common use cases: Product names, version numbers, feature flags, environment-specific values, and cascaded metadata like API versions or status badges.

Available Directives

Bengal provides 60+ directives organized by category:

Category Directives
Admonitions note, tip, warning, danger, error, info, example, success, caution, seealso
Layout tabs, tab-set, cards, card, child-cards, grid, container, steps, step, dropdown
Tables list-table, data-table
Code code-tabs, literalinclude
Media youtube, vimeo, tiktok, video, audio, figure, gallery
Embeds gist, codepen, codesandbox, stackblitz, asciinema, spotify, soundcloud
Navigation breadcrumbs, siblings, prev-next, related
Versioning since, deprecated, changed
Utilities badge, button, icon, rubric, target, include, glossary, checklist, marimo

Seealso

2

Linking Guide

Complete guide to creating links between pages, headings, and external resources

Linking Guide

Bengal provides multiple ways to create links between pages, headings, and external resources. Choose the method that best fits your use case.

Tip

Try it out: This guide includes a test target anchor that demonstrates arbitrary reference targets. Scroll down to see it in action!

Quick Reference

Method Use Case Syntax
Markdown Links Standard links, external URLs [text](url)
Cross-References Internal page links with auto-title [[path]]
External References Link to external docs (Python, NumPy, etc.) [[ext:project:target]]
Template Functions Dynamic links in templates {{ ref('path') }}
Anchor Links Link to headings [[#heading]] or #heading
Target Directives Arbitrary anchors mid-page :::{target} id
Target References Link to target directives [[!target-id]]

Use standard Markdown link syntax for external URLs and simple internal links.

[Visit GitHub](https://github.com)
[Email us](mailto:hello@example.com)

Absolute paths (from site root):

[Get Started](/docs/get-started/)
[API Reference](/docs/reference/api/)

Relative paths (from current page):

<!-- From docs/get-started/installation.md -->
[Quickstart](./quickstart.md)
[Configuration](../reference/config.md)
[Home](../../_index.md)

Path Resolution Rules:

  • .md extension is optional — Bengal resolves both page.md and page
  • Relative paths resolve from the current page's directory
  • ./ refers to current directory, ../goes up one level
  • Paths are normalized automatically (trailing slashes, etc.)
[Link text](https://example.com "Optional title")

Cross-references provide intelligent linking with automatic title resolution and O(1) lookup performance. Use them for internal page links.

Basic Cross-Reference

[[docs/getting-started]]

This automatically:

  • Resolves to the page atdocs/getting-started.md
  • Uses the page's title as link text
  • Handles path normalization automatically
[[docs/getting-started|Get Started Guide]]

The |separator lets you specify custom link text while still using intelligent path resolution.

If a page has a customidin frontmatter:

---
id: install-guide
title: Installation Guide
---

Link to it by ID:

[[id:install-guide]]
[[id:install-guide|Installation]]

Benefits of ID-based links:

  • Stable even if page path changes
  • Shorter syntax
  • Works across site restructures

Link to any heading in your site:

[[#installation]]
[[#configuration]]

This finds the heading text (case-insensitive) and links to it. If multiple pages have the same heading, it uses the first match.

Link to heading in specific page:

[[docs/getting-started#installation]]

Path Resolution

Cross-references support multiple path formats:

[[docs/getting-started]]        # Path without extension
[[docs/getting-started.md]]     # Path with extension (also works)
[[getting-started]]             # Slug (if unique)
[[id:install-guide]]            # Custom ID

Resolution order:

  1. Custom ID (id:xxx)
  2. Path lookup (docs/page)
  3. Slug lookup (page-name)

Broken References

If a cross-reference can't be resolved, Bengal shows a broken reference indicator:

[[nonexistent-page]]

Renders as: [nonexistent-page]with a visual indicator for debugging.

Link to headings within the same page or across pages.

Same-Page Anchors

[Installation](#installation)
[Configuration](#configuration)

Headings automatically get anchor IDs based on their text. For example:

## Installation

This heading gets ID: `installation`

Custom Anchor IDs

Use{#custom-id}syntax for custom anchor IDs on headings:

## Installation {#install-guide}

Link to it: [[#install-guide]]

Arbitrary Reference Targets

Create anchor targets anywhere in content (not just on headings) using the{target} directive. This is similar to RST's .. _label:syntax.

Syntax:

:::{target} my-anchor-id
:::

Example: Target Directive Usage

:::{target} important-caveat
:::

:::{warning}
This caveat is critical for production use.
:::

See [[!important-caveat|the caveat]] for details.

Reference Syntax:

Use[[!target-id]]to explicitly reference target directives:

[[!test-target]]              # Link with auto-generated text
[[!test-target|Custom Text]]   # Link with custom text

Why ! instead of #?

The!prefix distinguishes target directive references from heading anchor references:

  • [[#heading]] - References heading anchors (auto-generated or custom {#id})
  • [[!target-id]] - References target directives (explicit :::{target})

This eliminates collisions and makes your intent explicit.

Use cases:

  • Anchor before a note/warning that users should link to
  • Stable anchor that survives heading text changes
  • Anchor in middle of content (not tied to heading)
  • Migration from Sphinx (.. _label:) or RST reference targets

Anchor ID requirements:

  • Must start with a letter (a-z, A-Z)
  • May contain letters, numbers, hyphens, underscores
  • Case-sensitive in output, case-insensitive for resolution

Note: The target renders as an invisible anchor element. Any content inside the directive is ignored (targets are point anchors, not containers).

Try it: This page has a test target below. Jump to it: Test Target

This is a test target anchor. You can link to it using[[!test-target]]from anywhere in your site.

Cross-Page Anchors

[Installation](/docs/getting-started/#installation)
[[docs/getting-started#installation]]

Both syntaxes work. Cross-references are preferred for internal links.

Template Functions

Use template functions in Kida templates for dynamic link generation.

ref(path, text=None)

Generate a cross-reference link:

{{ ref('docs/getting-started') }}
{{ ref('docs/getting-started', 'Get Started') }}
{{ ref('id:install-guide') }}

Returns: Safe HTML link (<a href="...">...</a>)

Use cases:

  • Dynamic navigation menus
  • Related pages sections
  • Breadcrumbs

doc(path)

Get a page object for custom link generation:

{% let page = doc('docs/getting-started') %}
{% if page %}
  <a href="{{ page.href }}">{{ page.title }}</a>
  <p>{{ page.description }}</p>
{% end %}

Returns: Page object or None

Use cases:

  • Custom link formatting
  • Accessing page metadata
  • Conditional rendering

anchor(heading, page_path=None)

Link to a heading:

{{ anchor('Installation') }}
{{ anchor('Configuration', 'docs/getting-started') }}

Parameters:

  • heading: Heading text to find (case-insensitive)
  • page_path: Optional page path to restrict search

Returns: Safe HTML link with anchor fragment

Use cases:

  • Table of contents
  • "Jump to" links
  • Cross-page heading references

relref(path)

Get relative URL without generating a link:

<a href="{{ relref('docs/api') }}" class="btn">API Docs</a>

{% let api_url = relref('docs/api') %}
{% if api_url %}
  <link rel="preload" href="{{ api_url }}" as="document">
{% end %}

Returns: URL string or empty string if not found

Use cases:

  • Custom link HTML
  • Meta tags
  • Preload/prefetch links

Path Resolution

Understanding how Bengal resolves paths helps you write reliable links.

Path Formats

Bengal accepts multiple path formats:

[[docs/getting-started]]        # Recommended: path without extension
[[docs/getting-started.md]]     # Also works: path with extension
[[getting-started]]             # Slug (if unique across site)
[[id:install-guide]]            # Custom ID

Resolution Order

When resolving a cross-reference:

  1. Custom ID (id:xxx) — Checked first, fastest lookup
  2. Path lookup (docs/page) — Most explicit, recommended
  3. Slug lookup (page-name) — Fallback, may be ambiguous

Relative Path Resolution

Relative paths resolve from the current page's directory:

<!-- File: docs/getting-started/installation.md -->

[[./quickstart]]              # docs/getting-started/quickstart.md
[[../reference/config]]       # docs/reference/config.md
[[../../_index]]              # _index.md (site root)

Rules:

  • .mdextension is optional
  • ./refers to current directory
  • ../goes up one level
  • Paths are normalized (trailing slashes, etc.)

Best Practices

When to Use Each Method

Use Markdown Links ([text](url)) when:

  • Linking to external URLs
  • You need full control over link text
  • Simple relative paths within a section

Use Cross-References ([[path]]) when:

  • Linking to internal pages
  • You want automatic title resolution
  • You want stable links that survive path changes (with IDs)
  • You're writing documentation

Use Template Functions (ref(), doc(), etc.) when:

  • Generating links dynamically in templates
  • Building navigation menus
  • Creating related pages sections
  • Conditional link rendering

Use Anchor Links (#heading) when:

  • Linking to specific sections
  • Creating table of contents
  • Cross-referencing specific content

Use Target Directives (:::{target}) when:

  • Creating anchors not tied to headings
  • Need stable anchors that survive content restructuring
  • Migrating from RST/Sphinx (.. _label:)
  • Anchoring before notes/warnings for direct linking

Reference with[[!target-id]]:

  • Explicit syntax avoids collisions with heading anchors
  • Makes intent clear (target directive vs heading)
  • Required for target directive references

Most Stable (survives path changes):

  • Custom IDs:[[id:my-page]]
  • Template functions with IDs:{{ ref('id:my-page') }}

Moderately Stable (survives minor changes):

  • Path-based cross-references:[[docs/page]]
  • Absolute markdown links:[text](/docs/page/)

Least Stable (breaks on path changes):

  • Relative markdown links:[text](../page.md)
  • Slug-based references (if slugs change)

Performance

All linking methods use O(1) lookups from pre-built indexes:

  • Cross-references: Built during discovery phase
  • Template functions: Use same index
  • Markdown links: Resolved during rendering

No performance difference between methods — choose based on use case.

Examples

Navigation Menu

<nav>
  {% for item in site.menus.main %}
    <a href="{{ item.url }}">{{ item.name }}</a>
  {% end %}
</nav>
## Related Pages

- [[docs/tutorials/getting-started|Getting Started]]
- [[docs/reference/api|API Reference]]
- [[docs/tutorials/examples|Examples]]
{% let related = site.pages | where('tags', page.tags, 'in') | limit(5) %}
{% if related %}
  <h2>Related Pages</h2>
  <ul>
    {% for page in related %}
      <li>{{ ref(page.path) }}</li>
    {% end %}
  </ul>
{% end %}

Table of Contents

## Table of Contents

- [[#introduction|Introduction]]
- [[#installation|Installation]]
- [[#configuration|Configuration]]
- [[#usage|Usage]]

Cross-Reference in Content

For detailed setup instructions, see [[docs/getting-started/installation|the installation guide]].

Once installed, configure your site as described in [[docs/reference/config#site-settings|Site Settings]].

Troubleshooting

If a link doesn't resolve:

  1. Check path spelling — Paths are case-sensitive
  2. Verify page exists — Usebengal validateto check
  3. Check path format — Use path without.mdextension
  4. Try absolute path — Use/docs/pageinstead of relative

Bengal validates links during build:

bengal validate

This checks:

  • Cross-references resolve
  • Markdown links point to existing pages
  • Anchor links target valid headings

Debugging Broken References

Broken cross-references show visual indicators:

<span class="broken-ref" data-ref="nonexistent-page"
      title="Page not found: nonexistent-page">
  [nonexistent-page]
</span>

Use browser dev tools to inspect broken references and see what path was attempted.

See Also

3

Code Blocks

Add syntax-highlighted code with Rosettes, include files, configure themes, and create multi-language examples

Code Blocks

How to add syntax-highlighted code to your documentation.

Basic Code Blocks

Wrap code in triple backticks with a language identifier:

```python
def hello():
    print("Hello, World!")
```

Result:

def hello():
    print("Hello, World!")

Supported Languages

Bengal uses Rosettes for syntax highlighting—a modern, thread-safe highlighter designed for Python 3.14t free-threading. Rosettes supports 55+ languages:

Language Identifier
Python python, py
JavaScript javascript, js
TypeScript typescript, ts
Bash/Shell bash, sh, shell, zsh
YAML yaml, yml
JSON json
TOML toml
HTML html
CSS css
Markdown markdown, md
Language Identifier
Rust rust, rs
Go go, golang
C c, h
C++ cpp, c++, cxx
Zig zig
Nim nim
V v, vlang
Mojo mojo, 🔥
Language Identifier
Java java
Kotlin kotlin, kt
Scala scala, sc
Groovy groovy, gradle
Clojure clojure, clj
Language Identifier
SQL sql, mysql, postgresql
GraphQL graphql, gql
HCL/Terraform hcl, terraform, tf
Dockerfile dockerfile, docker
Nginx nginx
INI ini, cfg, conf
Protobuf protobuf, proto
Prisma prisma
CUE cue
Pkl pkl
Language Identifier
Ruby ruby, rb
PHP php
Perl perl, pl
Lua lua
R r, rlang
Julia julia, jl
Elixir elixir, ex
PowerShell powershell, ps1, pwsh
Fish fish
AWK awk, gawk
Language Identifier
SCSS/Sass scss, sass
Vue vue
Svelte svelte
Jinja2 jinja2, jinja, j2
Liquid liquid, jekyll
Handlebars handlebars, hbs
Nunjucks nunjucks, njk
Twig twig
Language Identifier
Diff diff, patch
reStructuredText rst, restructuredtext
AsciiDoc asciidoc, adoc
LaTeX latex, tex
MyST myst, mystmd
Mermaid mermaid, mmd
XML xml, xsl, svg
HTTP http, https
Regex regex, regexp
Language Identifier
Haskell haskell, hs
OCaml ocaml, ml, reasonml
Dart dart
Swift swift
WebAssembly wasm, wat
CUDA cuda, cu
Triton triton
Stan stan
Gleam gleam
Cypher cypher, neo4j

Tip

Languages not in the list are rendered as plain preformatted text with proper HTML escaping.

Line Highlighting

Draw attention to specific lines with{hl_lines="..."}:

```python {hl_lines="2"}
def hello():
    print("This line is highlighted!")
    return True
```

Highlight Multiple Lines

{hl_lines="2 4"}     # Lines 2 and 4
{hl_lines="2-5"}     # Lines 2 through 5
{hl_lines="1 3-5 8"} # Lines 1, 3-5, and 8

Line Numbers

Add line numbers for reference:

```python {linenos=true}
def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n - 1)
```

Start from a Specific Line

```python {linenos=true, linenostart=42}
# This starts at line 42
def important_function():
    pass
```

Include Code from Files

Useliteralincludeto include code directly from source files:

:::{literalinclude} /path/to/file.py
:language: python
:::

Include Specific Lines

:::{literalinclude} /path/to/file.py
:language: python
:start-line: 10
:end-line: 25
:::

With Line Numbers and Highlighting

:::{literalinclude} /path/to/file.py
:language: python
:linenos:
:emphasize-lines: 3,5
:::

Tip

Useliteralincludeto keep documentation in sync with actual source code. When the source changes, your docs update automatically.

Multi-Language Examples

Show the same concept in multiple languages with code tabs. Usecode-tabsfor a streamlined, code-first experience with auto-sync, language icons, and copy buttons.

Syntax

Tab labels are automatically derived from the code fence language—no extra markers needed:

:::{code-tabs}

```python
print("Hello")
```

```javascript
console.log("Hello");
```

```bash
echo "Hello"
```

:::

Result

I0:

With Filenames and Highlights

Add filenames and line highlights directly in the code fence info string:

Custom Tab Labels

Usetitle="..."to override the default language-derived label:

:::{code-tabs}

```javascript title="React"
const [count, setCount] = useState(0);
```

```javascript title="Vue"
const count = ref(0);
```

:::

Files Without Extensions

For files likeDockerfile that have no extension, use title=:

:::{code-tabs}

```dockerfile title="Dockerfile"
FROM python:3.14-slim
```

```yaml docker-compose.yml
services:
  web:
    build: .
```

:::

Code Tabs vs Tab-Set

Feature code-tabs tab-set
Syntax Just code fences Nested:::{tab-item}
Auto sync ✅ All code-tabs sync by language Manual with:sync:
Language icons ✅ Automatic Manual with:icon:
Copy button ✅ Built-in ❌ None
Line numbers ✅ Auto for 3+ lines ❌ None
Best for Code examples Mixed content

Tip

Usecode-tabs when you're showing the same concept in multiple programming languages. Use tab-setwhen tabs contain mixed content (text, images, etc.) or aren't code-focused.

Inline Code

For inline code, use single backticks:

Run `bengal build` to generate your site.

Result: Run bengal buildto generate your site.

Code with Caption

Add a caption to your code block:

```python
:caption: Example: Hello World function

def hello():
    print("Hello, World!")
```

Diff Highlighting

Show code changes with diff syntax:

```diff
- old_function()
+ new_function()
  unchanged_line()
```

Console/Terminal Output

For terminal sessions, useconsole or shell-session:

```console
$ bengal build
Building site...
✓ Built 42 pages in 1.2s
```

Best Practices

  • Always specify a language for syntax highlighting
  • Useliteralincludefor code that must stay in sync with source
  • Highlight the important lines to guide readers
  • Keep code examples short and focused on one concept
  • Add line numbers when referencing specific lines in text

Syntax Themes

Bengal's syntax highlighting uses Rosettes, which provides configurable themes that adapt to light/dark mode.

Available Themes

Theme Description Mode
bengal-tiger Bengal brand theme with orange accents Dark
bengal-snow-lynx Light variant with warm teal Adaptive
bengal-charcoal Minimal dark variant Dark
bengal-blue Blue accent variant Dark
monokai Classic warm, vibrant theme Dark
dracula Purple accent theme Dark
github GitHub's syntax theme Adaptive
github-light GitHub light mode only Light
github-dark GitHub dark mode only Dark

Configuration

Configure syntax highlighting inconfig/_default/theme.yaml:

theme:
  # Site palette (syntax inherits from this when theme is "auto")
  default_palette: "snow-lynx"

  syntax_highlighting:
    # Theme selection:
    # - "auto": Inherit from default_palette (recommended)
    # - Specific theme name: "monokai", "dracula", etc.
    theme: "auto"

    # CSS class output style:
    # - "semantic": Human-readable classes (.syntax-function, .syntax-string)
    # - "pygments": Pygments-compatible short classes (.nf, .s)
    css_class_style: "semantic"

Palette Inheritance

Whentheme: "auto" is set, the syntax theme automatically inherits from your site's default_palette:

Site Palette Syntax Theme
default/ empty bengal-tiger
snow-lynx bengal-snow-lynx
brown-bengal bengal-tiger
silver-bengal bengal-charcoal
charcoal-bengal bengal-charcoal
blue-bengal bengal-blue

CSS Class Styles

Rosettes supports two CSS class naming conventions:

Human-readable class names that describe the code element's purpose:

<span class="syntax-function">greet</span>
<span class="syntax-string">"Hello"</span>
<span class="syntax-keyword">def</span>

Best for: New projects, custom themes, semantic CSS.

Short class names compatible with existing Pygments themes:

<span class="nf">greet</span>
<span class="s">"Hello"</span>
<span class="k">def</span>

Best for: Migrating from Pygments, using existing Pygments CSS themes.

Note

Rosettes is designed for Python 3.14t free-threading with zero global mutable state. It provides thread-safe syntax highlighting that's up to 3.75× faster than Pygments. See Rosettes Performance for benchmarks.

Seealso

Rosettes Documentation:

4

Tables

Create simple and complex tables using pipe syntax, list-table directive, and interactive data-tables

Tables

Bengal supports three table formats, each suited to different use cases.

Quick Reference

Format Best For Features
Pipe tables Simple data, 3-5 columns Standard Markdown, easy to write
list-table Complex cells, multi-line content Code blocks, lists, formatted text
data-table Large datasets (50+ rows) Sort, filter, search, pagination

Pipe Tables (Standard Markdown)

Use pipe tables for simple, scannable data:

| Name | Role | Location |
|------|------|----------|
| Alice | Engineer | NYC |
| Bob | Designer | LA |
| Carol | PM | Seattle |

Result:

Name Role Location
Alice Engineer NYC
Bob Designer LA
Carol PM Seattle

Column Alignment

Control alignment with colons in the separator row:

| Left | Center | Right |
|:-----|:------:|------:|
| Text | Text   | 99.99 |
| More | More   | 0.50  |
Left Center Right
Text Text 99.99
More More 0.50
  • :---left-align (default)
  • :--:center
  • ---:right-align

List-Table Directive

For tables with multi-line cells, code blocks, or nested content:

:::{list-table} Pricing Comparison
:header-rows: 1
:widths: 20 40 40

* - Feature
  - Free Plan
  - Pro Plan
* - Storage
  - 5 GB
  - Unlimited
* - Support
  - Community forums
  - Priority email + phone
* - API Access
  - 1,000 calls/month
  - Unlimited
:::

Result:

List table has no rows

List-Table Options

Option Description Example
:header-rows: Number of header rows (default: 0) :header-rows: 1
:widths: Column widths as percentages :widths: 30 70
:class: CSS class for styling :class: striped

Rich Content in Cells

List-table cells support full Markdown, including code blocks:

:::{list-table} API Endpoints
:header-rows: 1

* - Endpoint
  - Description
  - Response
* - `GET /users`
  - List all users.

    Returns paginated results with metadata.
  - ```json
    {"users": [...], "total": 100}
    ```
* - `POST /users`
  - Create a new user.

    Requires authentication.
  - ```json
    {"id": "abc123", "name": "Alice"}
    ```
:::

Key syntax:

  • Start each row with* -
  • Start each subsequent cell with -(2-space indent)
  • Blank line before multi-line content within a cell

Data-Table Directive

For large datasets with interactive features:

:::{data-table} data/products.yaml
:search: true
:filter: true
:sort: true
:pagination: 10
:columns: name,price,category
:::

Data-Table Options

Option Description Default
:search: Enable text search false
:filter: Enable column filters false
:sort: Enable column sorting false
:pagination: Rows per page 25
:columns: Columns to display (comma-separated) All columns
:height: Fixed table height Auto

Data File Format

Data files can be YAML or CSV:

# data/products.yaml
- name: Widget A
  price: 29.99
  category: Electronics
  stock: 150
- name: Widget B
  price: 49.99
  category: Electronics
  stock: 75
- name: Gadget C
  price: 19.99
  category: Accessories
  stock: 200

Or CSV format:

name,price,category,stock
Widget A,29.99,Electronics,150
Widget B,49.99,Electronics,75
Gadget C,19.99,Accessories,200

Choosing the Right Format

Scenario Use This
Simple data, few columns Pipe table
Code examples in cells list-table
Multi-paragraph cells list-table
Feature comparison list-table with :header-rows: 1
API reference tables list-tablewith code cells
Hardware/software matrices data-table
Datasets with 50+ rows data-table
User-searchable data data-table with :search: true

Styling Tables

CSS Classes

Add custom classes to list-tables:

:::{list-table}
:class: striped hover compact

* - Row 1
  - Data
* - Row 2
  - Data
:::

Common classes: striped, hover, compact, bordered

Responsive Behavior

Tables scroll horizontally on small screens. For wide tables:

  • Split into multiple focused tables
  • Move less critical columns to expandable sections
  • Provide downloadable CSV for full data

Troubleshooting

Cells Not Aligning

Problem: Content appears in wrong columns.

Fix: Ensure consistent indentation. Each cell must start with exactly -(2 spaces + hyphen):

* - First cell
  - Second cell    ✅ Correct (2 spaces)
   - Third cell    ❌ Wrong (3 spaces)

Code Blocks Breaking Table

Problem: Fenced code blocks inside pipe tables break rendering.

Fix: Uselist-tablefor cells containing code blocks. Pipe tables don't support fenced code.

Data-Table Not Loading

Problem:data-tableshows error or empty.

Fix:

  1. Verify the data file path is correct (relative to content root)
  2. Check YAML/CSV syntax is valid
  3. Ensure column names in:columns:match the data file

Best Practices

  • Use pipe tables for simple, scannable data
  • Use list-table when cells need rich formatting
  • Always include a header row for context
  • Keep tables focused—one topic per table
  • Consider mobile readers—avoid very wide tables
  • Use:widths:to control column proportions
  • Right-align numeric columns for easier scanning

See Also

5

Directives Reference

Complete reference for all available markdown directives in Bengal

Directives Reference

Bengal extends Markdown with powerful directives using:::{name} or ```{name}syntax. Directives provide rich components like callouts, tabs, cards, and more.

Key Terms

Directive
Extended markdown syntax that creates rich components. Bengal supports two syntax styles: fenced (```{name}) and MyST (:::{name}).
Fenced Syntax
Directives using triple backticks (e.g.,```{note}). Used for admonitions, dropdowns, badges, checklists, and include directives.
MyST Syntax
Directives using triple colons (e.g.,:::{card}). Used for cards, tabs, buttons, steps, and list tables. Named after the MyST Markdown specification.
Show 30 more terms
Container Directive
A directive that contains other directives (e.g.,{cards}, {steps}, {tab-set}). Requires 4 fences minimum (::::) and increments for deeper nesting.
Nesting
Placing directives inside other directives. Each nesting level requires incrementing the fence count (container: 4 fences, nested item: 4 fences, deeper nesting:5+ fences).
Admonition
A styled callout box that draws attention to important information. Available in multiple types (note, warning, tip, danger, etc.) with distinct visual styling.
Callout
Another name for an admonition - a visual box that highlights important content separate from the main text flow.
Card Grid
A container directive ({cards}) that creates a responsive grid of card components. Supports flexible column layouts and responsive breakpoints.
Card
An individual card component ({card}) within a card grid. Can include icons, links, images, colors, and footer content.
Tab Set
A container directive ({tab-set}) that groups multiple tab items together. Provides tabbed navigation for organizing related content.
Tab Item
An individual tab ({tab-item}) within a tab set. Contains content for one tab panel.
Dropdown
A collapsible section directive ({dropdown}) that can be expanded or collapsed. Useful for optional or advanced content to reduce cognitive load.
Grid
A Sphinx-Design compatibility directive ({grid}) that converts to card grids internally. Prefer {cards}for new content.
Badge
A small styled label for tags, status indicators, or labels. Renders as an inline element with customizable CSS classes.
Button
A styled link element that appears as a button. Supports colors, sizes, icons, and link targets for calls-to-action.
Steps Container
A container directive ({steps}) that groups multiple step directives together. Requires 4 fences minimum (::::).
Step
An individual step directive ({step}) within a steps container. Contains content for one step in a sequential guide.
Checklist
A styled container for bullet lists and task lists. Provides visual styling for prerequisites, requirements, or task tracking.
Rubric
A pseudo-heading that looks like a heading but doesn't appear in the table of contents. Perfect for API documentation section labels.
List Table
A table created from nested lists, avoiding pipe character conflicts in type annotations. Useful for Python type hints and complex data structures.
Code Tabs
An interactive tabbed interface ({code-tabs}) for displaying code examples in multiple languages. Users can switch between languages with tab navigation.
Data Table
An interactive table directive ({data-table}) with JavaScript-enhanced features like sorting, filtering, and pagination. Requires Tabulator.js in your theme.
Object Tree
Bengal's hierarchical representation of your site structure (Site → Sections → Pages). Navigation directives traverse this tree to generate content automatically.
Child Cards
A directive ({child-cards}) that automatically creates a card grid from a section's child pages and subsections.
Breadcrumbs
A directive ({breadcrumbs}) that generates hierarchical navigation showing the current page's location in the site structure.
Siblings
A directive ({siblings}) that lists pages at the same level as the current page within their shared parent section.
Prev/Next
A directive ({prev-next}) that generates previous/next navigation links within a section.
Related
A directive ({related}) that lists pages with matching tags.
Include Directive
A directive ({include}) that includes markdown files directly in your content. Supports line ranges and relative path resolution.
Literal Include Directive
A directive ({literalinclude}) that includes code files as syntax-highlighted code blocks. Auto-detects language from file extension and supports line ranges, emphasis, and line numbers.
Snippet
A reusable content file (typically markdown or code) that can be included in multiple pages. Organized in dedicated directories likecontent/snippets/.
Path Resolution
The process of finding included files. Bengal resolves paths relative to the current page's directory first, then falls back to the site root.
Path Traversal Prevention
Security feature that prevents including files outside the site root. Blocks../sequences and absolute paths to protect against unauthorized file access.

Quick Reference

Admonitions

Directive Purpose
{note} Information callout
{tip} Helpful suggestion
{warning} Warning callout
{caution} Cautionary note (renders as warning)
{danger} Critical warning
{error} Error message
{info} Informational content
{example} Example usage
{success} Success message
{seealso} Cross-reference callout

Layout

Directive Aliases Purpose
{cards} {grid} Card grid container
{card} {grid-item-card} Individual card
{child-cards} Auto-generate cards from children
{tab-set} {tabs} Tab container
{tab-item} {tab} Individual tab
{dropdown} {details} Collapsible section
{container} {div} Generic wrapper

Formatting

Directive Aliases Purpose
{badge} {bdg} Styled badge
{button} Link button
{build} Build-time badge
{steps} Step-by-step guide
{step} Individual step
{checklist} Styled checklist
{rubric} Pseudo-heading (not in TOC)
{list-table} Table from lists

Content Reuse

Directive Purpose
{include} Include markdown file
{literalinclude} Include code file with syntax highlighting

Interactive

Directive Aliases Purpose
{code-tabs} {code_tabs} Multi-language code tabs
{data-table} Interactive data table

Media Embeds

Directive Purpose
{youtube} YouTube embed (privacy-enhanced by default)
{vimeo} Vimeo embed (DNT by default)
{tiktok} TikTok short-form video embed
{spotify} Spotify tracks, albums, playlists, podcasts
{soundcloud} SoundCloud tracks, playlists
{video} Self-hosted HTML5 video
{audio} Self-hosted HTML5 audio
{figure} Image with caption
{gist} GitHub Gist embed
{codepen} CodePen embed
{codesandbox} CodeSandbox embed
{stackblitz} StackBlitz embed
{asciinema} Terminal recording
{gallery} Image gallery

Navigation

Directive Purpose
{breadcrumbs} Breadcrumb navigation trail
{siblings} Sibling page links
{prev-next} Previous/next navigation links
{related} Related pages by shared tags

Versioning

Directive Aliases Purpose
{since} {versionadded} Mark feature as new
{deprecated} {versionremoved} Mark feature as deprecated
{changed} {versionchanged} Mark behavior change

Other

Directive Aliases Purpose
{glossary} Render terms from glossary data
{target} {anchor} Create link target
{icon} {svg-icon} Inline SVG icon
{example-label} Example label for documentation
{marimo} Interactive Python notebook

Directive Syntax

Bengal supports MyST-style directive syntax using triple colons:::. Most directives use this format:

:::{directive-name} Optional Title
:option: value

Content here
:::

Named Closers

For nested directives, use named closers (:::{/name}) to avoid ambiguity:

:::{cards}
:columns: 3

:::{card} First Card
Content
:::{/card}

:::{card} Second Card
Content
:::{/card}

:::{/cards}

Deep Nesting

Named closers are particularly useful for deeply nested structures:

:::{steps}

:::{step} First Step
:::{tip}
Remember to check the logs!
:::
:::{/step}

:::{step} Second Step
:::{warning}
This step requires admin access.
:::
:::{/step}

:::{/steps}

Directive Options

Options are specified with:option: valuesyntax on separate lines after the directive name:

:::{card} Card Title
:icon: rocket
:link: /docs/quickstart/
:color: blue

Card content here.
:::

Boolean options can omit the value:

:::{tab-item} Python
:selected:

Python code here.
:::

Categories

Common Options

Many directives support these common options:

  • :class:- Custom CSS class
  • :id:- Element ID
  • :title:- Title text (alternative to title in directive name)

Examples

Basic Admonition

:::{note}
This is a note with **markdown** support.
:::

Card Grid

:::{cards}
:columns: 3

:::{card} Card 1
:icon: book
:link: docs/getting-started

Content here
:::

:::{card} Card 2
Content here
:::

:::{/cards}

Tabs

:::{tab-set}

:::{tab-item} Python
```python
print("Hello")
```
:::

:::{tab-item} JavaScript
```javascript
console.log("Hello");
```
:::

:::{/tab-set}

Glossary Directive

The{glossary} directive renders terms from a centralized glossary data file (data/glossary.yaml) as a styled definition list. Filter terms by tags to show relevant definitions for each page.

Syntax

:::{glossary}
:tags: directives, core
:sorted: true
:collapsed: true
:limit: 3
:::

Options

Option Default Description
:tags: (required) Comma-separated tags to filter terms (OR logic)
:sorted: false Sort terms alphabetically
:show-tags: false Display tag badges under each term
:collapsed: false Wrap in collapsible<details>element
:limit: (all) Show only first N terms; remaining in expandable section
:source: data/glossary.yaml Custom glossary file path

Examples

Example: Basic Usage

Show terms tagged with "directives":

:::{glossary}
:tags: directives
:::

Example: Progressive Disclosure

Show first 3 terms, rest expandable:

:::{glossary}
:tags: directives, core
:limit: 3
:::

Example: Fully Collapsed

Entire glossary in collapsible section:

:::{glossary}
:tags: formatting
:collapsed: true
:::

Example: Both Options

Collapsed, with limited terms when expanded:

:::{glossary}
:tags: layout
:collapsed: true
:limit: 2
:::

Glossary Data Format

Terms are defined indata/glossary.yaml:

terms:
  - term: Directive
    definition: Extended markdown syntax using `{name}` that creates rich components.
    tags: [directives, core]

  - term: Admonition
    definition: A styled callout box for **important** information.
    tags: [directives, admonitions]

Note: Definitions support inline markdown: backticks for code, **bold**, and *italic*.

Next Steps

  • Browse directive categories above for detailed syntax
  • See Content Reuse for include/literalinclude strategies
  • Check Writer Quickstart for markdown basics
6

Admonitions

Reference for admonition directives (note, warning, tip, danger, etc.)

Admonition Directives

Admonitions create styled callout boxes for notes, warnings, tips, and other important information.

Key Terms

Admonition
A styled callout box that draws attention to important information. Available in multiple types (note, warning, tip, danger, etc.) with distinct visual styling.
Callout
Another name for an admonition - a visual box that highlights important content separate from the main text flow.

Syntax

:::{admonition-type} Optional Title
Content with **full markdown** support.
:::

Available Types

Type Purpose CSS Class
{note} General information admonition note
{tip} Helpful tips admonition tip
{warning} Warnings admonition warning
{caution} Cautions (renders as warning) admonition warning
{danger} Critical warnings admonition danger
{error} Error messages admonition error
{info} Informational content admonition info
{example} Examples admonition example
{success} Success messages admonition success
{seealso} Cross-references and related links admonition seealso

Examples

:::{note}
This is a note with **markdown** support.
:::
:::{warning} Important
This feature requires admin access.
:::
:::{tip}
Use this pattern for better performance.
:::

Renders as "Tip" (capitalized type name).

Admonitions support full markdown including nested directives. Use named closers for clarity:

:::{note}
Here's a tip:

:::{tip}
Nested admonitions work!
:::
:::{/note}

All Types

:::{note} Note
General information
:::

:::{tip} Tip
Helpful suggestion
:::

:::{warning} Warning
Something to be careful about
:::

:::{danger} Danger
Critical warning
:::

:::{error} Error
Error message
:::

:::{info} Info
Informational content
:::

:::{example} Example
Example usage
:::

:::{success} Success
Success message
:::

:::{caution} Caution
Cautionary note (renders as warning)
:::

:::{seealso} See Also
- [Related Topic](#)
- [Another Resource](#)
:::

Options

Admonitions support the:class:option for custom styling:

Option Description
:class: Additional CSS classes to apply
:::{note} Custom Note
:class: highlight bordered

Content here
:::

Rendering

Admonitions render with icons and structured markup:

<div class="admonition note">
  <p class="admonition-title">
    <span class="admonition-icon-wrapper"><!-- SVG icon --></span>
    <span class="admonition-title-text">Note</span>
  </p>
  <p>Content here</p>
</div>

CSS classes follow the pattern admonition {type}. The caution type maps to the warningCSS class.

With custom classes via:class::

<div class="admonition note highlight bordered">
  <!-- ... -->
</div>

Best Practices

  1. Match type to severity: Usenote/tip for helpful info, warning/caution for potential issues, danger/errorfor critical problems
  2. Keep titles concise: Short, descriptive titles (2-4 words) work best
  3. Use sparingly: More than 2-3 admonitions per page can overwhelm readers
  4. Nest carefully: Nested admonitions work but increase visual complexity
  5. Preferseealso for links: Group related links in a dedicated seealsoblock rather than inline
7

Layout Directives

Reference for layout directives (cards, tabs, dropdown, grid)

Layout Directives

Layout directives organize content into structured components like card grids, tabs, and collapsible sections.

Key Terms

Card Grid
A container directive ({cards}) that creates a responsive grid of card components. Supports flexible column layouts and responsive breakpoints.
Card
An individual card component ({card}) within a card grid. Can include icons, links, images, colors, and footer content.
Tab Set
A container directive ({tab-set}) that groups multiple tab items together. Provides tabbed navigation for organizing related content.
Tab Item
An individual tab ({tab-item}) within a tab set. Contains content for one tab panel.
Dropdown
A collapsible section directive ({dropdown}) that can be expanded or collapsed. Useful for optional or advanced content to reduce cognitive load.
Grid
A Sphinx-Design compatibility directive ({grid}) that converts to card grids internally. Prefer {cards}for new content.

Cards

Create responsive card grids for navigation, feature highlights, or content organization.

Card Grid ({cards})

Container for multiple cards with responsive column layout. Use:::{/cards}to close.

Syntax:

:::{cards}
:columns: 3
:gap: medium
:style: default
:variant: navigation

:::{card} Card Title
:icon: book
:link: docs/getting-started
:color: blue
:image: /hero.jpg
:footer: Updated 2025

Card content with **markdown** support.
:::

:::{/cards}

Options:

  • :columns:- Column layout:
    • auto(default) - Auto-fit based on card width
    • 2, 3, 4- Fixed columns
    • 1-2-3- Responsive (mobile-tablet-desktop)
    • 1-2-3-4- Responsive with wide breakpoint
  • :gap: - Gap between cards: small, medium (default), large
  • :style: - Card style: default, minimal, bordered
  • :variant: - Variant: navigation (default), info, concept
  • :layout: - Card layout: default, horizontal, portrait, compact

Individual Card ({card})

Single card within a cards container.

Syntax:

:::{card} Card Title
:icon: book
:link: docs/getting-started
:color: blue
:image: /hero.jpg
:footer: Footer text

Card content with **markdown** support.

+++
Footer content (alternative to :footer: option)
:::

Options:

  • :icon: - Icon name (e.g., book, code, rocket)
  • :link: - Card link URL (path, slug, or id:ref-target)
  • :color: - Color: blue, green, red, yellow, orange, purple, gray, pink, indigo, teal, cyan, violet
  • :image:- Header image URL
  • :footer: - Footer text (or use +++separator)
  • :pull: - Auto-fetch fields from linked page: title, description, icon, image, date, tags
  • :layout: - Override grid layout: horizontal, portrait, compact

Footer Separator:

:::{card} Title
Body content
+++
Footer content
:::

Examples

Example: Basic Card Grid

:::{cards}
:columns: 3

:::{card} Getting Started
:icon: rocket
:link: docs/get-started

Learn the basics
:::

:::{card} API Reference
:icon: code
:link: docs/reference/api

Complete API docs
:::

:::{card} Tutorials
:icon: book
:link: docs/tutorials

Step-by-step tutorials
:::

:::{/cards}

Example: Responsive Columns

:::{cards}
:columns: 1-2-3
:gap: large

:::{card} Card 1
Content
:::

:::{card} Card 2
Content
:::

:::{/cards}

Example: Cards with Nested Admonitions

Named closers eliminate fence-counting for complex nesting:

:::{cards}
:columns: 2

:::{card} Important Card
:::{warning}
This feature requires special setup.
:::
:::{/card}

:::{card} Regular Card
Standard content here.
:::{/card}

:::{/cards}

Named Closers

Use:::{/name}to explicitly close any container directive, eliminating the need to count colons.

Example: Auto-Pull from Linked Pages

Use:pull:to automatically fetch metadata from linked pages, reducing content duplication:

:::{cards}
:columns: 3

:::{card}
:link: docs/getting-started/writer-quickstart
:pull: title, description
:::

:::{card}
:link: id:themer-qs
:pull: title, description, icon
:::

:::{card} Custom Title
:link: docs/get-started/quickstart-contributor
:pull: description

Custom content overrides pulled description.
:::

:::{/cards}

The :pull:option supports:

  • title- Page title from frontmatter
  • description- Page description from frontmatter
  • icon- Icon from frontmatter
  • image- Image from frontmatter
  • badge- Badge from frontmatter

Reference Targets

Useid:ref-name syntax to reference pages by their frontmatter idfield instead of path. This makes links stable even if you reorganize content.

Example: Layout Variants

Use:layout:for different card arrangements:

:::{cards}
:columns: 2
:layout: horizontal

:::{card} Feature One
:image: /images/feature1.png

Image on left, content on right.
:::

:::{card} Feature Two
:image: /images/feature2.png

Great for feature showcases.
:::

:::{/cards}

Layout options:

  • default- Vertical card (image top, content below)
  • horizontal- Image left, content right
  • portrait- Tall aspect ratio (2:3), great for app screenshots or TCG-style cards
  • compact- Reduced padding for dense reference lists

Example: Portrait Cards (TCG/Phone Style)

:::{cards}
:columns: 3
:layout: portrait

:::{card} App Screenshot
:image: /images/app-home.png

Home screen of our mobile app.
:::

:::{card} Dashboard
:image: /images/app-dashboard.png

Analytics dashboard view.
:::

:::{/cards}

Tabs

Create tabbed content sections for organizing related content.

Tab Set ({tab-set})

Container for tab items. Use:::{/tab-set}to close.

Aliases:{tabs}

Syntax:

:::{tab-set}
:sync: my-key

:::{tab-item} Tab Title
:selected:

Tab content with **markdown** support.
:::

:::{tab-item} Another Tab
More content
:::

:::{/tab-set}

Options:

Option Default Description
:sync: Sync key for synchronizing tabs across multiple tab-sets
:id: auto-generated Tab set ID for targeting

Tab Item ({tab-item})

Individual tab within a tab-set.

Aliases:{tab}

Syntax:

:::{tab-item} Tab Title
:selected:
:icon: python
:badge: Recommended

Tab content
:::

Options:

Option Default Description
:selected: false Mark this tab as initially selected
:icon: Icon name to display next to the tab label
:badge: Badge text (e.g., "New", "Beta", "Pro")
:disabled: false Mark tab as disabled/unavailable

Examples

Example: Basic Tabs

:::{tab-set}

:::{tab-item} Python
```python
print("Hello")
```
:::

:::{tab-item} JavaScript
```javascript
console.log("Hello");
```
:::

:::{/tab-set}

Example: Synchronized Tabs

:::{tab-set}
:sync: code-example

:::{tab-item} Python
Python code
:::

:::{/tab-set}

:::{tab-set}
:sync: code-example

:::{tab-item} Python
Same Python code (synced)
:::

:::{/tab-set}

Example: Tabs with Icons and Badges

:::{tab-set}

:::{tab-item} Python
:icon: python
:badge: Recommended
:selected:

Python code here.
:::

:::{tab-item} JavaScript
:icon: javascript

JavaScript code here.
:::

:::{tab-item} Ruby
:disabled:

Ruby support coming soon.
:::

:::{/tab-set}

Example: Tabs with Nested Admonitions

Named closers eliminate fence-counting for complex nesting:

:::{tab-set}

:::{tab-item} Setup
:::{warning}
Make sure to backup your data first!
:::

Setup instructions here.
:::{/tab-item}

:::{tab-item} Usage
Regular usage content.
:::{/tab-item}

:::{/tab-set}

Named Closers

Use:::{/name}to explicitly close any container directive, eliminating the need to count colons.

Dropdown

Collapsible sections for optional or advanced content. Renders as HTML5<details>/<summary>elements for native browser support without JavaScript.

Aliases:{details}

Syntax:

:::{dropdown} Title
:open: true
:icon: info
:description: Additional context about what is inside

Content with **markdown** support.

:::{note}
Nested directives work!
:::
:::

Options:

Option Default Description
:open: false Open by default
:icon: Icon name to display next to title
:badge: Badge text (e.g., "New", "Advanced")
:color: Color variant:success, warning, danger, info, minimal
:description: Secondary text below the title to elaborate on content
:class: Additional CSS classes

Examples

Example: Collapsed by Default

:::{dropdown} Advanced Options
:icon: settings

Click to expand advanced configuration options.
:::

Example: Open by Default

:::{dropdown} Quick Reference
:icon: info
:open: true

Common commands and shortcuts.
:::

Example: With Description

:::{dropdown} API Authentication
:icon: lock
:description: Learn about OAuth 2.0, API keys, and JWT tokens

Detailed authentication documentation here.
:::

Example: With Badge and Color

:::{dropdown} New Features
:icon: star
:badge: New
:color: success

Check out the latest features!
:::

Example: Warning Dropdown

:::{dropdown} Breaking Changes
:icon: alert
:color: warning
:badge: v2.0

Review breaking changes before upgrading.
:::

Grid (Sphinx-Design Compatibility)

Deprecated

Legacy compatibility layer for Sphinx-Design grid syntax. Grid directives are parsed and converted to cards internally.

Syntax:

:::{grid} 1 2 2 2
:gutter: 1

:::{grid-item-card} Title
:link: docs/getting-started

Content
:::

:::{/grid}

Migration: Replace {grid} with {cards} and {grid-item-card} with {card}:

:::{cards}
:columns: 1-2-2-2
:gap: small

:::{card} Title
:link: docs/getting-started

Content
:::

:::{/cards}

Best Practices

  1. Card Grids: Use for navigation, feature highlights, or content organization
  2. Tabs: Use for related content that doesn't need to be visible simultaneously
  3. Dropdowns: Use for optional or advanced content to reduce cognitive load
  4. Responsive Design: Use responsive column syntax (1-2-3) for mobile-friendly layouts

Auto-Generated Cards

For section index pages, consider using{child-cards}instead of manual cards. It automatically generates cards from child sections and pages:

:::{child-cards}
:columns: 2
:gap: medium
:layout: default
:include: sections
:fields: title, description, icon
:::

Options:

Option Default Description
:columns: auto Column layout (same as{cards})
:gap: medium Gap between cards:small, medium, large
:layout: default Card layout:default, horizontal, portrait, compact
:include: all What to include:sections, pages, all
:fields: title Fields to pull:title, description, icon

See Navigation Directives for full documentation.

8

Interactive Directives

Reference for interactive directives (code-tabs, data-table)

Interactive Directives

Interactive directives provide JavaScript-enhanced components for code examples and data tables.

Quick Reference

Directive Purpose Key Features
:::{code-tabs} Multi-language code examples Auto-sync, icons, copy button, highlighting
```{data-table} Interactive data tables Search, filter, sort, pagination

Key Terms

Code Tabs
An interactive tabbed interface ({code-tabs}) for displaying code examples in multiple languages. Users can switch between languages with tab navigation.
Data Table
An interactive table directive ({data-table}) with JavaScript-enhanced features like sorting, filtering, and pagination. Requires Tabulator.js in your theme.

Code Tabs

Create tabbed interfaces for multi-language code examples with automatic language sync, syntax highlighting, and copy buttons.

Aliases:{code-tabs}, {code_tabs}

Features

  • Simplified syntax — Tab labels derived from code fence language (no markers needed)
  • Auto language sync — All code-tabs on a page sync when user picks a language
  • Language icons — Automatic icons for common language categories
  • Pygments highlighting — Proper syntax coloring with line numbers and line emphasis
  • Copy button — One-click copy per tab
  • Filename display — Show filename badges in tab labels

Syntax

Tab labels are derived automatically from the code fence language:

:::{code-tabs}

```python main.py {3-4}
def greet(name):
    """Say hello."""
    print(f"Hello, {name}!")

greet("World")
```

```javascript index.js {2-3}
function greet(name) {
    // Say hello
    console.log(`Hello, ${name}!`);
}

greet("World");
```

:::

Info String Format

The code fence info string supports filename, title override, and highlights:

language [filename] [title="Label"] [{line-highlights}]
Example Tab Label Filename Badge Highlights
python Python
python client.py Python client.py
python {3-4} Python Lines 3-4
python client.py {3-4} Python client.py Lines 3-4
python title="Flask" Flask
python app.py title="Flask" {5-7} Flask app.py Lines 5-7

Options

Option Default Description
:sync: "language" Sync key for cross-tab synchronization. Use"none"to disable.
:linenos: auto Force line numbers on/off. Auto-enabled for 3+ lines.

Option aliases::line-numbers: = :linenos:

Custom Tab Labels

Usetitle="..."to override the default language-derived label:

:::{code-tabs}

```javascript title="React"
const [count, setCount] = useState(0);
```

```javascript title="Vue"
const count = ref(0);
```

:::

Files Without Extensions

For files likeDockerfile that have no extension, use title=:

:::{code-tabs}

```dockerfile title="Dockerfile"
FROM python:3.14-slim
```

```yaml docker-compose.yml
services:
  web:
    build: .
```

:::

Language Display Names

Languages are automatically formatted with proper casing:

Language Display Name
javascript, js JavaScript
typescript, ts TypeScript
cpp, cxx C++
csharp, cs C#
golang Go
python Python
Others Capitalized

Language Sync

By default, all code-tabs on a page with the same sync key synchronize. When a user selects Python in one code-tabs block, all other blocks switch to Python.

Sync is persistent: User preferences are saved to localStorage and restored on page load.

Disable sync for a specific block:

:::{code-tabs}
:sync: none

```python
# Before
```

```python
# After
```

:::

Custom sync groups:

:::{code-tabs}
:sync: api-examples

```python
...
```

:::

:::{code-tabs}
:sync: config-examples

```yaml
...
```

:::

Examples

Example: Basic Usage

:::{code-tabs}

```python
def hello():
    print("Hello, World!")
```

```javascript
function hello() {
    console.log("Hello, World!");
}
```

:::

Example: With Filenames and Highlights

:::{code-tabs}

```python api_client.py {3-4}
import requests

def get_users():
    response = requests.get("/api/users")
    return response.json()
```

```javascript client.js {3-4}
import fetch from 'node-fetch';

async function getUsers() {
    const response = await fetch('/api/users');
    return response.json();
}
```

:::

Example: Multiple Languages

:::{code-tabs}

```python
print("Hello")
```

```javascript
console.log("Hello");
```

```go
fmt.Println("Hello")
```

```bash
echo "Hello"
```

:::

Rendering

Code tabs render as an interactive tabbed interface with:

  • Tab navigation for switching languages
  • Language icons (terminal for bash/shell, database for SQL, code for others)
  • Filename badges in tab labels
  • Pygments syntax highlighting with line numbers
  • Line emphasis for highlighted lines
  • Copy button on hover
  • First tab selected by default
  • Synced tab selection across the page

Language Icons

Icons are automatically assigned based on language category:

Languages Icon
bash, shell, sh, zsh, fish, powershell, cmd, console Terminal
sql, mysql, postgresql, sqlite, mongodb Database
json, yaml, toml, xml, ini, env File/Code
All others Code

Legacy Syntax

The older### Languagemarker syntax is still supported for backward compatibility:

:::{code-tabs}

### Python (main.py)
```python
print("hello")
```

### JavaScript
```javascript
console.log("hello");
```

:::

This legacy syntax is not recommended for new content.

Data Table

Create interactive data tables with sorting, filtering, and pagination from external data files.

Requires: Tabulator.js (included in default theme)

Syntax

The directive takes a file path argument pointing to a YAML or CSV data file:

```{data-table} data/people.yaml
:search: true
:filter: true
:sort: true
:pagination: 50
```

Supported File Formats

YAML (recommended):

# data/people.yaml
columns:
  - title: Name
    field: name
  - title: Age
    field: age
  - title: City
    field: city
data:
  - name: Alice
    age: 30
    city: Seattle
  - name: Bob
    age: 25
    city: Portland

CSV:

name,age,city
Alice,30,Seattle
Bob,25,Portland

Column headers are auto-generated from field names in CSV files.

Options

Option Default Description
:search: true Show search box above table
:filter: true Enable column header filters
:sort: true Enable column sorting (click headers)
:pagination: 50 Rows per page. Set tofalseto disable pagination.
:height: auto Table height (e.g.,400px). Use autofor dynamic height.
:columns: all Comma-separated list of columns to display (filters visible columns)

Examples

Example: Basic Data Table

```{data-table} data/api-functions.yaml
```

With data/api-functions.yaml:

columns:
  - title: Name
    field: name
  - title: Type
    field: type
  - title: Description
    field: description
data:
  - name: get_user
    type: Function
    description: Get user by ID
  - name: create_user
    type: Function
    description: Create new user
  - name: update_user
    type: Function
    description: Update user

Example: With Options

```{data-table} data/packages.yaml
:search: true
:filter: true
:sort: true
:pagination: false
:height: 300px
```

Example: Filtered Columns

Show only specific columns from the data file:

```{data-table} data/people.yaml
:columns: name,city
```

Rendering

Data tables render as interactive Tabulator tables with:

  • Search box: Filter across all columns (if:search: true)
  • Column filters: Per-column filtering in headers (if:filter: true)
  • Sorting: Click headers to sort (if:sort: true)
  • Pagination: Navigate large datasets (if:pagination:is a number)
  • Responsive design: Columns collapse on narrow screens
  • Keyboard navigation: Full keyboard support for accessibility

Best Practices

  1. Code Tabs: Use for comparing code across languages or showing multiple approaches
  2. Language Sync: Keep sync enabled (default) for API docs with consistent language examples
  3. Filenames: Add filenames when showing complete file contents
  4. Line Highlights: Use sparingly to draw attention to key lines
  5. Data Tables: Use YAML format for explicit column definitions; CSV for simple tabular data
  6. Performance: Enable pagination for tables with more than 50 rows
  7. Accessibility: Both directives support full keyboard navigation
9

Content Reuse

Snippets, data files, and DRY patterns

Reusing Content

Write once, publish everywhere. Bengal provides multiple ways to avoid repeating yourself.

Reuse Strategies

flowchart LR subgraph "Single Source" A[Snippet] B[Data File] C[Shortcode] end subgraph "Multiple Outputs" D[Page 1] E[Page 2] F[Page 3] end A --> D A --> E B --> D B --> F C --> E C --> F

Quick Reference

Reusable Markdown fragments stored in_snippets/:

_snippets/
├── install/
│   ├── pip.md
│   └── uv.md
└── warnings/
    └── experimental.md

Include in any page:

```{include} _snippets/install/pip.md
```

Structured YAML/JSON indata/:

# data/team.yaml
- name: Jane Doe
  role: Lead Developer
  github: janedoe

Access in templates:

{% for member in site.data.team %}
  {{ member.name }} - {{ member.role }}
{% end %}

Query content dynamically:

{# All tutorials #}
{% let tutorials = site.pages
   |> where('type', 'tutorial') %}

{# Recent posts #}
{% let recent = site.pages
   |> sort_by('date', reverse=true)
   |> take(5) %}

When to Use What

Method Best For Example
Snippets Repeated prose blocks Installation instructions, warnings
Data Files Structured data Team members, product features
Filtering Dynamic lists Recent posts, related pages
Shortcodes Parameterized components Video embeds, API badges

Tip

Start with snippets for common content blocks. Graduate to data files when you need structured data, and filtering when you need dynamic queries.

10

Content Snippets

Reusable content fragments with include directives

Content Snippets

Maintaining consistency across hundreds of pages is hard. If you copy-paste the same "Warning" or "Code Example" into 20 pages, updating it becomes a nightmare.

Bengal provides powerful directives to implement a "Write Once, Publish Everywhere" strategy directly in your Markdown content.

Strategy 1: Include Markdown Files

Use the{include}directive to include entire markdown files or sections.

Basic Usage

Create a reusable snippet file:

<!-- content/snippets/warning.md -->
:::{warning}
This feature is in beta. Please report any issues.
:::

Include it in any page:

# My Article

Here is some content.

```{include} snippets/warning.md
```

More content.

Include Specific Lines

Include only a portion of a file:

```{include} snippets/api-example.md
:start-line: 5
:end-line: 20
```

This includes lines 5-20 from the file.

Path Resolution

Paths are resolved relative to:

  1. Current page's directory - If you're incontent/docs/content/, snippets/warning.md looks in content/docs/content/snippets/
  2. Site root - Falls back to site root if not found relative to page

Strategy 2: Include Code Files

Use the{literalinclude}directive to include code files as syntax-highlighted code blocks.

Basic Usage

```{literalinclude} examples/api.py
```

This automatically:

  • Detects the language from the file extension (.py→ Python)
  • Applies syntax highlighting
  • Includes the entire file

Include Specific Lines

Include only a portion of a code file:

```{literalinclude} examples/api.py
:start-line: 10
:end-line: 25
```

Emphasize Lines

Highlight specific lines:

```{literalinclude} examples/api.py
:emphasize-lines: 7,8,9
```

Or a range:

```{literalinclude} examples/api.py
:emphasize-lines: 7-9
```

Complete Example

```{literalinclude} examples/api.py
:language: python
:start-line: 10
:end-line: 30
:emphasize-lines: 15,16,17
:linenos: true
```

Supported Languages

literalincludeauto-detects language from file extensions:

  • Python:.py
  • JavaScript:.js, .ts
  • Web:.html, .css
  • Config:.yaml, .yml, .json, .toml
  • Shell:.sh, .bash, .zsh
  • And many more - See the directive documentation for full list

Best Practices

Organize Snippets

Create a dedicated directory for reusable content:

content/
├── _snippets/
│   ├── warnings/
│   │   ├── beta-notice.md
│   │   └── deprecated-feature.md
│   ├── code-examples/
│   │   ├── api-client.py
│   │   └── config.yaml
│   └── common-content/
│       ├── installation-steps.md
│       └── troubleshooting.md
└── docs/
    └── guides/
        └── my-guide.md  # Uses snippets via {include}

Keep Snippets Focused

Each snippet should have a single purpose:

Good:_snippets/warnings/beta-notice.md- One warning
Bad:_snippets/all-warnings.md- Multiple warnings mixed together

Use Relative Paths

Prefer relative paths for portability:

Good:_snippets/warning.md
Bad:/absolute/path/to/warning.md

Security Model

Bengal includes built-in security protections to prevent path traversal attacks and unauthorized file access.

Security Features

1. Path Traversal Prevention

  • All include paths are validated to ensure they stay within the site root
  • Attempts to use../to access files outside the site root are blocked
  • Absolute paths (starting with/) are rejected

2. Site Root Enforcement

  • Files must be within the site's content directory or site root
  • Resolved paths are checked against the site root boundary
  • Invalid paths log warnings and fail gracefully

Troubleshooting

Summary

Directive Best For Example
{include} Reusable markdown content (warnings, steps, explanations) {include} _snippets/warning.md
{literalinclude} Code examples, configuration files, scripts {literalinclude} examples/api.py

Both directives support:

  • ✅ Line ranges (:start-line:, :end-line:)
  • ✅ Relative path resolution
  • ✅ Security (prevents path traversal)
  • ✅ Nested directives (included content can use other directives)

Seealso

✓ Track Complete