Add Syntax Highlighting to Code Blocks in the Blog's CSS
A clear guide on how to implement syntax highlighting for code blocks in a blog using CSS and client-side highlighting libraries.
Hello! This is Pan.
In this post I summarize what I did to add syntax highlighting to code blocks on my blog.
The build process only formats and outputs the structure of code blocks; the visual styling is handled on the client side via CSS and a highlighting library. Below I describe the actual implementation I confirmed and the CSS and setup steps I added or adjusted, with concrete examples.
Build overview
The custom renderer in the build script outputs Markdown code blocks as the following HTML structure:
<figure class="code-block" data-lang="xx">: wraps the entire code blockdiv.code-header: displays a language label at the top- The intended build output shape is
<pre><code class="language-xx">...</code></pre>
That's it.
For chart outputs using Mermaid, emit something like:
<div class="mermaid-wrapper"><div class="mermaid">...</div></div>
and let the client initialize Mermaid to render it.
In short, the build side's responsibility is to attach "which language" and "visual hooks" — actual coloring is delegated to a client-side library.
This design has the following benefits:
- Lighter site builds (no tokenization or server-side highlighting)
- Easier client-side theme switching
- Easier handling of blocks that require client rendering (like Mermaid)
Steps I actually took (overview)
- Create CSS that matches the HTML structure emitted (
.code-blockfamily of rules). - Add Prism.js (or highlight.js) to highlight
<code class="language-...">elements. - If there are Mermaid blocks, call
mermaid.initialize()on the client to render them. - Implement line numbers, line highlighting, and dark-mode support in CSS (and add JS helpers if needed).
Below I include the sample CSS and Prism setup examples I used in the article. Adjust them to fit your project's conventions.
Sample CSS
An example I actually used. Change colors and fonts as you like.
/* Blog: base styles for code blocks */
figure.code-block {
margin: 1.2em 0;
border-radius: 8px;
overflow: hidden;
background: #0b0f14; /* example dark background. Override for light theme via another class */
color: #e6eef6;
box-shadow: 0 1px 0 rgba(0,0,0,0.15);
font-size: 0.9rem;
}
figure.code-block .code-header {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: linear-gradient(90deg, rgba(255,255,255,0.03), rgba(255,255,255,0.01));
border-bottom: 1px solid rgba(255,255,255,0.04);
}
figure.code-block .lang-icon { display: inline-flex; align-items: center; }
figure.code-block .lang-label { font-weight: 600; font-size: 0.85rem; color: #cfe3ff; }
figure.code-block pre {
margin: 0;
padding: 12px;
overflow: auto;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono", "Courier New", monospace;
line-height: 1.6;
background: transparent;
color: inherit;
}
/* Prepare for line numbers or line highlighting (can be used with Prism plugins) */
pre[data-line] {
position: relative;
}
/* Prism token colors are intended to be overridden per theme */
.token.comment { color: #6a737d; }
.token.keyword { color: #ff7b72; font-weight: 600; }
.token.function { color: #79b8ff; }Loading Prism.js via CDN (example)
For quick testing you can load from a CDN. For production, prefer bundling or hosting locally.
<!-- Prism CSS and JS (example: CDN) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@latest/themes/prism-tomorrow.css" />
<script src="https://cdn.jsdelivr.net/npm/prismjs@latest/prism.min.js"></script>
<!-- Load language components you need -->
<script src="https://cdn.jsdelivr.net/npm/prismjs@latest/components/prism-javascript.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@latest/components/prism-python.min.js"></script>
<!-- Prism plugins (line numbers, etc.) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@latest/plugins/line-numbers/prism-line-numbers.css" />
<script src="https://cdn.jsdelivr.net/npm/prismjs@latest/plugins/line-numbers/prism-line-numbers.min.js"></script>
<!-- Initialization is usually not required (Prism often runs on DOMContentLoaded automatically) -->Note: tools/build-blog.js emits <code class="language-xxx">, so loading the corresponding Prism language components should color them automatically.
Handling Mermaid
Because the build emits a dedicated wrapper for Mermaid blocks, initialize Mermaid on the client.
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<script>
mermaid.initialize({ startOnLoad: false, theme: 'base' });
// build-blog.js outputs .mermaid elements, so after DOM is ready initialize each
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.mermaid').forEach((el) => {
try {
mermaid.parse(el.textContent);
// Simple render call
mermaid.init(undefined, el);
} catch (e) {
console.warn('mermaid parse failed', e);
}
});
});
</script>Extra improvements worth adding
- Line numbers
- Line highlighting
- Copy button
- Light / dark theme switching
- Server-side prerendering: since highlighting is client-side in this approach, consider running Prism during build to produce highlighted HTML if you want better first-paint performance or SEO.
Summary
- Emit well-structured code blocks (labels, classes, data-lang) from the build, and let CSS + a client-side highlighting library handle coloring and fine visual adjustments. This makes implementation straightforward and easier to maintain.
- For small blogs, using Prism and Mermaid from a CDN is a fast way to get things working; after testing, bundle them into your production build.
- You can easily add user-friendly features like line numbers, copy buttons, and theme switching.
Thanks for reading! If you want, I can add a concrete CSS file to the project or explain how to integrate Prism into your build pipeline. Let me know which you'd prefer.
Loading comments...