Skip to main content

Code blocks and syntax highlighting

For showing code without running it, use fenced Markdown code blocks. Starlight integrates Expressive Code for syntax highlighting, frames, line markers, and copy-to-clipboard — no extra configuration needed.

For code learners can edit and run, use <RunnablePython> or <RunnableRobot> instead.

Basic syntax

A fenced block:

```python
def greet(name):
return f"Hello, {name}!"
````

Renders as:

def greet(name):
return f"Hello, {name}!"

Supported languages cover everything authors typically need: python, javascript / js, typescript / ts, tsx, jsx, bash / sh, json, yaml, xml, html, css, markdown / md, mdx, robot, powershell, dockerfile, nginx, sql, rust, go, c, cpp, java. Full list: Expressive Code's Shiki bundle.

Title

Add a title= after the language to render a filename in the frame:

```python title="hello.py"
print("Hello, world!")
````
hello.py
print("Hello, world!")

The frame chrome makes it clear the snippet is the contents of a file.

Mark lines

The {N} and {N-M} ranges highlight specific lines:

```python title="example.py" {2,5-7}
def average(nums):
total = 0 # ← marked
for n in nums:
total += n
n = len(nums) # ← marked
if n == 0: # ← marked
return 0 # ← marked
return total / n
````

Renders with the marked lines highlighted.

Insertion / deletion markers

For diffs:

```diff lang="python"
def greet(name):
- return "Hello, " + name + "!"
+ return f"Hello, {name}!"

```diff lang="python"
def greet(name):
- return "Hello, " + name + "!"
+ return f"Hello, {name}!"
````

The `lang="python"` after `diff` ensures the surrounding code keeps its
Python coloring; the `+` and `-` lines get the conventional green/red
highlight.

### "ins" / "del" / "mark" / "error" / "warning"

For richer per-line semantics without diff syntax:

`````mdx
```python ins={2} del={5} {7-8}
def greet(name):
print(f"Hello, {name}!") # added
if not name:
raise ValueError("name required")
print("legacy path") # removed
# focus area:
return name.lower().strip()
# ↑ marked
````
MarkerMeaningColor
{N}markYellow
ins={N}insertionGreen
del={N}deletionRed
{"text" }search-and-markYellow
frame="none"drop the frame chrome
frame="terminal"render as terminal

Frames: code vs terminal

Expressive Code auto-detects whether a block is a terminal command (and shows it with a terminal-style frame) vs source code (file frame). Force the choice:

```bash frame="terminal"
$ pnpm install
$ pnpm dev

````mdx
```bash frame="code" title=".bashrc"
export PATH="$HOME/.local/bin:$PATH"
````

Heuristic: bash blocks where every line starts with $ get the terminal frame automatically.

Inline code

Wrap inline code with single backticks:

Use the `pip install` command to add a dependency.

Renders: Use the pip install command to add a dependency.

For inline code with a specific language (rare; mostly for syntax-aware tooltips), Expressive Code doesn't auto-highlight inline. Stick to plain backticks for inline.

Hiding lines

Hide setup lines that aren't part of the lesson focus, but are needed to make the example complete:

```python
# collapse-start
import sys
sys.path.insert(0, '../lib')
# collapse-end

# This is the line the lesson is about
print(answer)
````

The hidden lines are collapsed by default with a "+ N more" toggle. (Note: this requires the expressive-code-plugin-collapsible-sections plugin, which Starlight 0.30+ ships out of the box.)

Robot Framework code blocks

hello.robot
*** Settings ***
Documentation Minimal example

*** Test Cases ***
Say Hello
Log Hello, Robot Framework!
Should Be Equal ${1 + 1} ${2}

Robot Framework grammar is supported. The ${variable} syntax highlights correctly without escaping (this is plain Markdown, not MDX-evaluated code — see MDX for the difference).

Multi-language tabs

For "do this on macOS / Linux / Windows" content, pair <Tabs> with fenced blocks:

import { Tabs, TabItem } from '@astrojs/starlight/components';

<Tabs syncKey="os">
<TabItem label="macOS / Linux">
```bash
source .venv/bin/activate
```
</TabItem>

<TabItem label="Windows (PowerShell)">
```powershell
.venv\Scripts\Activate.ps1
```
</TabItem>

<TabItem label="Windows (cmd.exe)">
```bat
.venv\Scripts\activate.bat
```
</TabItem>
</Tabs>

The syncKey="os" makes all <Tabs> on the page with the same key remember the learner's choice across them. So picking macOS / Linux in one tab group shows macOS / Linux in every other group on the page.

See Tables, callouts, images for the full <Tabs> reference.

When to use code blocks vs runnable cells

Decision tree:

  • Show only (the learner is reading, not running): fenced code block.
  • Run-and-edit (Python): <RunnablePython>.
  • Run-and-edit (RF): <RunnableRobot>.
  • Two side-by-side examples (e.g., the same task in pip vs uv): a Markdown table or two <Tabs>. Don't try to make the runnable cells side-by-side; they're heavy and competing for attention.

Authoring tips

  • Title every file-shaped block. A learner copying code without knowing where it goes is lost.
  • Mark the line you're talking about. {3} to highlight line 3 makes the prose line up with the code line.
  • Don't overuse frame="none". The frame is what makes the block feel like a unit.
  • Wrap shell prompts. $ for user shell, # for root, > for Windows. Expressive Code colors them distinctly.
  • Avoid copy-paste-bait. Code that has placeholders like <your-key> with <> characters looks like JSX inside MDX even if it's inside a code block — they're safe inside fences, but the prose around them needs care. Use literal &lt;your-key&gt; outside fences.

What about Mermaid / diagrams?

Mermaid diagrams aren't enabled by default in this Starlight config. Add the Starlight Mermaid integration if you need them; for now, ASCII diagrams in fenced blocks work everywhere:

```
┌─────────┐ ┌─────────┐
│ Author │ → │ build │
└─────────┘ └─────────┘

.scorm12.zip
```

Where to go next