screenshot of a colourful Javascript code block on a screen

Supercharged syntax highlighting with Astro & Rehype Pretty Code

Want to add next level syntax highlighting to your Astro blog? We take a look at Rehype Pretty Code - a simple way to add beautifully highlighted code to your website.

Astro is great for building content focussed websites, in particular developer blogs. When it comes to displaying code, the built-in Shiki highlighter does a good job at displaying your code nicely. But what if you crave some next-level features that go beyond simply highlighting keywords? That’s where Rehype Pretty Code comes in. You get all these awesome features in your code blocks:

  • Line numbers
  • Word & line highlighting
  • Titles
  • Captions
  • Light & dark themes

Let’s take a look at getting it working in an Astro blog.

Setup

Firstly, initialise a new Astro website (jump to here if you have an Astro project already).

yarn create astro
yarn create astro

Call your project whatever you want, but be sure to select a blog template to get things going quickly. Select the defaults for the rest of the setup and then open up the project in your IDE of choice.

Install additional dependencies

We’re going to need to install a few more dependencies. Run this command in your terminal:

yarn add rehype-pretty-code shiki
yarn add rehype-pretty-code shiki

RPC is a rehype plugin powered by Shiki to highlight code blocks. It also has the additional benefit of running at build time so you get all these fancy features without any extra Javascript added to your pages. That keeps things moving pretty fast! 🏃🏾‍♂️💨

Modify Astro config

With all our dependencies installed, we can now modify our Astro config to include the new rehype plugin:

astro.config.mjs
import { defineConfig } from "astro/config";
import mdx from "@astrojs/mdx";
import sitemap from "@astrojs/sitemap";
import rehypePrettyCode from "rehype-pretty-code";
 
// https://astro.build/config
export default defineConfig({
  site: "https://example.com",
  integrations: [mdx(), sitemap()],
  markdown: {
    rehypePlugins: [rehypePrettyCode],
  },
});
astro.config.mjs
import { defineConfig } from "astro/config";
import mdx from "@astrojs/mdx";
import sitemap from "@astrojs/sitemap";
import rehypePrettyCode from "rehype-pretty-code";
 
// https://astro.build/config
export default defineConfig({
  site: "https://example.com",
  integrations: [mdx(), sitemap()],
  markdown: {
    rehypePlugins: [rehypePrettyCode],
  },
});

If you run yarn dev now in your terminal and navigate to the “Markdown Style Guide” page you’ll see some beautifully styled code blocks, but we’re not quite there yet. As I mentioned earlier, Astro has built in syntax highlighting that we need to disable in order to get to the new stuff, so let’s go ahead and disable that:

export default defineConfig({
  site: "https://example.com",
  integrations: [mdx(), sitemap()],
  markdown: {
    rehypePlugins: [rehypePrettyCode],
+   syntaxHighlight: false,
  },
});
export default defineConfig({
  site: "https://example.com",
  integrations: [mdx(), sitemap()],
  markdown: {
    rehypePlugins: [rehypePrettyCode],
+   syntaxHighlight: false,
  },
});

Now the setup is complete, we’re ready to take our code blocks to the next level! (I think that’s the nerdiest sentence I’ve ever written in my life.)

Using our new powers 🦸‍♂️

RPC uses meta strings to configure the code blocks to add in details like titles, captions, highlighted lines etc. The syntax (for want of a better phrase) is very simple, and full details can be found on their website. We’ll just go over some basics here. Feel free to add these examples to the same “Markdown Style Guide” page we viewed earlier to see them in action.

```js title="/src/app/fetchMovie.js"
const movies = await fetchMovies();
```
Adding a title
```js title="/src/app/fetchMovie.js"
const movies = await fetchMovies();
```
Adding a title
```js caption="Fetch movies from our server"
export async function fetchMovies() {
  const response = await fetch("http://example.com/movies.json");
  return await response.json();
}
```
Adding a caption
```js caption="Fetch movies from our server"
export async function fetchMovies() {
  const response = await fetch("http://example.com/movies.json");
  return await response.json();
}
```
Adding a caption
```js {2-4}
const movies = await fetchMovies();
movies.map((movie, i) => {
  console.log(movie.title);
});
```
Adding highlighted lines
```js {2-4}
const movies = await fetchMovies();
movies.map((movie, i) => {
  console.log(movie.title);
});
```
Adding highlighted lines
```js /movie.title/
const movies = await fetchMovies();
movies.map((movie, i) => {
  console.log(movie.title);
});
```
Highlight specific words & phrases (uses regex)
```js /movie.title/
const movies = await fetchMovies();
movies.map((movie, i) => {
  console.log(movie.title);
});
```
Highlight specific words & phrases (uses regex)
```js showLineNumbers
const movies = await fetchMovies();
movies.map((movie, i) => {
  console.log(movie.title);
});
```
Adding line numbers
```js showLineNumbers
const movies = await fetchMovies();
movies.map((movie, i) => {
  console.log(movie.title);
});
```
Adding line numbers

Adding styles

Now if you head over to the markdown style guide page, you might not notice much difference. That’s because RPC doesn’t add styles to these “supercharged” elements. That’s for you to do, so you remain in full control and can apply whatever styles you want. We can target all these elements using the attributes added to our code blocks.

Let’s make a new posts.css file in the styles directory of our project. It’s good to separate so we’re not clogging up other pages with unnecessary CSS. We’ll need to import this into our blog post [slug] page as well.

src/pages/blog/[...slug].astro
import "../../styles/posts.css";
src/pages/blog/[...slug].astro
import "../../styles/posts.css";
src/styles/posts.css
/* Styling titles */
[data-rehype-pretty-code-title] {
  background-color: #6f4870;
  border-top-left-radius: 0.5rem;
  border-top-right-radius: 0.5rem;
  color: #eee;
  margin-bottom: -1.5rem;
  padding: 0.5rem;
  text-align: center;
}
 
/* Styling captions */
[data-rehype-pretty-code-caption] {
  text-align: center;
  font-size: 1rem;
  line-height: 1.25rem;
}
 
/* Styling highlighted blocks */
[data-highlighted-line],
[data-highlighted-chars] {
  background-color: rgba(49, 173, 113, 0.4);
}
 
[data-highlighted-line] {
  border-left-width: 4px;
  border-left-style: solid;
  border-left-color: #6f4870;
}
 
/* Styling line numbers */
[data-rehype-pretty-code-fragment] pre > code {
  display: grid;
  counter-reset: line;
}
 
[data-line-numbers] > [data-line]::before {
  counter-increment: line;
  content: counter(line);
  display: inline-block;
  width: 1rem;
  margin-right: 2rem;
  text-align: right;
  color: gray;
}
src/styles/posts.css
/* Styling titles */
[data-rehype-pretty-code-title] {
  background-color: #6f4870;
  border-top-left-radius: 0.5rem;
  border-top-right-radius: 0.5rem;
  color: #eee;
  margin-bottom: -1.5rem;
  padding: 0.5rem;
  text-align: center;
}
 
/* Styling captions */
[data-rehype-pretty-code-caption] {
  text-align: center;
  font-size: 1rem;
  line-height: 1.25rem;
}
 
/* Styling highlighted blocks */
[data-highlighted-line],
[data-highlighted-chars] {
  background-color: rgba(49, 173, 113, 0.4);
}
 
[data-highlighted-line] {
  border-left-width: 4px;
  border-left-style: solid;
  border-left-color: #6f4870;
}
 
/* Styling line numbers */
[data-rehype-pretty-code-fragment] pre > code {
  display: grid;
  counter-reset: line;
}
 
[data-line-numbers] > [data-line]::before {
  counter-increment: line;
  content: counter(line);
  display: inline-block;
  width: 1rem;
  margin-right: 2rem;
  text-align: right;
  color: gray;
}

Almost there! Finally, let’s add some base styles to get everything looking a bit neater.

/* Neaten everything up */
[data-rehype-pretty-code-fragment] pre {
  padding-left: 0;
  padding-right: 0;
  overflow-x: scroll;
}
 
[data-line] {
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}
 
[data-rehype-pretty-code-title] + pre {
  border-top-right-radius: 0px;
  border-top-left-radius: 0px;
}
/* Neaten everything up */
[data-rehype-pretty-code-fragment] pre {
  padding-left: 0;
  padding-right: 0;
  overflow-x: scroll;
}
 
[data-line] {
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}
 
[data-rehype-pretty-code-title] + pre {
  border-top-right-radius: 0px;
  border-top-left-radius: 0px;
}

Nice. We’ve added a lot of stuff already and now our code blocks are looking great. But what about if you’ve got a site that supports multiple themes?

Light & dark themes

RPC makes it easy to configure separate light and dark themes. It requires a minor change in our Astro config and a little more CSS. Add the following underneath your imports in the Astro config file:

astro.config.mjs
/** @type {import('rehype-pretty-code').Options} */
const options = {
  // https://rehype-pretty-code.netlify.app
  theme: {
    dark: "slack-dark",
    light: "slack-ochin",
  },
};
astro.config.mjs
/** @type {import('rehype-pretty-code').Options} */
const options = {
  // https://rehype-pretty-code.netlify.app
  theme: {
    dark: "slack-dark",
    light: "slack-ochin",
  },
};

And make the following edit where you’ve included the rehype plugin:

markdown: {
-  rehypePlugins: [rehypePrettyCode],
+  rehypePlugins: [[rehypePrettyCode, options]],
  syntaxHighlight: false,
  remarkPlugins: [a11yEmoji],
},
markdown: {
-  rehypePlugins: [rehypePrettyCode],
+  rehypePlugins: [[rehypePrettyCode, options]],
  syntaxHighlight: false,
  remarkPlugins: [a11yEmoji],
},

Finally, we just need to add this CSS to hide/show the appropriate theme depending on the system settings:

src/styles/posts.css
/* Light/dark themes*/
@media (prefers-color-scheme: dark) {
  *[data-theme="light"] {
    display: none;
  }
}
 
@media (prefers-color-scheme: light) {
  *[data-theme="dark"] {
    display: none;
  }
}
 
.slack-ochin {
  background-color: #eee !important;
}
src/styles/posts.css
/* Light/dark themes*/
@media (prefers-color-scheme: dark) {
  *[data-theme="light"] {
    display: none;
  }
}
 
@media (prefers-color-scheme: light) {
  *[data-theme="dark"] {
    display: none;
  }
}
 
.slack-ochin {
  background-color: #eee !important;
}

Alternatives

Not gonna lie, that’s a lot of effort to get decent syntax highlighting working on our website, but now we can reap the benefits! 💪🏾 You might prefer another integration, however. Expressive Code essentially does the same thing and it even has support for copying code blocks to your clipboard, but no light/dark mode toggling baked in. I also had to use the remark plugin rather than the Astro one in order to get it working. Your mileage may vary!

There’s also Prism which shares a lot of the same features, but will add some Javascript bulk to your pages. And that’s before you’ve even added more plugins for line-highlighting and other features!

If you’ve got any other suggestions, I’d love to hear it!