Shaku - code annotation made easy
Shaku makes it super easy to annotate code with special directives in comments.
A very basic example
const Hello = "World!"// ^// [Hello World!]
const Hello = "World!"// ^// [Hello World!]
Above code is already self-explanatory, but with Shaku it is rendered into sth even better.
const Hello = "World!"Hello World!
const Hello = "World!"Hello World!
Now code and annotation are visually separated, super cool to explain code, right?
Usage
Choose the right tool for your use case.
- shaku-code-annotate-core - Shaku parser that parses the syntax in the comments. Tokenizer not included.
- shaku-code-annotate-shiki - Shaku code annotation based on the tokenizer from shiki. You can use this package directly in node.js or browser.
- shaku-code-annotate-sugar-high - Enable Shaku on the lightweight syntax highlighter - Sugar High.
- remark-shaku-code-annotate - plugin for remark to easily integrate Shaku in Markdown/MDX(Astro .etc)
- marked-shaku-code-annotate - a plugin for another markdown engine: marked
Also some demos you can refer to:
Or you can just inspect the source code of this website - CodeBlock.tsx
, CodePreviewRemark.tsx
or CodePreviewMarked.tsx
Supported Languages
Shaku on Shiki supports most languages that are supported by Shiki. You can find the +150 languages from Shaku Snippet.
Styling
Shaku renders code into a <pre />
with class.shaku
, and shaku elements have class names prefixed with .shaku
, you can use the CSS from this website and adapt to your needs.
The class names for each Shaku element will be explained in Syntax section.
Dark mode support
You can render multiple themes by setting themes
in the Shaku plugins, the theme lang is put on the <pre>
tag. Thus we can control the visibility by CSS
const marked = new Marked();marked.use(markedShakuCodeAnnotate({themes: ["github-light", "github-dark"],langs: ["javascript", "css", "jsx", "html", "typescript", "tsx"],}));
const marked = new Marked();marked.use(markedShakuCodeAnnotate({themes: ["github-light", "github-dark"],langs: ["javascript", "css", "jsx", "html", "typescript", "tsx"],}));
pre.shaku.github-dark {display: none;}@media (prefers-color-scheme: dark) {pre.shaku.github-dark {display: block;}pre.shaku.github-light {display: none;}}
pre.shaku.github-dark {display: none;}@media (prefers-color-scheme: dark) {pre.shaku.github-dark {display: block;}pre.shaku.github-light {display: none;}}
Syntax
You can also try out the syntax on Shaku Playground or Shaku Snippet.
Callout
const blog = "https://jser.dev"JSer.dev is the homepage for JSer.
Check it out! jser.dev
// This is a normal comment
const blog = "https://jser.dev"JSer.dev is the homepage for JSer.
Check it out! jser.dev
// This is a normal comment
Place ^
for the arrow, and []
for the text, you can also enable basic markdown support.
const blog = "https://jser.dev"// ^// [JSer.dev is the *homepage* for JSer.]// [Check it out! [jser.dev](https://jser.dev)]// This is a normal comment
const blog = "https://jser.dev"// ^// [JSer.dev is the *homepage* for JSer.]// [Check it out! [jser.dev](https://jser.dev)]// This is a normal comment
.shaku-callout
and .shaku-callout-arrow
are used to style callout.
.shaku-callout {background-color: var(--color-shaku-callout-light, #0685ce);color: #fff;padding: 0.5em 1ch;position: relative;margin: 0.5em 0 0 -0.2ch;display: inline-block;border-radius: 2px;}.shaku-callout-arrow {width: 1ch;height: 1ch;display: inline-block;background-color: var(--color-shaku-callout-light, #0685ce);position: absolute;top: -0.5ch;transform: rotate(45deg);margin-left: 0.2ch;}
.shaku-callout {background-color: var(--color-shaku-callout-light, #0685ce);color: #fff;padding: 0.5em 1ch;position: relative;margin: 0.5em 0 0 -0.2ch;display: inline-block;border-radius: 2px;}.shaku-callout-arrow {width: 1ch;height: 1ch;display: inline-block;background-color: var(--color-shaku-callout-light, #0685ce);position: absolute;top: -0.5ch;transform: rotate(45deg);margin-left: 0.2ch;}
Underlines
// This is normal comments from source code.const blog = "https://jser.dev"const blog = "jser.dev"--------const blog = "jser.dev"~~~~~~~~const blog = "jser.dev"........
// This is normal comments from source code.const blog = "https://jser.dev"const blog = "jser.dev"--------const blog = "jser.dev"~~~~~~~~const blog = "jser.dev"........
Simply use -----
, ....
, ~~~~~
for underlines.
// This is normal comments from source code.const blog = "https://jser.dev"// ----------------// [JSer.dev is the **homepage** for JSer.]// [Check it out! [jser.dev](https://jser.dev)]const blog = "jser.dev"// --------const blog = "jser.dev"// ~~~~~~~~const blog = "jser.dev"// ........
// This is normal comments from source code.const blog = "https://jser.dev"// ----------------// [JSer.dev is the **homepage** for JSer.]// [Check it out! [jser.dev](https://jser.dev)]const blog = "jser.dev"// --------const blog = "jser.dev"// ~~~~~~~~const blog = "jser.dev"// ........
.shaku-underline
is the base style, .shaku-underline-wavy
, .shaku-underline-solid
and .shaku-underline-dotted
are for the variations.
.shaku-underline {padding: 0 1ch;position: relative;display: block;border-radius: 3px;color: var(--color-shaku-underline-light, red);margin: 0;width: min-content;}.shaku-underline-line {line-height: 0;top: 0.5em;position: absolute;text-decoration-line: overline;text-decoration-color: var(--color-shaku-underline-light, red);color: transparent;pointer-events: none;user-select: none;text-decoration-thickness: 2px;}.shaku-underline-wavy > .shaku-underline-line {text-decoration-style: wavy;top: 0.7em;}.shaku-underline-solid > .shaku-underline-line {text-decoration-style: solid;}.shaku-underline-dotted > .shaku-underline-line {text-decoration-style: dotted;}
.shaku-underline {padding: 0 1ch;position: relative;display: block;border-radius: 3px;color: var(--color-shaku-underline-light, red);margin: 0;width: min-content;}.shaku-underline-line {line-height: 0;top: 0.5em;position: absolute;text-decoration-line: overline;text-decoration-color: var(--color-shaku-underline-light, red);color: transparent;pointer-events: none;user-select: none;text-decoration-thickness: 2px;}.shaku-underline-wavy > .shaku-underline-line {text-decoration-style: wavy;top: 0.7em;}.shaku-underline-solid > .shaku-underline-line {text-decoration-style: solid;}.shaku-underline-dotted > .shaku-underline-line {text-decoration-style: dotted;}
Highlight Lines
function useSomeEffect({blog}) {useEffect(() => {// do some stuffreturn () => {location.href = 'https://jser.dev'}}, [blog])}
function useSomeEffect({blog}) {useEffect(() => {// do some stuffreturn () => {location.href = 'https://jser.dev'}}, [blog])}
Use @highlight
to highlight next line, append start
/end
(or v
/ ^
) to mark multiple lines.
// @highlightfunction useSomeEffect({blog}) {useEffect(() => {// do some stuff// @highlight startreturn () => {location.href = 'https://jser.dev'}// @highlight end}, [blog])}
// @highlightfunction useSomeEffect({blog}) {useEffect(() => {// do some stuff// @highlight startreturn () => {location.href = 'https://jser.dev'}// @highlight end}, [blog])}
.shaku .line.highlight
could be used to set highlight style.
pre.shaku .line.highlight {background-color: var(--color-shaku-highlight-light,color-mix(in srgb, rgb(5, 118, 149) 15%, #fff));display: block;}
pre.shaku .line.highlight {background-color: var(--color-shaku-highlight-light,color-mix(in srgb, rgb(5, 118, 149) 15%, #fff));display: block;}
Highlight Words(inline)
function useSomeEffect({blog}) {useEffect(() => {return () => {location.href = 'https://jser.dev'}}, [blog])}
function useSomeEffect({blog}) {useEffect(() => {return () => {location.href = 'https://jser.dev'}}, [blog])}
(
and )
are used to mark the selection of next line. optional id inside could be used to map to different color.
// ( )function useSomeEffect({blog}) {//( r )useEffect(() => {return () => {//( g ) ( b )location.href = 'https://jser.dev'}}, [blog])}
// ( )function useSomeEffect({blog}) {//( r )useEffect(() => {return () => {//( g ) ( b )location.href = 'https://jser.dev'}}, [blog])}
.shaku-inline-highlight
is used to style the inline blocks, target specific blocks with the id you set - [data-id=*]
.
.shaku-inline-highlight {background-color: #fa05f230;border-bottom: 2px solid rgb(235, 4, 158);margin: 0 1px;border-radius: 3px;padding: 0 3px;}.shaku-inline-highlight[data-id="r"] {background-color: #fa05f230;border-bottom: 2px solid rgb(235, 4, 158);}.shaku-inline-highlight[data-id="g"] {background-color: #05faa930;border-bottom: 2px solid green;}.shaku-inline-highlight[data-id="b"] {background-color: #05a4fa30;border-bottom: 2px solid rgb(9, 113, 239);}
.shaku-inline-highlight {background-color: #fa05f230;border-bottom: 2px solid rgb(235, 4, 158);margin: 0 1px;border-radius: 3px;padding: 0 3px;}.shaku-inline-highlight[data-id="r"] {background-color: #fa05f230;border-bottom: 2px solid rgb(235, 4, 158);}.shaku-inline-highlight[data-id="g"] {background-color: #05faa930;border-bottom: 2px solid green;}.shaku-inline-highlight[data-id="b"] {background-color: #05a4fa30;border-bottom: 2px solid rgb(9, 113, 239);}
Diff lines
function useSomeEffect({blog}) {useEffect(() => {return () => {location.href = 'https://jser.dev'}console.log('remove this')}, [blog])}
function useSomeEffect({blog}) {useEffect(() => {return () => {location.href = 'https://jser.dev'}console.log('remove this')}, [blog])}
Use @diff +
and @diff -
to mark next line as diff, append start
/end
(or v
/ ^
) to diff multiple lines.
function useSomeEffect({blog}) {useEffect(() => {// @diff + startreturn () => {location.href = 'https://jser.dev'}// @diff + end// @diff -console.log('remove this')}, [blog])}
function useSomeEffect({blog}) {useEffect(() => {// @diff + startreturn () => {location.href = 'https://jser.dev'}// @diff + end// @diff -console.log('remove this')}, [blog])}
.diff
, .diff-insert
and .diff-delete
are used to style the diff lines.
pre.shaku .line.diff::before {position: absolute;margin-left: -1ch;}pre.shaku .line.diff-insert {background-color: rgba(46, 160, 67, 0.2);}pre.shaku .line.diff-insert::before {content: "+";}pre.shaku .line.diff-delete {background-color: rgba(248, 81, 73, 0.2);}pre.shaku .line.diff-delete::before {content: "-";}
pre.shaku .line.diff::before {position: absolute;margin-left: -1ch;}pre.shaku .line.diff-insert {background-color: rgba(46, 160, 67, 0.2);}pre.shaku .line.diff-insert::before {content: "+";}pre.shaku .line.diff-delete {background-color: rgba(248, 81, 73, 0.2);}pre.shaku .line.diff-delete::before {content: "-";}
Dim lines
function useSomeEffect({blog}) {useEffect(() => {// do some stuffreturn () => {location.href = 'https://jser.dev'}}, [blog])}
function useSomeEffect({blog}) {useEffect(() => {// do some stuffreturn () => {location.href = 'https://jser.dev'}}, [blog])}
Similar to highlighting, use @dim
to dim next line, append start
/end
(or v
/ ^
) to dim multiple lines.
function useSomeEffect({blog}) {// @dimuseEffect(() => {// do some stuff// @dim startreturn () => {location.href = 'https://jser.dev'}// @dim end}, [blog])}
function useSomeEffect({blog}) {// @dimuseEffect(() => {// do some stuff// @dim startreturn () => {location.href = 'https://jser.dev'}// @dim end}, [blog])}
Use .dim
to style dimmed lines.
.shaku .line.dim {filter: blur(2px) brightness(0.5);}
.shaku .line.dim {filter: blur(2px) brightness(0.5);}
Focus lines
function useSomeEffect({blog}) {useEffect(() => {// do some stuffreturn () => {location.href = 'https://jser.dev'}}, [blog])}
function useSomeEffect({blog}) {useEffect(() => {// do some stuffreturn () => {location.href = 'https://jser.dev'}}, [blog])}
Focus means to highlight some by dimming the other lines. Use @focus
to focus next line, append start
/end
(or v
/ ^
) to focus multiple lines.
function useSomeEffect({blog}) {// @focususeEffect(() => {// do some stuffreturn () => {// @focus startlocation.href = 'https://jser.dev'// @focus end}}, [blog])}
function useSomeEffect({blog}) {// @focususeEffect(() => {// do some stuffreturn () => {// @focus startlocation.href = 'https://jser.dev'// @focus end}}, [blog])}
Since it is actually @dim
, there is no special class for it.
Fold lines
function useSomeEffect({blog}) {useEffect(() => {}, [blog])}
function useSomeEffect({blog}) {useEffect(() => {}, [blog])}
@fold
is to collapse lines, append start
/ end
(or v
/ ^
) to mark the lines.
function useSomeEffect({blog}) {useEffect(() => {// @fold start// do some stuffreturn () => {location.href = 'https://jser.dev'}// @fold end}, [blog])}
function useSomeEffect({blog}) {useEffect(() => {// @fold start// do some stuffreturn () => {location.href = 'https://jser.dev'}// @fold end}, [blog])}
@fold
renders <details><summary><mark></mark></summary></details>
, class .shaku-expand
could be used to style.
.shaku-expand summary mark {color: var(--color-text-sub);cursor: pointer;border-radius: 3px;}.shaku-expand summary::-webkit-details-marker,.shaku-expand summary::marker {display: none;content: "";}.shaku-expand[open] summary {display: none;}
.shaku-expand summary mark {color: var(--color-text-sub);cursor: pointer;border-radius: 3px;}.shaku-expand summary::-webkit-details-marker,.shaku-expand summary::marker {display: none;content: "";}.shaku-expand[open] summary {display: none;}
Cut lines
In case you want to remove some lines from the rendered output but keep them in the source code, you can use @cut
to cut one line, or append start
/ end
(or v
/ ^
) to cut multiple lines.
// @cut vimport Button from './Button'import { useEffect } from 'react'// @cut ^function component() {return <Buttonclass="button"disabled/>}
// @cut vimport Button from './Button'import { useEffect } from 'react'// @cut ^function component() {return <Buttonclass="button"disabled/>}
function component() {return <Buttonclass="button"disabled/>}
function component() {return <Buttonclass="button"disabled/>}
Position Shift
function component() {This is very beginning
return <Buttonclass="button"--------------Hello World!
disabled/>}
function component() {This is very beginning
return <Buttonclass="button"--------------Hello World!
disabled/>}
Sometimes it is hard to position it right(with formatter .etc), you can use position shift <
to move shaku elements toward left
function component() {//^<<//[This is very beginning ] <<return <Buttonclass="button"//--------------<<//[Hello World!]<<<disabled/>}
function component() {//^<<//[This is very beginning ] <<return <Buttonclass="button"//--------------<<//[Hello World!]<<<disabled/>}
Escape
For cases where rendering raw comments are desirable, we can put !
at the end of shaku lines to escape.
const Hello = "World!"// ^// [Hello World!]Above two lines are not rendered into UI elements!
Since they have
!
at the end and!
is removed when rendered
const Hello = "World!"// ^// [Hello World!]Above two lines are not rendered into UI elements!
Since they have
!
at the end and!
is removed when rendered
Custom class names for lines
Sometimes you might want to add custom class names for a line, this could be easily done by @class
directive.
// @class custom-class1 custom-class2const Hello = "World!"Open the dev console and inspect this line,
you'll see this line is rendered with the custom class names!
// @class custom-class1 custom-class2const Hello = "World!"Open the dev console and inspect this line,
you'll see this line is rendered with the custom class names!
Custom data attributes
You can also use @data
to add custom data attributes to a line, which could be useful if you are building something on top of Shaku.
// @data hello=world jser="dev"const Hello = "World!"Open the dev console and inspect this line,
you'll see this line is rendered with the custom data attributes!
// @data hello=world jser="dev"const Hello = "World!"Open the dev console and inspect this line,
you'll see this line is rendered with the custom data attributes!
Dev Tools
We got some tools to understand how Shaku works, such as Shiki Token Inspector and Sugar High Token Inspector
Showcases
Check out some examples built with Shaku.
- jser.dev blog is using Shaku heavily to annotate code snippets.
- jser.pro has interactive React quizzes on top of Shaku.
Got a bug or have better ideas? Raise an issue on shaku repo.