Code Block Client with Shiki

Create a client-side Code Block component using Shiki for syntax highlighting.

ReactClientShikiComponents

Preview

Preparing...

Installation

shadcn/ui

shadcn/ui Command
pnpm dlx shadcn@latest add https://code-blocks.pheralb.dev/r/client-shiki.json

Manual

Before creating the component, make sure you have the basic structure:

reactCode Block

The main structure of the Code Block component with header and content areas.

  1. Create your Shiki highlighter utility:
shikiShiki Highlighter

The main highlighter utility to render syntax highlighted code.

  1. Create the Code Block Client component:
.tsx
"use client";

import { useEffect, useState, type ComponentProps } from "react";

import { cn } from "@/utils/cn";
import { highlight, Themes, type Languages } from "@/utils/shiki/highlight";

interface CodeblockClientShikiProps extends ComponentProps<"div"> {
  code: string;
  language?: Languages;
  lineNumbers?: boolean;
}

const CodeblockShiki = ({
  code,
  language = "tsx",
  lineNumbers = false,
  className,
  ...props
}: CodeblockClientShikiProps) => {
  const [highlightedHtml, setHighlightedHtml] = useState<string | null>(null);

  useEffect(() => {
    async function clientHighlight() {
      if (!code) {
        setHighlightedHtml("<pre><code></code></pre>");
        return;
      }
      const highlighter = await highlight();
      const html = highlighter.codeToHtml(code, {
        lang: language,
        themes: {
          light: Themes.light,
          dark: Themes.dark,
        },
        transformers: [
          {
            name: "AddLineNumbers",
            pre(node) {
              if (lineNumbers) {
                const shikiStyles = node.properties.class;
                node.properties.class = `${shikiStyles} shiki-line-numbers`;
              }
            },
          },
        ],
      });
      setHighlightedHtml(html);
    }
    void clientHighlight();
  }, [code, language, lineNumbers]);

  const classNames = cn("w-full overflow-x-auto", className);

  // SSR fallback
  return highlightedHtml ? (
    <div
      className={classNames}
      dangerouslySetInnerHTML={{ __html: highlightedHtml }}
      {...props}
    />
  ) : (
    <div className={classNames} {...props}>
      <pre>
        <code>{code}</code>
      </pre>
    </div>
  );
};

export { CodeblockShiki };

Usage

Now you can use the CodeblockShiki into your CodeBlockContent component:

.tsx
import {
  CodeBlock,
  CodeBlockContent,
  CodeBlockHeader,
  CodeBlockGroup,
  CodeBlockIcon,
} from "@/components/code-block";
import { CopyButton } from "@/components/code-block/copy-button";
import { CodeblockShiki } from "@/components/code-block/client/shiki";

<CodeBlock>
  <CodeBlockHeader>
    <CodeBlockGroup>
      <CodeBlockIcon language="ts" />
      <span>Code Block + Shiki</span>
    </CodeBlockGroup>
    <CopyButton content={code} />
  </CodeBlockHeader>
  <CodeBlockContent>
    <CodeblockShiki language="ts" code="console.log('Hello, world!');" />
  </CodeBlockContent>
</CodeBlock>;

Props

React Props

PropTypeRequired
codestringYes
languageLanguagesNo
lineNumbersbooleanNo
Included: DOMAttributes<HTMLDivElement>

Languages type is from the shiki utility.