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.

  1. shaku-code-annotate-core - Shaku parser that parses the syntax in the comments. Tokenizer not included.
  2. shaku-code-annotate-shiki - Shaku code annotation based on the tokenizer from shiki. You can use this package directly in node.js or browser.
  3. remark-shaku-code-annotate - plugin for remark to easily integrate Shaku in Markdown/MDX(Astro .etc)
  4. marked-shaku-code-annotate - a plugin for another markdown engine: marked

Also some demos you can refer to:

  1. Shaku + MDX + Astro
  2. Shaku + MDX + Next.js

Or you can just inspect the source code of this website - CodeBlock.tsx, CodePreviewRemark.tsx or CodePreviewMarked.tsx

Supported Languages

Shaku 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"
----------------

JSer.dev is the homepage for JSer.

Check it out! 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

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-solidand .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 stuff
return () => {
location.href = 'https://jser.dev'
}
}, [blog])
}
function useSomeEffect({blog}) {
useEffect(() => {
// do some stuff
return () => {
location.href = 'https://jser.dev'
}
}, [blog])
}

Use @highlight to highlight next line, @highlight start and@highlight end for multiple lines.

// @highlight
function useSomeEffect({blog}) {
useEffect(() => {
// do some stuff
// @highlight start
return () => {
location.href = 'https://jser.dev'
}
// @highlight end
}, [blog])
}
// @highlight
function useSomeEffect({blog}) {
useEffect(() => {
// do some stuff
// @highlight start
return () => {
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 set different color.

// ( )
function useSomeEffect({blog}) {
//( 2 )
useEffect(() => {
return () => {
//( 3 ) ( 3 )
location.href = 'https://jser.dev'
}
}, [blog])
}
// ( )
function useSomeEffect({blog}) {
//( 2 )
useEffect(() => {
return () => {
//( 3 ) ( 3 )
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: #05a4fa30;
border-bottom: 2px solid rgb(9, 113, 239);
margin: 0 1px;
border-radius: 3px;
padding: 0 3px;
}
.shaku-inline-highlight[data-id="1"] {
background-color: #05a4fa30;
border-bottom: 2px solid rgb(9, 113, 239);
}
.shaku-inline-highlight[data-id="2"] {
background-color: #fa05f230;
border-bottom: 2px solid rgb(235, 4, 158);
}
.shaku-inline-highlight[data-id="3"] {
background-color: #05faa930;
border-bottom: 2px solid green;
}
.shaku-inline-highlight {
background-color: #05a4fa30;
border-bottom: 2px solid rgb(9, 113, 239);
margin: 0 1px;
border-radius: 3px;
padding: 0 3px;
}
.shaku-inline-highlight[data-id="1"] {
background-color: #05a4fa30;
border-bottom: 2px solid rgb(9, 113, 239);
}
.shaku-inline-highlight[data-id="2"] {
background-color: #fa05f230;
border-bottom: 2px solid rgb(235, 4, 158);
}
.shaku-inline-highlight[data-id="3"] {
background-color: #05faa930;
border-bottom: 2px solid green;
}

Dim lines

function useSomeEffect({blog}) {
useEffect(() => {
// do some stuff
return () => {
location.href = 'https://jser.dev'
}
}, [blog])
}
function useSomeEffect({blog}) {
useEffect(() => {
// do some stuff
return () => {
location.href = 'https://jser.dev'
}
}, [blog])
}

Similar to highlighting, use @dim to dim next line, @dim start and@dim end for multiple lines.

function useSomeEffect({blog}) {
// @dim
useEffect(() => {
// do some stuff
return () => {
// @dim start
location.href = 'https://jser.dev'
// @dim end
}
}, [blog])
}
function useSomeEffect({blog}) {
// @dim
useEffect(() => {
// do some stuff
return () => {
// @dim start
location.href = 'https://jser.dev'
// @dim end
}
}, [blog])
}

Use .dim to style dimmed lines.

.shaku .line.dim {
opacity: 0.3;
}
.shaku .line.dim {
opacity: 0.3;
}

Focus lines

function useSomeEffect({blog}) {
useEffect(() => {
// do some stuff
return () => {
location.href = 'https://jser.dev'
}
}, [blog])
}
function useSomeEffect({blog}) {
useEffect(() => {
// do some stuff
return () => {
location.href = 'https://jser.dev'
}
}, [blog])
}

Focus means to highlight some lines and dim the others, it is a shorthand of @highlight and @dim. Use @focus to focus next line, @focus start and@focus end for multiple lines.

function useSomeEffect({blog}) {
// @focus
useEffect(() => {
// do some stuff
return () => {
// @focus start
location.href = 'https://jser.dev'
// @focus end
}
}, [blog])
}
function useSomeEffect({blog}) {
// @focus
useEffect(() => {
// do some stuff
return () => {
// @focus start
location.href = 'https://jser.dev'
// @focus end
}
}, [blog])
}

Since it is a shorthand syntax, there is no special class for it.

Position Shift

function component() {

This is very beginning

return <Button
class="button"
--------------

Hello World!

disabled
/>
}
function component() {

This is very beginning

return <Button
class="button"
--------------

Hello World!

disabled
/>
}

Sometimes it is hard to position it right, you can use position shift < to move shaku elements toward left

function component() {
//^<<
//[This is very beginning ] <<
return <Button
class="button"
//--------------<<
//[Hello World!]<<<
disabled
/>
}
function component() {
//^<<
//[This is very beginning ] <<
return <Button
class="button"
//--------------<<
//[Hello World!]<<<
disabled
/>
}

Escape with !

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


Got an issue or have better ideas? Raise an issue on shaku repo.