Content Author Mastery
Master advanced Markdown features, directives, and content reuse.
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
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?
Create internal links, cross-references, and anchor links.
Link to Python stdlib, NumPy, and other Bengal sites.
Syntax highlighting, line numbers, and file includes.
Add images, figures, videos, and embeds.
Simple pipe tables and complex list-tables.
Notes, warnings, tips, and collapsible sections.
Tabs, dropdowns, steps, and cards.
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
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
- Directives Reference — Complete directive documentation
- Icon Reference — Available icons
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]] |
Standard Markdown Links
Use standard Markdown link syntax for external URLs and simple internal links.
External Links
[Visit GitHub](https://github.com)
[Email us](mailto:hello@example.com)
Internal Links
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:
.mdextension is optional — Bengal resolves bothpage.mdandpage- 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 with Title
[Link text](https://example.com "Optional title")
Cross-References ([[link]]Syntax)
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 at
docs/getting-started.md - Uses the page's title as link text
- Handles path normalization automatically
Custom Link Text
[[docs/getting-started|Get Started Guide]]
The |separator lets you specify custom link text while still using intelligent path resolution.
Link by Custom ID
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 Headings
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:
- Custom ID (
id:xxx) - Path lookup (
docs/page) - 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.
Anchor Links
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:
- Custom ID (
id:xxx) — Checked first, fastest lookup - Path lookup (
docs/page) — Most explicit, recommended - 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
Link Stability
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 Section
## Related Pages
- [[docs/tutorials/getting-started|Getting Started]]
- [[docs/reference/api|API Reference]]
- [[docs/tutorials/examples|Examples]]
Dynamic Related Pages
{% 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
Broken Links
If a link doesn't resolve:
- Check path spelling — Paths are case-sensitive
- Verify page exists — Use
bengal validateto check - Check path format — Use path without
.mdextension - Try absolute path — Use
/docs/pageinstead of relative
Link Validation
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
- External References — Link to Python stdlib, NumPy, other Bengal sites
- Content Authoring — Markdown and MyST syntax
- Template Functions — All template helpers
- Content Validation — Link validation and health checks
- Graph Analysis — Analyze site connectivity
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:
- Common
- Systems
- JVM & .NET
- Data & Config
- Scripting
- Web & Templates
- Markup & Docs
- Functional
| 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:
I0: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
- Use
literalincludefor 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
- Formatting Directives Reference — Complete
literalincludeoptions - Authoring Overview — Other authoring features
- Theming Guide — Customize your site's appearance including syntax themes
Rosettes Documentation:
- Supported Languages — Full list of 55+ supported languages
- Custom Themes — Create your own syntax themes
- Line Highlighting — Advanced highlighting options
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 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:
- Verify the data file path is correct (relative to content root)
- Check YAML/CSV syntax is valid
- 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
- Formatting Directives Reference — Complete directive options
- Authoring Overview — Other content authoring features
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
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
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
- Match type to severity: Use
note/tipfor helpful info,warning/cautionfor potential issues,danger/errorfor critical problems - Keep titles concise: Short, descriptive titles (2-4 words) work best
- Use sparingly: More than 2-3 admonitions per page can overwhelm readers
- Nest carefully: Nested admonitions work but increase visual complexity
- Prefer
seealsofor links: Group related links in a dedicatedseealsoblock rather than inline
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 width2,3,4- Fixed columns1-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, orid: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 frontmatterdescription- Page description from frontmattericon- Icon from frontmatterimage- Image from frontmatterbadge- 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 rightportrait- Tall aspect ratio (2:3), great for app screenshots or TCG-style cardscompact- 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)
DeprecatedLegacy 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
- Card Grids: Use for navigation, feature highlights, or content organization
- Tabs: Use for related content that doesn't need to be visible simultaneously
- Dropdowns: Use for optional or advanced content to reduce cognitive load
- 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.
Related
- Navigation Directives - Auto-generated cards, breadcrumbs, siblings
- Admonitions - Callout boxes
- Formatting Directives - Badges, buttons, steps
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
- Code Tabs: Use for comparing code across languages or showing multiple approaches
- Language Sync: Keep sync enabled (default) for API docs with consistent language examples
- Filenames: Add filenames when showing complete file contents
- Line Highlights: Use sparingly to draw attention to key lines
- Data Tables: Use YAML format for explicit column definitions; CSV for simple tabular data
- Performance: Enable pagination for tables with more than 50 rows
- Accessibility: Both directives support full keyboard navigation
Related
- Layout Directives — Static tabs (
tab-set) and cards - Formatting Directives — List tables (static)
- Code Blocks — Standard code block syntax
Content Reuse
Snippets, data files, and DRY patterns
Reusing Content
Write once, publish everywhere. Bengal provides multiple ways to avoid repeating yourself.
Reuse Strategies
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.
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:
- Current page's directory - If you're in
content/docs/content/,snippets/warning.mdlooks incontent/docs/content/snippets/ - 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
File Not Found
Error:Include error: File not found: snippets/warning.md
Solutions:
- Check the path is correct relative to your page
- Verify the file exists in the expected location
- Try an absolute path from site root:
content/_snippets/warning.md - Check for typos in the filename (case-sensitive on some systems)
Path Traversal Blocked
Error:Include path traversal rejected or Include outside site root
Cause: Attempted to include files outside the site root (security feature)
Solutions:
- Move the file into your site's content directory
- Use a relative path that stays within the site root
- Check that your site root is correctly configured in
bengal.toml
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
- Content Reuse Overview — DRY content strategies
- Filtering — Advanced content queries