Skip to content
Astro as markdown 4 min read

Syntax Highlighting

Code blocks are the backbone of technical writing, and Astro highlights them out of the box with no plugins to install. Fenced code in your Markdown and MDX is colorized at build time using Shiki, the same engine that powers VS Code. Because the work happens during the build, the resulting HTML carries inline styles and ships zero JavaScript to the browser — your readers get pixel-accurate highlighting without paying a runtime cost. This page covers how Shiki works in Astro, how to pick and switch themes, and how to fall back to Prism if you prefer it.

How highlighting works

When Astro renders Markdown, every fenced code block is passed to Shiki. The language tag after the opening backticks tells Shiki which grammar to apply, and the configured theme determines the colors. The output is a plain <pre><code> element with style attributes baked in — no client-side <script>, no flash of unhighlighted code.

```ts
const greeting: string = "Hello, Astro";
console.log(greeting.toUpperCase());
```

Shiki ships with hundreds of TextMate grammars (ts, astro, bash, json, python, and many more) and dozens of themes, so most projects never need additional configuration. The language identifier is required for accurate tokenization; an unknown or missing tag falls back to plain, uncolored text.

Highlighting runs at build time. If you change the theme, you must rebuild (or restart astro dev) for existing pages to pick up the new colors — there is no runtime restyling.

Configuring Shiki

All highlighting options live under markdown.shikiConfig in astro.config.mjs. The most common change is selecting a theme. You can pass a single theme by name, or a themes object to enable dual light/dark output.

// astro.config.mjs
import { defineConfig } from "astro/config";

export default defineConfig({
  markdown: {
    shikiConfig: {
      theme: "dracula",
      wrap: true,
    },
  },
});
OptionTypeDescription
themestringA single bundled theme name, e.g. "github-dark"
themesobject{ light, dark } pair for dual-theme output
langsarrayExtra languages or custom TextMate grammars to load
wrapboolean | nulltrue wraps long lines, false adds a scrollbar, null overflows
transformersarrayShiki transformers to mutate the generated HTML/tokens

Dual light and dark themes

Pass a themes object to emit CSS variables for both modes in a single build. Astro outputs --shiki-light and --shiki-dark custom properties; you toggle between them with a tiny bit of CSS keyed off your color-scheme switch.

// astro.config.mjs
import { defineConfig } from "astro/config";

export default defineConfig({
  markdown: {
    shikiConfig: {
      themes: {
        light: "github-light",
        dark: "github-dark",
      },
    },
  },
});
---
// src/components/ThemeStyles.astro — switch Shiki vars by data attribute
---
<style is:global>
  html[data-theme="dark"] .astro-code,
  html[data-theme="dark"] .astro-code span {
    color: var(--shiki-dark) !important;
    background-color: var(--shiki-dark-bg) !important;
  }
</style>

Adding languages and transformers

If you use a niche language not in the default bundle, register it under langs. You can also wire in Shiki transformers to add features like highlighted lines, diff markers, or focus blocks — these run at build time and decorate the generated markup.

// astro.config.mjs
import { defineConfig } from "astro/config";
import { transformerNotationDiff } from "@shikijs/transformers";

export default defineConfig({
  markdown: {
    shikiConfig: {
      theme: "nord",
      langs: ["astro", "prisma"],
      transformers: [transformerNotationDiff()],
    },
  },
});

With transformerNotationDiff, a comment like // [!code ++] on a line marks it as an addition and // [!code --] marks a removal, letting you render change diffs inside ordinary code blocks.

Switching to Prism

Astro also bundles support for Prism if you prefer its ecosystem or already maintain a Prism theme. Set markdown.syntaxHighlight to "prism". Unlike Shiki, Prism does not inject inline styles — you must import a Prism stylesheet yourself so the token classes have colors.

// astro.config.mjs
import { defineConfig } from "astro/config";

export default defineConfig({
  markdown: {
    syntaxHighlight: "prism",
  },
});
---
// src/layouts/BaseLayout.astro — load a Prism theme stylesheet
import "prismjs/themes/prism-tomorrow.css";
---
<slot />

You can disable highlighting entirely with syntaxHighlight: false, which is occasionally useful when an external tool or a custom Rehype plugin owns the code formatting.

syntaxHighlight valueHighlighterStyling source
"shiki" (default)ShikiInline styles, no extra CSS needed
"prism"PrismRequires a Prism theme stylesheet
falseNonePlain <code>, style it yourself

Shiki and Prism are mutually exclusive per build. Pick one highlighter for the whole site rather than mixing them, since their class names and styling models differ.

Best practices

  • Always tag fenced blocks with the correct language so Shiki tokenizes accurately; an unlabeled block renders as plain text.
  • Prefer the default Shiki highlighter — its inline styles avoid a flash of unstyled code and need no extra CSS bundle.
  • Use a themes light/dark pair instead of two separate builds so a single deployment serves both color schemes.
  • Reach for transformers (diff, highlighted lines, focus) before writing custom Rehype code that re-parses your blocks.
  • If you choose Prism, remember to import a Prism theme stylesheet, or your code will render unstyled.
  • Rebuild after changing any shikiConfig value — highlighting is static and will not update existing pages on its own.
Last updated June 14, 2026
Was this helpful?