CodeHighlighted
@cloudflare/kumo

New in v1.10: Shiki-powered syntax highlighting with lazy loading. Import from @cloudflare/kumo/code to use.

const greeting = "Hello, World!";
console.log(greeting);
<ShikiProvider
  engine="javascript"
  languages={["tsx", "bash", "json"]}
>
  <CodeHighlighted code={code} lang="tsx" />
</ShikiProvider>

Overview

A Shiki-powered syntax highlighter. Supports 200+ languages with TextMate grammars, dual light/dark themes, and lazy loading. Exported from a separate entry point (@cloudflare/kumo/code) to avoid bundling Shiki for apps that don't need it.

Installation

CodeHighlighted is exported from a separate entry point to avoid bundling Shiki for apps that don't need it.

import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";

Important: Do NOT import from the main @cloudflare/kumo entry. That would pull Shiki into your bundle even if you don't use it.

Basic Usage

Wrap your app with ShikiProvider to configure Shiki once. All CodeHighlighted components inside share the same Shiki instance.

import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";

export function App() {
  return (
    <ShikiProvider
      engine="javascript"
      languages={["tsx", "typescript", "bash", "json"]}
    >
      {/* All CodeHighlighted components share the same Shiki instance */}
      <CodeHighlighted code="const x = 1;" lang="typescript" />
    </ShikiProvider>
  );
}

Examples

Languages

CodeHighlighted supports 200+ languages through Shiki. Only load the languages you need.

TypeScript

interface User {
  id: string;
  name: string;
  email: string;
}

async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}
<CodeHighlighted code={code} lang="typescript" />

React / TSX

import { useState } from "react";

export function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}
<CodeHighlighted code={code} lang="tsx" />

Bash / Shell

# Install Kumo
npm install @cloudflare/kumo

# Or with pnpm
pnpm add @cloudflare/kumo

# Start development server
pnpm dev
<CodeHighlighted code={code} lang="bash" />

JSON

{
  "name": "@cloudflare/kumo",
  "version": "1.9.0",
  "dependencies": {
    "react": "^19.0.0",
    "shiki": "^4.0.0"
  }
}
<CodeHighlighted code={code} lang="json" />

CSS

.button {
  background: var(--color-brand);
  border-radius: 0.5rem;
  padding: 0.5rem 1rem;
  
  &:hover {
    background: var(--color-brand-hover);
  }
}
<CodeHighlighted code={code} lang="css" />

Highlight Lines

Emphasize specific lines with highlightLines (1-indexed).

function processData(items: string[]) {
  // Filter out empty items
  const filtered = items.filter(Boolean);
  
  // Transform to uppercase (highlighted)
  const transformed = filtered.map(item => item.toUpperCase());
  
  // Return sorted result
  return transformed.toSorted();
}
<CodeHighlighted code={code} lang="typescript" highlightLines={[5, 6]} />

Custom Highlight Color

Customize the highlight color with the --kumo-code-highlight-bg CSS variable.

function greet(name: string) {
  // This line is highlighted
  console.log(`Hello, ${name}!`);
  
  return name.toUpperCase();
}
CSS Variable--kumo-code-highlight-bg: hsla(220, 80%, 50%, 0.1)

Line Numbers

Display line numbers with showLineNumbers.

import { useState, useEffect } from "react";

export function useWindowSize() {
  const [size, setSize] = useState({ width: 0, height: 0 });
  
  useEffect(() => {
    function handleResize() {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }
    
    handleResize();
    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);
  
  return size;
}
<CodeHighlighted code={code} lang="typescript" showLineNumbers />

Copy Button

Add a copy-to-clipboard button with showCopyButton.

npm install @cloudflare/kumo
<CodeHighlighted code={code} lang="bash" showCopyButton />

Combine all features for a complete code display experience.

import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";

export function CodeExample({ code, language }: Props) {
  return (
    <ShikiProvider
      engine="javascript"
      languages={["tsx", "typescript", "bash", "json"]}
    >
      <CodeHighlighted
        code={code}
        lang={language}
        showCopyButton
      />
    </ShikiProvider>
  );
}
<CodeHighlighted
  code={code}
  lang="tsx"
  showCopyButton
  highlightLines={[7, 8, 9, 10, 11]}
/>

Shared Provider

Multiple code blocks can share a single ShikiProvider. Shiki loads once and is reused for all blocks.

const config = { theme: "dark" };
npm run build
{ "success": true }
<ShikiProvider>
  <CodeHighlighted code={tsCode} lang="typescript" />
  <CodeHighlighted code={bashCode} lang="bash" />
  <CodeHighlighted code={jsonCode} lang="json" />
</ShikiProvider>

Themes

CodeHighlighted uses hardcoded themes for consistent styling across all Kumo applications:

  • Light mode: github-light
  • Dark mode: vesper

Theme customization is not supported. This ensures visual consistency across all code blocks in your application.

Server-Side Usage

For SSR frameworks (Next.js RSC, Astro, Remix), use the server utilities to highlight at build time.

One-off highlighting

// Next.js RSC or Astro
import { highlightCode } from "@cloudflare/kumo/code/server";

export default async function Page() {
  const html = await highlightCode(`const x = 1;`, "typescript");

  return <pre dangerouslySetInnerHTML={{ __html: html }} />;
}

Reusable highlighter

// For multiple highlights, reuse the highlighter
import { createServerHighlighter } from "@cloudflare/kumo/code/server";

const highlighter = await createServerHighlighter({
  languages: ["tsx", "bash", "json"],
});

const html1 = highlighter.highlight(code1, "tsx");
const html2 = highlighter.highlight(code2, "bash");

highlighter.dispose(); // Clean up when done

Custom Hook

Use useShikiHighlighter for custom implementations.

import { useShikiHighlighter } from "@cloudflare/kumo/code";

function CustomCodeBlock({ code, lang }) {
  const { highlight, isLoading, isReady, error } = useShikiHighlighter();

  if (error) {
    return <div className="text-red-500">Failed to load highlighter</div>;
  }

  if (isLoading) {
    return <pre className="animate-pulse"><code>{code}</code></pre>;
  }

  const html = highlight(code, lang);

  // null means highlighting failed — render plain text
  if (html === null) {
    return <pre><code>{code}</code></pre>;
  }

  return <pre dangerouslySetInnerHTML={{ __html: html }} />;
}

Internationalization

Customize button labels at the provider level for all code blocks, or override per-component.

// Set labels at the provider level for all code blocks
<ShikiProvider
  engine="javascript"
  languages={["tsx", "bash"]}
  labels={{ copy: "Copier", copied: "Copié!" }}
>
  <App />
</ShikiProvider>

// Or override at the component level
<CodeHighlighted
  code={code}
  lang="tsx"
  showCopyButton
  labels={{ copy: "Copy code", copied: "Done!" }}
/>

Framework Integration

Next.js App Router

// app/providers.tsx
"use client";

import { ShikiProvider } from "@cloudflare/kumo/code";

export function Providers({ children }) {
  return (
    <ShikiProvider
      engine="javascript"
      languages={["tsx", "bash", "json"]}
    >
      {children}
    </ShikiProvider>
  );
}

// app/layout.tsx
import { Providers } from "./providers";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

Astro (Static)

For static sites, use server-side highlighting for zero client-side JavaScript.

---
// src/components/CodeBlock.astro
import { highlightCode } from "@cloudflare/kumo/code/server";

const { code, lang } = Astro.props;
const html = await highlightCode(code, lang);
---

<div class="code-block" set:html={html} />

Bundle Size

Shiki is lazy-loaded on first render. The size depends on your configuration:

Scenario Languages Engine Lazy Load Size
Minimal tsx, json JS ~75 KB
Standard tsx, ts, bash, json, css, yaml JS ~95 KB
Full 15+ languages WASM ~250 KB

Teams that don't import from @cloudflare/kumo/code pay 0 KB.

Migration from Code/CodeBlock

The legacy Code and CodeBlock components are deprecated. They will be removed in v2.0.

// Before (deprecated)
import { Code, CodeBlock } from "@cloudflare/kumo";
<CodeBlock code="const x = 1;" lang="ts" />

// After
import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";

// Once at app root
<ShikiProvider
  engine="javascript"
  languages={["tsx"]}
>
  <App />
</ShikiProvider>

// In components
<CodeHighlighted code="const x = 1;" lang="tsx" />

API Reference

ShikiProvider Props

Prop Type Required Description
engine "javascript" | "wasm" Yes JS is smaller (~50KB), WASM is more accurate (~180KB)
languages string[] Yes Languages to support (e.g., ["tsx", "bash"])
labels { copy?: string, copied?: string } No Localized labels for copy button
children ReactNode Yes App content

CodeHighlighted Props

Prop Type Required Description
code string Yes Source code to display
lang string Yes Language identifier (must be in provider's languages)
showLineNumbers boolean No Display line numbers
highlightLines number[] No Lines to emphasize (1-indexed)
showCopyButton boolean No Show copy-to-clipboard button
labels { copy?: string, copied?: string } No Override provider labels for this instance
className string No Additional CSS classes

useShikiHighlighter Return Value

Property Type Description
highlight (code, lang, options?) => string | null Returns highlighted HTML, or null if not ready
isLoading boolean True while Shiki is loading
isReady boolean True when highlight() is safe to call
error Error | null Error if Shiki initialization failed