Expressive Code in your Astro project.
I also use the line numbers plugin, which is a separate package.
#installs expressive code and line numbers plugin
npm i astro-expressive-code @expressive-code/plugin-line-numbers
Obviously, the most basic way to show code in Astro, is to just write it in a code block, in the mdx
file where the content lives and define the language after the ```
.
which then renders as:
This is all well and good, but what if you want to show one of the files that actually lives in your site and don’t want to copy and paste it into the mdx
file?
Let’s look at one of my example files from my The Math of Calligraphy Grids article .
I made a file for my DotGrid Example, which looks like this
Show SVG Code style = " max-width: 100%; border: 1px solid red "
xmlns = " http://www.w3.org/2000/svg "
< title >dot Grid with pattern</ title >
id = " horizontal-dotted-line "
fill = " url(#horizontal-dotted-line) "
Now, if I wanted to display the code of that, I’d import the Code
component as well as the Components raw
content and render it like this.
import ExampleDotCode from " @components/article-content/svg/ExampleDot.astro?raw " ;
import { Code } from " astro-expressive-code/components " ;
< Code code = { ExampleDotCode } lang = " astro " title = " ExampleDot.astro " />
Which renders as:
import Example from " ./Example.astro " ;
import SVG from " ./SVG.astro " ;
const strokeColor = " currentColor " ;
const gridYStart = ( docHeight % cellSize ) / 2 ;
const gridXStart = ( docWidth % cellSize ) / 2 ;
const patternHeight = ( cellSize / docHeight ) * 100 ;
title = " dot Grid with pattern "
id = " horizontal-dotted-line "
viewBox ={ `0 0 ${ docWidth } ${ dotSize } ` }
height ={ ` ${ patternHeight } %` }
stroke-dasharray ={ `0, ${ cellSize } ` }
fill = " url(#horizontal-dotted-line) "
This is great, but what if I just wanted to get a part of that file?
For that, I wrote a new Astro Component, which takes a code string as well as a partial
prop.
In that prop, I can specify wheter or not not I want to show the frontmatter, the template, or a specific part of the file.
import { Code } from " astro-expressive-code/components " ;
type Partial = ' frontmatter ' | ' template ' | { lineStart : number ; lineEnd : number };
const { codeTitle , rawFile = '' , lang , partial } = Astro . props ;
const title = codeTitle ? ` ${ codeTitle } . ${ lang } ` : undefined ;
const lines = rawFile . split ( " \n " );
const getCodeString = ( lines : string [], partial : Partial ) => {
if ( partial === ' frontmatter ' ) {
const start = lines . findIndex (( line ) => line === ' --- ' );
const end = lines . findIndex (( line , index ) => index > start && line === ' --- ' ) + 1 ;
return lines . slice ( start , end ). join ( ' \n ' );
if ( partial === ' template ' ) {
const start = lines . findIndex (( line ) => line === ' --- ' );
const end = lines . findIndex (( line , index ) => index > start && line === ' --- ' );
return lines . slice ( end + 1 ). join ( ' \n ' );
if ( typeof partial === ' object ' ) {
return lines . slice ( partial . lineStart , partial . lineEnd ). join ( ' \n ' );
const codeString = getCodeString ( lines , partial );
So if, I wanted to just show the SVG
part of the ExampleDot
file, I’d use the PartialFile
component like this:
< PartialFile codeTitle = " frontmatter " rawFile = { ExampleDotCode } lang = " astro " partial = " frontmatter " />
< PartialFile codeTitle = " template " rawFile = { ExampleDotCode } lang = " astro " partial = " template " />
< PartialFile codeTitle = " partial " rawFile = { ExampleDotCode } lang = " astro " partial = {{lineStart : 40 , lineEnd : 47 }} />
Which renders like this:
import Example from " ./Example.astro " ;
import SVG from " ./SVG.astro " ;
const strokeColor = " currentColor " ;
const gridYStart = ( docHeight % cellSize ) / 2 ;
const gridXStart = ( docWidth % cellSize ) / 2 ;
const patternHeight = ( cellSize / docHeight ) * 100 ;
title = " dot Grid with pattern "
id = " horizontal-dotted-line "
viewBox ={ `0 0 ${ docWidth } ${ dotSize } ` }
height ={ ` ${ patternHeight } %` }
stroke-dasharray ={ `0, ${ cellSize } ` }
fill = " url(#horizontal-dotted-line) "
fill = " url(#horizontal-dotted-line) "
Okay, so this is if we’re passing in files in the original syntax. But what if we want to get the resulting HTML of that template?
This is where the ShowCode
component comes in.
Let’s have a look at how the Example file works first though.
If you go back to the preview of the Dot, you see there is a summary with Show SVG Code, which expands the SVG code.
In the Example.astro
Component, the Code is actually added twice.
import ShowCode from " @components/ShowCode.astro " ;
< div class = " svg-example " >
toggleText = " Show SVG Code "
So, once is when we render the actual SVG and the second is then the Code we’d like to render.
Since that HTML is rendered and potentially no longer nicely formatted, I personally like to use the prettier
package to format the HTML.
We can access the slot content which is rendered and then run prettier over it and then get a code string from it.
Astro may add some script tags or data-astro attributes which we don’t want, so we can use a quick replace to get rid of those.
And that’s all the magic there is to it:
const { codeTitle = " rendered-example.html " , lang = " html " , toggleText = " Show Code " } = Astro . props ;
import * as prettier from " prettier " ;
import { Code } from " astro-expressive-code/components " ;
import Icon from " @components/Icon.astro " ;
// note no <slot/> is placed in content, we manually add the formatted code.
const defaultSlotContent = await Astro . slots . render ( " default " );
// slot content will add script tags and astro-ids if there is custom css, we don't want these added
const scriptPattern = / <script [ ^ > ] * > [ \s\S ] *? < \/ script> / g ;
const astroPattern = / ( \s * data-astro-cid- [ ^ \s "'`> ] + ) / g ;
const modifiedHtmlString = await defaultSlotContent . replace ( scriptPattern , "" ). replace ( astroPattern , "" );
const codeString = await prettier . format ( modifiedHtmlString , {
htmlWhitespaceSensitivity : " ignore " ,
singleAttributePerLine : true ,
< details class = " doc-show-code " >
< summary class = " doc-show-code__summary t-mono " >
Astro . slots . has ( " default " ) && (
title = { ` ${ codeTitle } . ${ lang } ` }
< style lang = " scss " is:global >
border : 1 px solid var ( --sys-c-line );
transition : 300 ms border ease ;
border-color : var ( --sys-c-interactive );
transition : 300 ms transform ease ;
transform : rotate ( 180 deg );
Displaying Code Blocks and keeping them in sync was always a big hassle in previous setups. So this is a huge win for me.
I hope this helps you in your code block endeavours as well, even if you aren’t using Astro, I hope you can take some inspiration from this and apply it to your own setup.
Results for
Load More Results