Markdown extensions catalog
Every non-CommonMark Markdown feature Pennington adds — tabs, alerts, code annotations, and cross-reference tags — with syntax, arguments, and emitted classes.
The catalog of non-CommonMark Markdown features enabled in Pennington's Markdig pipeline. Markdig's own built-in syntax (tables, footnotes, and so on) is not covered here.
| Extension | Syntax | Controlled by | Doc page |
|---|---|---|---|
| Tabbed code | Adjacent fences with tabs=true |
UseTabbedCodeBlocks |
Tabbed code |
| Content tabs | # [Label](#tab/id) headings ended by --- |
UseContentTabs |
Content tabs |
| Includes | [!INCLUDE …] (full form under Includes) |
Markdown parser (always on) | Reuse shared content |
| Alerts | > [!KIND] inside blockquote |
UseCustomAlerts |
Alerts |
| Code annotations | Trailing-comment [!code …] directive |
UseSyntaxHighlighting |
Code annotations |
| Cross-reference tags | <xref:uid> or href="xref:uid" |
Resolved in the response stage | Cross-references |
| Shortcodes | <?# Name args /?> |
Registered IShortcode handlers (pre-parse expansion) |
Shortcodes |
Tabs
Renders a run of consecutive fenced code blocks (starting with one that carries tabs=true) as a single tabbed container with role="tablist", role="tab", and panel regions. The first tab is active by default.
Syntax
```csharp tabs=true title="C#"
// block A
```
```razor title="Razor"
@* block B *@
```
Each fenced block in the consecutive run becomes a tab panel; only the first block requires tabs=true to open the group.
Arguments
| Name | Type | Default | Description |
|---|---|---|---|
tabs |
true |
(absent) | Applies to the first fence in the group. Marks a fenced block as the start of a tabbed run; consecutive subsequent fences join the same group. |
title |
string (optionally quoted) |
pretty language name derived from the info string | Applies to each fence in the group. Overrides the label shown on the tab button. |
Arguments are key=value pairs; quoted values are allowed. See Code-block argument reference for the full grammar.
Emitted CSS classes
| Option | Default class | Role |
|---|---|---|
OuterWrapperCss |
not-prose |
Outer <div> wrapper that opts out of prose styling. |
ContainerCss |
tab-container |
Container wrapping tablist and panels. |
TabListCss |
tab-list |
role="tablist" row. |
TabButtonCss |
tab-button |
role="tab" <button> (carries data-state="active"|"inactive"). |
TabPanelCss |
tab-panel |
aria-labelledby-bound panel wrapping the rendered code block. |
Classes are configurable via TabbedCodeBlockRenderOptions passed to UseTabbedCodeBlocks.
Minimal example
Markdown source showing a two-fence tabbed group:
---
title: Authoring a doc page
description: Populate DocSiteFrontMatter, add an alert, and group code samples into tabs.
tags:
- authoring
- front-matter
- markdown
sectionLabel: Guides
order: 20
---
# Authoring a doc page
## Callouts
> [!NOTE]
> Alerts render with a coloured left border and an icon matching the kind.
## Tabbed code groups
```bash tabs=true title="dotnet CLI"
dotnet add package Pennington
```
```powershell title="PowerShell"
Install-Package Pennington
```
```xml title="csproj"
<PackageReference Include="Pennington" Version="*" />
```
Content tabs
Groups a run of DocFX-style tab headings into a single tabset whose panels hold arbitrary Markdown — prose, lists, code, callouts. Distinct from tabbed code: the tab strip sits in the reading flow rather than inside code chrome.
Syntax
A tab opens with a level-1 heading whose only inline is a link to #tab/<id>; the link text is the button label. Consecutive tab headings form one group, ended by a thematic break (---).
# [Bash](#tab/bash)
Use the bash variant.
# [PowerShell](#tab/pwsh)
Use the PowerShell variant.
---
Dependent tabs
A third path segment — #tab/<id>/<condition> — gates the tab on another group's selection. <condition> is a tab id that has its own plain group elsewhere on the page; the dependent panel shows only when its <id> is the active button and its <condition> is that other group's selected id.
# [.NET](#tab/lang/linux)
.NET on Linux.
# [.NET](#tab/lang/windows)
.NET on Windows.
---
Arguments
| Name | Type | Default | Description |
|---|---|---|---|
id |
identifier | — | First segment after #tab/. Identifies the tab; ids are page-wide, so equal ids select together. |
condition |
identifier | (absent) | Optional second segment. Gates the tab on the selected id of the condition's own group. |
Emitted CSS classes
| Element | Class | Attributes | Role |
|---|---|---|---|
| Container | ctabs |
data-content-tabs |
Tabset wrapper; not not-prose. |
| Tab strip | ctabs-bar not-prose |
role="tablist" |
Button row; not-prose isolates the buttons from page typography. |
| Button | ctab-btn |
data-tab, data-active, role="tab", aria-selected |
One per distinct id. |
| Panel | ctab-panel |
data-tab, data-condition, data-active, role="tabpanel" |
One per tab heading; not not-prose, so content keeps prose styling. |
The first panel is active in the server-rendered HTML; the client script recomputes selection on load, syncs equal ids page-wide, and persists each choice in localStorage.
Minimal example
A two-tab group whose panels hold prose; the --- thematic break closes the set:
# [Bash](#tab/bash)
Run the bash script.
# [PowerShell](#tab/pwsh)
Run the PowerShell script.
---
Includes
Splices a referenced Markdown file into the host page during parsing. The directive works as a standalone block or inline within a sentence; the referenced file is expanded recursively.
Syntax
[!INCLUDE [block partial](../_includes/partial.md)]
Text before [!INCLUDE [inline partial](../_includes/snippet.md)] and after.
Arguments
| Name | Type | Default | Description |
|---|---|---|---|
title |
string | — | Bracketed label. Parsed but not emitted; present for DocFX compatibility. |
path |
path | — | File path resolved relative to the referencing file. Absolute URLs are not fetched. |
Behavior
| Case | Result |
|---|---|
| Target file found | Content is spliced in; a leading YAML front-matter block is stripped first. |
| Directive in a fenced code block | Left verbatim, so the syntax can be documented. |
| Target missing | Replaced with <!-- Pennington: include not found: <path> -->. |
| Include cycle, or depth past 16 | Replaced with <!-- Pennington: include cycle broken: <path> -->. |
| Absolute URL path | Replaced with <!-- Pennington: include skipped (not a local file): <path> -->. |
Relative links and images inside an included file are not rebased — they resolve as if written in the host page.
Minimal example
A standalone block include splicing a shared partial into the host page:
[!INCLUDE [install steps](../_includes/install.md)]
Alerts
Parses a GitHub-flavored > [!KIND] token as the first line of a blockquote and emits an AlertBlock with two CSS classes. The blockquote form is the only accepted syntax.
Syntax
> [!NOTE]
> Body text of the alert, CommonMark rendered.
KIND is a case-insensitive alphabetic token.
Arguments
| Name | Type | Default | Description |
|---|---|---|---|
KIND |
identifier | — | One of NOTE, TIP, CAUTION, WARNING, IMPORTANT. Case-insensitive. Unrecognized tokens still parse, emitting markdown-alert-<kind> using the lowercased value. |
Built-in kinds and emitted CSS classes
Every alert receives two classes: markdown-alert (constant) and markdown-alert-<kind>.
| Kind | Secondary class | Typical use |
|---|---|---|
NOTE |
markdown-alert-note |
Supplementary information. |
TIP |
markdown-alert-tip |
Helpful aside. |
CAUTION |
markdown-alert-caution |
Risky operation. |
WARNING |
markdown-alert-warning |
Something likely to go wrong. |
IMPORTANT |
markdown-alert-important |
Must-read information. |
Minimal example
Markdown excerpt showing a [!NOTE] block in context:
---
title: Authoring a doc page
description: Populate DocSiteFrontMatter, add an alert, and group code samples into tabs.
tags:
- authoring
- front-matter
- markdown
sectionLabel: Guides
order: 20
---
# Authoring a doc page
## Callouts
> [!NOTE]
> Alerts render with a coloured left border and an icon matching the kind.
> Supported kinds include `NOTE`, `TIP`, `IMPORTANT`, `WARNING`, and
> `CAUTION`.
Code annotations
After syntax highlighting, each rendered line is scanned for a [!code …] directive inside a language-appropriate comment. The directive is stripped and a CSS class is applied to the line (and optionally to the enclosing <pre>). The word: variant wraps a matching substring; the include-start/include-end/exclude-start/exclude-end directives remove surrounding lines from the output.
Syntax
```csharp
var x = 1; // [!code highlight]
var y = 2; // [!code ++]
var z = 3; // [!code word:z|renamed from q]
```
The directive must appear inside a comment marker recognized for the language; the marker and any now-empty comment wrapper are removed, leaving trailing content intact. Code-block argument reference is the canonical reference for the full directive set (highlight, ++/--, focus, error, warning, word:, the include/exclude region markers) and the recognized comment markers.
Emitted CSS classes
Line-level classes (highlight, diff-add, diff-remove, focused / blurred, error, warning) are added to the <span class="line"> wrapper. Block-level classes (has-highlighted, has-diff, has-focused, has-errors, has-warnings, has-word-highlights) are added to the outer <pre>. The word: notation emits word-highlight or word-highlight-with-message on the wrapped span, plus the callout elements word-highlight-wrapper, word-highlight-message, word-highlight-arrow-container, word-highlight-arrow-outer, and word-highlight-arrow-inner.
Minimal example
An annotated fence exercising [!code highlight], [!code ++], and [!code --]; the enclosing <pre> receives has-highlighted and has-diff, and the trailing directive comments are stripped from the emitted HTML:
```csharp
var message = "hello"; // [!code highlight]
var added = "added"; // [!code ++]
var removed = "gone"; // [!code --]
```
Cross-reference tags
xref: links resolve after rendering against the uid-to-route map built from every page's front-matter uid:. Two surface forms are supported: the tag form <xref:uid> and the attribute form [text](xref:uid). Unknown uids emit a diagnostic that surfaces in the dev overlay and in the static-build report.
Syntax
See <xref:reference.api.pennington-options>.
See [PenningtonOptions](xref:reference.api.pennington-options).
uid is the exact string declared in a page's front-matter uid: key.
Arguments
| Name | Type | Default | Description |
|---|---|---|---|
uid |
identifier | — | Exact string declared in a page's front-matter uid: key. The tag form derives link text from the target page's title; the attribute form uses the supplied link text verbatim. |
Emitted CSS classes
The rewriter emits a standard <a href="…"> element with no added class; styling is delegated to the surrounding prose stylesheet.
Minimal example
Both surface forms resolving the same uid — the tag form derives its link text from the target page title, the attribute form uses the supplied label verbatim:
See <xref:reference.api.pennington-options> for the full options catalog.
Configure MonorailCSS through [the options record](xref:reference.api.monorail-css-options).
Shortcodes
Shortcodes run before Markdig parses the page rather than as a Markdig extension: each <?# Name args /?> directive is expanded to text or HTML first by a registered IShortcode handler, and the result flows through the pipeline as ordinary markdown. Names are case-insensitive.
Syntax
A call has a self-closing form and a block form with inline content:
<?# Name positional key="value" /?>
<?# Name ?>inline content<?#/ Name ?>
To document a directive without expanding it, prefix the opener with a backslash — the expander emits the directive verbatim. (Every literal directive on this page uses that escape.)
Built-ins
| Name | Arguments | Description |
|---|---|---|
Version |
format=full\|major\|minor\|informational (default full) |
Emits the host application's assembly version. |
PackageVersion |
none | Emits Pennington's own published NuGet version. |
Unknown names and handler failures degrade to an HTML comment plus a warning diagnostic, so one bad call site never fails the render. See the shortcodes how-to for the handler contract and registration.
See also
- How-to: Add a Markdig extension or inline parser — register syntax this catalog doesn't list
- How-to: Tabbed code
- How-to: Content tabs
- How-to: Reuse shared content
- How-to: Alerts
- How-to: Code annotations
- How-to: Cross-references
- How-to: Expand a directive before Markdig parses
- Related reference: Code-block argument reference