codeblock
What it does
Renders the code blocks EmDash already stores using Shiki syntax highlighting. Highlighting runs server-side at render time, so the browser gets plain coloured HTML and no JavaScript. Nothing is rewritten on save - change the theme and every existing post picks it up on the next render.
codeblock is a Native plugin - it needs Shiki, which is a Node dependency, so it can't be sandboxed or published to the marketplace. It registers no new editor block type; it renders the codeblock EmDash already has.
Install
npm install @plugdash/codeblockRegister
Open astro.config.mjs at the root of your Astro project. Import the plugin at the top, then add it to the pluginsarray of the emdash() integration - the one that sits inside Astro's top-level integrations array:
import { defineConfig } from "astro/config"
import emdash from "emdash/astro"
import { codeblockPlugin } from "@plugdash/codeblock"
export default defineConfig({
integrations: [
emdash({
// native - must be in plugins, not sandboxed
plugins: [
codeblockPlugin({ theme: "github-dark", lineNumbers: true }),
],
}),
],
})Configuration
All options are optional. Defaults cover most sites out of the box.
| Option | Type | Default | Description |
|---|---|---|---|
theme | string | github-dark | Shiki theme name. Unknown names fall back to the default. |
lightTheme | string | - | Second theme for light mode, emitted as CSS variables. |
langs | string[] | common set | Languages loaded up front. Anything else loads on first use. |
lineNumbers | boolean | false | Render line numbers in the gutter. |
Preloaded by default: TypeScript, JavaScript, Python, Go, Rust, Shell, JSON, YAML, Markdown, HTML, CSS, SQL.
Add the component
The component is auto-wired into <PortableText>through componentsEntry - no manual block mapping needed. Once the plugin is registered, existing code blocks start rendering highlighted.
For direct usage outside Portable Text, import the component:
---
import CodeBlock from "@plugdash/codeblock/CodeBlock.astro"
---
<CodeBlock code={source} language="python" filename="app.py" lineNumbers />Live demo
Rendered by the real CodeBlock.astro on this page. The last one uses a language Shiki doesn't know, so it falls back to plaintext.
import { getEmDashCollection } from "emdash"
export async function latestPosts(limit = 5) {
const { entries } = await getEmDashCollection("posts")
return entries
.filter((p) => p.data.status === "published")
.slice(0, limit)
}def reading_time(words: int, wpm: int = 238) -> int:
return max(1, round(words / wpm))no grammar for this one, so it falls back to plaintextExamples
A site that wants light and dark themes to switch with the reader's system preference passes both:
codeblockPlugin({
theme: "github-dark",
lightTheme: "github-light",
})In the editor, an author picks a language on the code block and writes the source. Nothing about the stored content changes - EmDash keeps it as a plain code block in the Portable Text body:
{
"_type": "code",
"code": "def greet(name):\n return f\"hi {name}\"",
"language": "python",
"filename": "greet.py"
}On the site, that block renders as a header bar with the filename and language, followed by Shiki's highlighted markup - a<pre class="shiki github-dark"> wrapper with a coloured <span> per token. If lineNumbersis on, a numbered gutter runs down the left edge, built from a CSS counter so the numbers never end up in a reader's copy-paste.
Props
| prop | type | default | description |
|---|---|---|---|
code | string | Source to highlight - required to render | |
language | string | Language name. Unknown ones render as plaintext. | |
filename | string | Shown in the header bar | |
theme | string | "github-dark" | Shiki theme name |
lightTheme | string | Second theme for light mode | |
lineNumbers | boolean | false | Show the line number gutter |
class | string | Additional CSS class | |
node | object | Block data from PortableText auto-wiring |
CSS custom properties
| token | description |
|---|---|
--plugdash-codeblock-radius | Border radius (default 6px) |
--plugdash-codeblock-padding | Padding around the code (default 1rem) |
--plugdash-codeblock-size | Font size (default 0.875rem) |
--plugdash-codeblock-line-height | Line height (default 1.6) |
--plugdash-codeblock-font | Font family (default monospace stack) |
--plugdash-codeblock-header-padding | Header bar padding (default 0.5rem 1rem) |
--plugdash-codeblock-header-bg | Header bar background (default #1a1a1a) |
--plugdash-codeblock-header-color | Header bar text (default #9ca3af) |
--plugdash-codeblock-gutter-width | Line number column width (default 2rem) |
--plugdash-codeblock-gutter-color | Line number colour (default #6b7280) |
Edge cases
- Unknown or missing language renders as plaintext instead of throwing.
text,txt,plain,plaintext, andnoneall mean plaintext.- Empty code renders an empty
<pre>block. - Code over 10,000 lines is cut off with a trailing truncation notice.
- Unknown theme names fall back to
github-dark.
No copy button, line highlighting, or diff view. No client-side highlighting, so no runtime theme or language switching - pick a theme (or a light/dark pair) at registration time.
For agents
After installing and registering the plugin, no further setup is needed for rendering - block components are auto-wired into<PortableText> and existing codeblocks start rendering highlighted immediately. To verify, publish a post containing a code block with a language set, then view the page source: it should contain class="shiki github-dark"(or whatever theme was configured) with coloured styleattributes on the token spans. If nothing renders differently, confirm the plugin is in the plugins array, notsandboxed - native plugins cannot run sandboxed.