starlight-cannoli-plugins
v2.17.1
Published
Starlight plugins for automatic sidebar generation and link validation
Maintainers
Readme
Cannoli Starlight Plugins
A collection of powerful plugins for Astro Starlight documentation sites.
Plugins
Starlight Index-Only Sidebar
Automatically generates a nested Starlight sidebar by recursively scanning directories for index.md/index.mdx files. Only directories with index files appear in the sidebar, creating a clean, minimal navigation structure.
Features:
- Recursively scans directories for
index.mdorindex.mdxfiles - Creates sidebar entries only for pages with index files
- Respects frontmatter:
draft: trueandsidebar.hidden: truehide entries - Automatically collapses single-child groups (no intermediate wrappers)
- Configurable depth limiting to flatten deeply nested content
- Two labeling modes: directory names or frontmatter titles
- Ignores
assetsdirectories entirely
Options:
directories(required): Array of directory names to scan (e.g.,["guides", "api"])maxDepthNesting(optional, default:100): Maximum nesting depth. Root is level 0. At max depth, deeper index files are flattened as sibling items.dirnameDeterminesLabels(optional, default:true): Whentrue, all labels use raw directory names. Whenfalse, slug item labels come from frontmattertitlefield; group labels still use directory names.
Usage:
// astro.config.mjs
import { defineConfig } from "astro/config";
import starlight from "@astrojs/starlight";
import { starlightIndexOnlySidebar } from "starlight-cannoli-plugins";
export default defineConfig({
integrations: [
starlight({
title: "My Docs",
plugins: [
starlightIndexOnlySidebar({
directories: ["guides", "api", "tutorials"],
maxDepthNesting: 2, // optional
dirnameDeterminesLabels: false, // optional
}),
],
}),
],
});Starlight Index-Sourced Sidebar
Generates a Starlight sidebar by parsing markdown links out of index.md files. Instead of scanning the filesystem for every index file, this plugin reads the links declared in each directory's own index.md and uses those to build the sidebar — giving you explicit, author-controlled navigation with automatic nesting.
Features:
- Builds sidebar entries from the links written in each directory's
index.md - Links ending in
/indexpointing to anindex.mdcreate nested sub-groups (recursed automatically) - Links ending in
/indexpointing to anindex.mdxadd it as a single leaf item without recursing into it - Sub-group depth limited by
maxDepthNesting; deeper items are flattened - Each index page itself appears as the first leaf item in its group
- Labels for leaf items come from the linked page's frontmatter
title - Respects frontmatter:
draft: true,pagefind: false, andsidebar.hidden: truehide entries - Links to non-markdown files (images, PDFs, etc.) are silently skipped
- Links to markdown files that do not exist on disk are silently skipped
- External links and same-page anchor links (
#section) are silently skipped
Options:
directories(required): Array of directory names to scan (relative tosrc/content/docs)maxDepthNesting(optional, default:100): Maximum nesting depth. At this depth, sub-index groups are not created and their items are flattened into the current level instead. WithmaxDepthNesting: 1, all items are flat under the root group.indexMarker(optional, default:undefined): A string prepended to the label of every index entry to visually distinguish it from regular sidebar entries. Example:"★".
Link conventions in index.md:
- Links to sub-sections must end in
/index(e.g.[Subtopic](./subtopic/index)) — the plugin recurses into those if the target is anindex.md, or adds a leaf item if the target is anindex.mdx - All other relative links become flat leaf items
- External URLs are ignored
Usage:
// astro.config.mjs
import { defineConfig } from "astro/config";
import starlight from "@astrojs/starlight";
import { starlightIndexSourcedSidebar } from "starlight-cannoli-plugins";
export default defineConfig({
integrations: [
starlight({
title: "My Docs",
plugins: [
starlightIndexSourcedSidebar({
directories: ["guides", "reference"],
maxDepthNesting: 2, // optional
}),
],
}),
],
});Example index.md:
---
title: Guides
---
- [Getting Started](./getting-started)
- [Configuration](./configuration)
- [Advanced Topics](./advanced/index)LaTeX Compile
Automatically compiles fenced tex compile and latex compile code blocks to SVG diagrams during the build process. Uses pdflatex and dvisvgm for high-quality, cached SVG output.
Features:
- Compiles LaTeX/TikZ code blocks to SVG automatically
- Caches compiled SVGs by content hash (no recompilation if unchanged)
- Comprehensive error reporting with line numbers and formatted LaTeX source
- Works with Starlight and plain Astro projects
- Requires
svgOutputDirconfiguration (no defaults)
System Requirements:
This plugin requires the following CLI tools to be installed and available on your system:
pdflatex— LaTeX compiler that produces PDF outputdvisvgm— Converts PDF to SVG format
Verify installation by running:
pdflatex --version
dvisvgm --versionOptions:
svgOutputDir(required): Directory where compiled SVG files are written. Must be insidepublic/so Astro serves them as static assets.removeOrphanedSvgs(optional, default:false): Whentrue, SVG files that are no longer referenced by anytex compileblock are deleted automatically. In dev mode, stale SVGs are removed immediately when a block is edited. On build, any remaining orphans are swept at the end.svgClassname(optional): Default CSS class(es) applied to every compiled SVG<img>element. Individual blocks can override this with aclassmeta tag value. Thetex-compiledclass is always present and cannot be overridden.texInputDirs(optional): Directories added to the TeX input search path (TEXINPUTS), allowing\input{}and\include{}to resolve files from your project. Use a trailing/to search only that directory, or//to search it recursively. Multiple directories are supported. When any file in these directories changes, all cached SVGs are invalidated and recompiled.tempOutputDir(optional): When set, a JPEG copy of each compiled diagram is written to this directory, mirroring the folder structure ofsrc/content/docs/. Only blocks that carry ablockid=<n>meta tag produce a JPEG — blocks without it are ignored. The filename format is<originating-file>--<blockid>--<hash>.jpg(e.g.tex-test.md--5--abc123.jpg). JPEGs are deleted automatically when their block is removed, itsblockidchanges, or its content changes. SVG output is unaffected — the JPEG is complementary and intended for local inspection.
astroLatexCompile({
svgOutputDir: "public/static/tex-svgs",
texInputDirs: ["src/latex//"], // search src/latex/ and all its subdirectories
});Usage:
// astro.config.mjs
import { defineConfig } from "astro/config";
import starlight from "@astrojs/starlight";
import { astroLatexCompile } from "starlight-cannoli-plugins";
export default defineConfig({
integrations: [
astroLatexCompile({
svgOutputDir: "public/static/tex-svgs",
removeOrphanedSvgs: true, // optional
}),
starlight({ title: "My Docs" }),
],
});Markdown Syntax:
Use either ```tex compile or ```latex compile — both work identically:
```tex compile
\documentclass[border=5pt]{standalone}
\usepackage{tikz}
\begin{document}
\Large
\begin{tikzpicture}
\node (A) at (0,0) {A};
\node (B) at (2,0) {B};
\draw (A) -- (B);
\end{tikzpicture}
\end{document}
```Document structure:
Each code block must be a complete, self-contained LaTeX document:
```tex compile
\documentclass[border=10pt]{standalone}
\usepackage{tikz}
\usepackage{amsmath}
\begin{document}
\begin{equation*}
E = mc^2
\end{equation*}
\end{document}
```Meta Attributes:
The following attributes can be added to the opening fence:
class="...": CSS classes applied to the resulting<img>element (space-separated). Thetex-compiledclass is always included.alt="...": Alt text for the resulting<img>element. Defaults to"LaTeX diagram"if omitted.
```tex compile class="bg-white rounded-1" alt="A commutative diagram"
\documentclass[border=5pt]{standalone}
\usepackage{tikz}
\begin{document}
\Large
\begin{tikzpicture}
\node {Custom styled diagram};
\end{tikzpicture}
\end{document}
```Remark GFM Slug
A remark plugin that assigns heading IDs using the same GFM slug algorithm as VS Code and GitHub, before Astro's internal rehypeHeadingIds runs. Since rehypeHeadingIds skips headings that already have an id, this takes precedence and produces fragment anchors that match what VS Code's markdown link resolver expects.
This is especially useful when headings contain inline math or other syntax that Astro/Starlight would otherwise slugify from the rendered HTML output (producing long encoded IDs that differ from VS Code's raw-text slugification).
Usage:
// astro.config.mjs
import { defineConfig } from "astro/config";
import { remarkGfmSlug } from "starlight-cannoli-plugins";
export default defineConfig({
markdown: {
remarkPlugins: [
remarkGfmSlug, // must come before remark-math or other transforming plugins
],
},
});Rehype Validate Links
A rehype plugin that validates all internal links in your Markdown/MDX files at build time. Links without matching files will cause the build to fail.
Features:
- Validates
<a href>and<img src>attributes - Supports relative paths (
../other) and absolute paths (/some/page) - Auto-expands extensionless links to match
.mdor.mdxfiles - Converts internal links to site-absolute paths
- Throws build errors for broken links
- Skip validation for forward-reference links using multiple approaches
Usage:
// astro.config.mjs
import { defineConfig } from "astro/config";
import { rehypeValidateLinks } from "starlight-cannoli-plugins";
export default defineConfig({
markdown: {
rehypePlugins: [rehypeValidateLinks],
},
});Or import directly:
import { rehypeValidateLinks } from "starlight-cannoli-plugins/rehype-validate-links";Skipping Link Validation:
There are three ways to skip validation for specific links:
1. Question Mark Prefix (Per-link, in markdown)
Prepend a ? to the link href to skip validation:
[Grade Calculator](?csci-320-331-obrenic/grade-calculator)
[Grade Calculator](?./csci-320-331-obrenic/grade-calculator)
[Grade Calculator](?/csci-320-331-obrenic/grade-calculator)2. HTML Data Attribute (Per-link, requires HTML syntax)
Use the data-no-link-check attribute on anchor tags:
<a href="csci-320-331-obrenic/grade-calculator" data-no-link-check>
Grade Calculator
</a>3. Global Skip Patterns (Configuration-based)
Use the skipPatterns option to exclude links matching glob patterns:
// astro.config.mjs
export default defineConfig({
markdown: {
rehypePlugins: [
[
rehypeValidateLinks,
{
skipPatterns: [
"/csci-320-331-obrenic/grade-calculator", // exact match
"**/draft-*", // glob pattern
],
},
],
],
},
});Sync Docs to Public
Syncs src/content/docs/ to public/ so local files (e.g., PDFs, images) referenced in markdown are served by the dev server and included in builds. In dev mode, it watches for file changes and re-syncs automatically — no restart needed.
Features:
- Syncs once at build start
- Watches for changes in dev mode and re-syncs automatically (debounced)
- Preserves specified child directories in
public/during sync (e.g.,static/) - Supports glob patterns to exclude files from syncing
Usage:
// astro.config.mjs
import { defineConfig } from "astro/config";
import starlight from "@astrojs/starlight";
import { syncDocsToPublic } from "starlight-cannoli-plugins";
export default defineConfig({
integrations: [
syncDocsToPublic({ preserveDirs: ["static"] }),
starlight({ title: "My Docs" }),
],
});Options:
preserveDirs(required): Names of child directories insidepublic/to preserve during sync. These will not be deleted when re-syncing.ignorePatterns(optional): Glob patterns for files to exclude from syncing. Patterns are matched against paths relative tosrc/content/docs/.
syncDocsToPublic({
preserveDirs: ["static"],
ignorePatterns: ["**/*.txt", "**/drafts/**"],
});Starlight DOM Patches
An Astro integration that injects a client-side script to apply opt-in DOM patches on every page. Each patch is disabled by default and must be explicitly enabled.
Options:
hideSingleLineGutters(optional, default:false): Hides the line number gutter on Expressive Code blocks that contain only a single line.syncTocLabelsFromHeadings(optional, default:false): Copies the rendered HTML of each heading into its matching Starlight TOC anchor label, so the TOC properly reflects any custom markup (e.g. math) present in the heading.limitDetailsElementHeight(optional, default:false): Limits the height of expanded<details>elements and makes their content scrollable.offerToggleAllDetails(optional, default:false): Injects an "Expand All Dropdowns" toggle checkbox into the right sidebar (beforenav[aria-labelledby="starlight__on-this-page"]). Clicking it opens or closes every<details>element on the page at once. Only appears on pages that contain at least one visible<details>element.offerTabbedContent(optional, default:false): Injects a "Tabbed view" toggle checkbox immediately after#starlight__on-this-pagein the right sidebar. When enabled by the user, the page's markdown content is reorganised into tabs — one per<h2>heading, plus an optional "Main" tab for any content that appears before the first<h2>. The toggle defaults to off on every page load and is not persisted. Only activates when the page has at least two sections (i.e. two or more<h2>elements, or one<h2>with pre-heading content). Has no effect on pages that lack#starlight__on-this-page(e.g. pages with the TOC disabled). Clicking a TOC anchor while tabs are enabled automatically switches to the tab containing the target heading. The generated elements use the classestabbed-content,tabbed-content-nav,tabbed-content-tab, andtabbed-content-panelfor styling; toggle button uses the existing.toggle-checkbox-btnclass.decorateExternalLinks(optional): Scans all links inside.sl-markdown-contentand prepends a destination-specific icon to recognised link targets. Pass an object enabling each supported destination individually:youtube(default:false): prepends a YouTube play-button SVG (classyt-icon,aria-hidden, inheritscurrentColor) to links pointing toyoutube.comoryoutu.be.
Usage:
// astro.config.mjs
import { defineConfig } from "astro/config";
import starlight from "@astrojs/starlight";
import { starlightDomPatches } from "starlight-cannoli-plugins";
export default defineConfig({
integrations: [
starlightDomPatches({
hideSingleLineGutters: true,
syncTocLabelsFromHeadings: true,
limitDetailsElementHeight: true,
offerToggleAllDetails: true,
offerTabbedContent: true,
decorateExternalLinks: {
youtube: true,
},
}),
starlight({ title: "My Docs" }),
],
});Expressive Code Emphasis
An Expressive Code plugin that highlights specific terms inside code blocks using the emph meta attribute. Matched terms are wrapped in a <span class="fw-supreme"> for custom styling.
Features:
- Comma-separated list of terms to emphasize per code block
- Exact-word matching: terms surrounded by word boundaries or whitespace/string edges are matched; partial substrings are not
- Supports non-word characters (e.g.
(,],;) as well as identifiers
Usage:
Register the plugin with Expressive Code:
// astro.config.mjs
import { defineConfig } from "astro/config";
import starlight from "@astrojs/starlight";
import { expressiveCodeEmphasis } from "starlight-cannoli-plugins";
export default defineConfig({
integrations: [
starlight({
title: "My Docs",
expressiveCode: {
plugins: [expressiveCodeEmphasis()],
},
}),
],
});Then use the emph meta attribute on any code block:
```js emph="foo,bar,()"
function foo() {
return bar() || ();
}
```Multiple terms are separated by commas. Each term is matched as a whole unit — it must be surrounded by whitespace, start/end of line, or (for terms that start/end with word characters) word boundaries.
CLI Utilities
cannoli-latex-cleanup
A manual cleanup utility for the LaTeX compile plugin. Scans your markdown source files for all tex compile code blocks, hashes them, and identifies orphaned SVG files in the output directory that are no longer referenced by any code block.
Note: If you use
removeOrphanedSvgs: truein yourastroLatexCompileconfig, this CLI is generally not needed — orphaned SVGs are cleaned up automatically during both dev and build.
Usage:
Check for orphaned SVGs without deleting:
npx cannoli-latex-cleanup --svg-dir public/static/tex-svgs --checkDelete orphaned SVGs:
npx cannoli-latex-cleanup --svg-dir public/static/tex-svgs --deleteWith custom docs directory (defaults to src/content/docs):
npx cannoli-latex-cleanup --svg-dir public/static/tex-svgs --docs-dir ./src/content/docs --deleteOptions:
--svg-dir(required): Path to the SVG output directory configured inastroLatexCompile--docs-dir(optional, default:src/content/docs): Path to markdown source directory--check: List orphaned SVGs without deleting--delete: Delete orphaned SVGs
Installation
npm install starlight-cannoli-pluginsWith pnpm:
pnpm add starlight-cannoli-pluginsWith yarn:
yarn add starlight-cannoli-pluginsPeer Dependencies
astro≥ 5.0.0@astrojs/starlight≥ 0.30.0 (optional, only needed if usingstarlightIndexOnlySidebar)
License
MIT
Contributing
Contributions welcome! Feel free to open issues or submit pull requests.
