Code Block

Fully composable code block with Shiki syntax highlighting, copy button, filename, language selector, diff, focus, and word annotations.

login-form.tsx
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export function LoginForm() {
  return (
    <form className="grid gap-4">
      <div className="grid gap-1.5">
        <Label htmlFor="email">Email</Label>
        <Input id="email" type="email" placeholder="you@example.com" />
      </div>
      <Button type="submit" className="w-full">
        Sign in
      </Button>
    </form>
  )
}
"use client"

import { FileIcon } from "lucide-react"

Installation

pnpm dlx shadcn@latest add https://ui.tyap.me/r/styles/base/code-block.json

Usage

import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockCopyButton,
  CodeBlockFilename,
  CodeBlockHeader,
  CodeBlockTitle,
} from "@/components/ui/code-block"
import { FileIcon } from "lucide-react"
<CodeBlock code={code} language="tsx">
  <CodeBlockHeader>
    <CodeBlockTitle>
      <FileIcon className="size-3.5" />
      <CodeBlockFilename>demo.tsx</CodeBlockFilename>
    </CodeBlockTitle>
    <CodeBlockActions>
      <CodeBlockCopyButton />
    </CodeBlockActions>
  </CodeBlockHeader>
</CodeBlock>

Examples

No Header

A minimal code block with no header — useful for inline snippets, shell commands, and install instructions.

import { createHighlighter } from "shiki"
import { transformerNotationDiff } from "@shikijs/transformers"

const highlighter = await createHighlighter({
  themes: ["github-light", "github-dark"],
  langs: ["typescript"],
})

const html = highlighter.codeToHtml(source, {
  lang: "typescript",
  themes: { light: "github-light", dark: "github-dark" },
  transformers: [transformerNotationDiff()],
})
"use client"

import {

Line Numbers

Use the showLineNumbers prop to display line numbers.

use-debounce.ts
import { useEffect, useRef, useState } from "react"

export function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value)

  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay)
    return () => clearTimeout(id)
  }, [value, delay])

  return debounced
}
"use client"

import { FileIcon } from "lucide-react"

Language Selector

Use CodeBlockLanguageSelector to let users switch between languages. Pass the current language as value and update it with onValueChange.

button.tsx
import { cva } from "class-variance-authority"

const buttonVariants = cva(
  "inline-flex items-center rounded-full font-medium",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground",
        ghost: "hover:bg-muted hover:text-foreground",
      },
      size: {
        sm: "h-8 px-3 text-sm",
        md: "h-10 px-4",
      },
    },
    defaultVariants: { variant: "default", size: "md" },
  }
)
"use client"

import { useState } from "react"

Diff

Add // [!code --] to mark a line as removed and // [!code ++] to mark it as added. The annotation comment is stripped from the rendered output.

store.ts
import { createStore } from "zustand" // [!code --]
import { create } from "zustand" // [!code ++]

const useStore = createStore(() => ({ // [!code --]
const useStore = create(() => ({ // [!code ++]
  count: 0,
  increment: () =>
    set((state) => ({ count: state.count + 1 })),
}))

export default useStore
"use client"

import { FileIcon } from "lucide-react"

Highlight

Add // [!code highlight] to draw attention to specific lines.

highlight.ts
import { createHighlighter } from "shiki"

const highlighter = await createHighlighter({
  langs: ["typescript"],
  themes: ["github-light", "github-dark"], // [!code highlight]
})

const html = highlighter.codeToHtml(code, {
  lang: "typescript",
  themes: { // [!code highlight]
    light: "github-light", // [!code highlight]
    dark: "github-dark", // [!code highlight]
  }, // [!code highlight]
})
"use client"

import { FileIcon } from "lucide-react"

Focus

Add // [!code focus] to focus specific lines. All other lines are blurred, drawing the reader's eye to the relevant code.

use-local-storage.ts
import { useEffect, useState } from "react"

export function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => { // [!code focus]
    try { // [!code focus]
      const item = window.localStorage.getItem(key) // [!code focus]
      return item ? JSON.parse(item) : initial // [!code focus]
    } catch { // [!code focus]
      return initial // [!code focus]
    } // [!code focus]
  }) // [!code focus]

  useEffect(() => {
    window.localStorage.setItem(key, JSON.stringify(value))
  }, [key, value])

  return [value, setValue] as const
}
"use client"

import { FileIcon } from "lucide-react"

Word Highlight

Add // [!code word:term] at the top of the block to highlight every occurrence of a word. You can also target specific lines with // [!code word:term:1].

use-toggle.ts
// [!code word:useCallback]
import { useCallback, useState } from "react"

export function useToggle(initial = false) {
  const [on, setOn] = useState(initial)

  const toggle = useCallback(() => setOn((v) => !v), [])
  const setTrue = useCallback(() => setOn(true), [])
  const setFalse = useCallback(() => setOn(false), [])

  return { on, toggle, setTrue, setFalse }
}
"use client"

import { FileIcon } from "lucide-react"

Language Detection

Use detectLanguage to automatically infer the language from a filename extension.

import { CodeBlock, detectLanguage } from "@/components/ui/code-block"
 
<CodeBlock code={code} language={detectLanguage("app.tsx")}>
  ...
</CodeBlock>

Supported extensions: ts, tsx, js, jsx, py, go, rs, css, scss, html, json, yaml, bash, sql, graphql, prisma, vue, svelte, and more.

API Reference

CodeBlock

PropTypeDefault
codestring
languageBundledLanguage
showLineNumbersbooleanfalse
syntaxHighlightingbooleantrue
themesCodeThemes{ light: "github-light", dark: "github-dark" }
classNamestring

CodeBlockCopyButton

PropTypeDefault
onCopy() => void
onError(error: Error) => void
timeoutnumber2000

Annotation Syntax

NotationEffect
// [!code --]Mark line as removed (diff)
// [!code ++]Mark line as added (diff)
// [!code highlight]Highlight line
// [!code focus]Focus line (blur all others)
// [!code word:term]Highlight all occurrences of a word
// [!code error]Mark line as error
// [!code warning]Mark line as warning