Code Block

PreviousNext

A versatile code block component supporting package manager commands, syntax highlighting with themes, and markdown-wrapped code strings.

# Hello World in Python
def main():
print("Hello, World!")
if __name__ == "__main__":
main()

Installation

pnpm dlx shadcn@latest add https://deltacomponents.dev/r/code-block.json
Required CSS

Define --surface and .no-scrollbar in your globals.css:

:root {
  --surface: #fafafa; /* Light mode */
}
 
.dark {
  --surface: #171717; /* Dark mode */
}
 
.no-scrollbar::-webkit-scrollbar {
  display: none;
}
 
.no-scrollbar {
  -ms-overflow-style: none;
  scrollbar-width: none;
}

The --surface variable enables bg-surface styling. The .no-scrollbar class hides scrollbars when scrollbar={false} (default).

Usage

Standard Code String

import { CodeBlock } from "@/components/ui/code-block"
 
export default function Example() {
  return (
    <CodeBlock
      code={`print("Hello, World!")`}
      language="python"
      filename="hello.py"
      showLineNumbers={true}
    />
  )
}

Markdown-Wrapped Code String

import { CodeBlock } from "@/components/ui/code-block"
 
const markdownCode = `\`\`\`python
print("Hello, World!")
\`\`\``
 
export default function Example() {
  return <CodeBlock code={markdownCode} filename="hello.py" />
}

MDX Integration (Contentlayer/Fumadocs)

To automatically render all code blocks in your MDX files with the CodeBlock component, configure your mdx-components.tsx:

mdx-components.tsx
import { CodeBlock } from "@/components/ui/code-block"
import type { MDXComponents } from "mdx/types"
 
export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    ...components,
    pre: ({ children, ...props }) => {
      // Extract code from the pre > code structure
      const code = typeof children === "object" && "props" in children
        ? children.props.children
        : ""
 
      // Extract language from className (e.g., "language-typescript")
      const className = typeof children === "object" && "props" in children
        ? children.props.className || ""
        : ""
      const language = className.replace(/language-/, "")
 
      return (
        <CodeBlock
          code={code}
          language={language || "typescript"}
          showLineNumbers={true}
        />
      )
    },
  }
}

Now all code blocks in your MDX files will automatically use your CodeBlock component:

example.mdx
# My Documentation
 
\`\`\`typescript
const greeting = "Hello, World!"
console.log(greeting)
\`\`\`

For package manager commands, use the language identifier to trigger automatic conversion:

installation.mdx
\`\`\`npm
npx shadcn@latest add button
\`\`\`

The CodeBlock component will automatically:

  • Parse the markdown code fence syntax (```language)
  • Extract the language identifier
  • Apply syntax highlighting
  • Convert package manager commands to tabbed interfaces

Features

  • Dual Mode: Package manager commands or syntax-highlighted code
  • Syntax Highlighting: Powered by Prism with custom themes
  • Package Manager Tabs: Support for npm, yarn, pnpm, and bun
  • Copy to Clipboard: One-click copy functionality with visual feedback
  • Line Numbers: Optional line numbering for code blocks
  • Custom Themes: Light/dark adaptive themes with hover effects
  • Background Options: Choose between surface background or JSON theme background
  • Terminal Styling: Command-line interface appearance
  • Responsive: Horizontal scrolling for long content
  • Flexible: Shows only provided package managers or code content

Examples

Custom Themes with Background

Apply adaptive themes with useThemeBackground to use theme-defined backgrounds instead of bg-surface:

fibonacci.py
1def fibonacci(n: int) -> list[int]:
2 """Generate Fibonacci sequence up to n numbers. This is a long comment to test horizontal scrolling behavior in the code block component."""
3 if n <= 0:
4 return []
5 elif n == 1:
6 return [0]
7
8 sequence = [0, 1]
9 while len(sequence) < n:
10 sequence.append(sequence[-1] + sequence[-2]) # Add the sum of the last two numbers to the sequence list for Fibonacci calculation
11
12 return sequence
13
14def fibonacci_recursive(n: int, memo: dict[int, int] | None = None) -> int:
15 """Calculate the nth Fibonacci number using memoization for better performance in recursive calls."""
16 if memo is None:
17 memo = {}
18 if n in memo:
19 return memo[n]
20 if n <= 1:
21 return n
22 memo[n] = fibonacci_recursive(n - 1, memo) + fibonacci_recursive(n - 2, memo)
23 return memo[n]
24
25if __name__ == "__main__":
26 result = fibonacci(10)
27 print(f"Fibonacci sequence: {result}") # Output: Fibonacci sequence: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] for the first 10 numbers

JSON Syntax Highlighting

Display formatted JSON data with syntax highlighting:

Scrollbar: Visible
user-session.json
1{
2 "user": {
3 "id": "usr_2nQz7kX9pLm4",
4 "email": "sarah.chen@acme.com",
5 "name": "Sarah Chen",
6 "role": "senior_engineer",
7 "permissions": ["read", "write", "deploy"],
8 "metadata": {
9 "department": "platform",
10 "team": "infrastructure",
11 "timezone": "America/Los_Angeles"
12 }
13 },
14 "session": {
15 "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
16 "expiresAt": "2024-12-31T23:59:59Z",
17 "refreshToken": "rt_9xKmP3vN8qL2",
18 "scopes": ["api:read", "api:write", "admin:users"]
19 },
20 "preferences": {
21 "theme": "dark",
22 "notifications": {
23 "email": true,
24 "slack": true,
25 "digest": "daily"
26 },
27 "editor": {
28 "tabSize": 2,
29 "formatOnSave": true
30 }
31 }
32}

Expandable Code Blocks

Handle long code snippets with expand/collapse functionality:

data-table.tsx
|
1import * as React from "react"
2import { useEffect, useState, useCallback, useMemo } from "react"
3import { cn } from "@/lib/utils"
4
5interface DataItem {
6 id: string
7 name: string
8 description: string
9 status: "active" | "inactive" | "pending"
10 createdAt: Date
11 updatedAt: Date
12 metadata: Record<string, unknown>
13}
14
15interface DataTableProps {
16 data: DataItem[]
17 onSelect?: (item: DataItem) => void
18 onDelete?: (id: string) => void
19 onEdit?: (item: DataItem) => void
20 className?: string
21 loading?: boolean
22 error?: Error | null
23}
24
25export function DataTable({
26 data,
27 onSelect,
28 onDelete,
29 onEdit,
30 className,
31 loading = false,
32 error = null,
33}: DataTableProps) {
34 const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
35 const [sortColumn, setSortColumn] = useState<keyof DataItem>("name")
36 const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc")
37 const [filterText, setFilterText] = useState("")
38
39 const filteredData = useMemo(() => {
40 return data
41 .filter((item) =>
42 item.name.toLowerCase().includes(filterText.toLowerCase()) ||
43 item.description.toLowerCase().includes(filterText.toLowerCase())
44 )
45 .sort((a, b) => {
46 const aVal = a[sortColumn]
47 const bVal = b[sortColumn]
48 const modifier = sortDirection === "asc" ? 1 : -1
49 if (aVal < bVal) return -1 * modifier
50 if (aVal > bVal) return 1 * modifier
51 return 0
52 })
53 }, [data, filterText, sortColumn, sortDirection])
54
55 const handleSelectAll = useCallback(() => {
56 if (selectedIds.size === filteredData.length) {
57 setSelectedIds(new Set())
58 } else {
59 setSelectedIds(new Set(filteredData.map((item) => item.id)))
60 }
61 }, [filteredData, selectedIds.size])
62
63 const handleSelectItem = useCallback((id: string) => {
64 setSelectedIds((prev) => {
65 const next = new Set(prev)
66 if (next.has(id)) {
67 next.delete(id)
68 } else {
69 next.add(id)
70 }
71 return next
72 })
73 }, [])
74
75 if (loading) {
76 return <div className="flex items-center justify-center p-8">Loading...</div>
77 }
78
79 if (error) {
80 return <div className="text-red-500 p-4">Error: {error.message}</div>
81 }
82
83 return (
84 <div className={cn("rounded-lg border", className)}>
85 <div className="p-4 border-b">
86 <input
87 type="text"
88 placeholder="Filter items..."
89 value={filterText}
90 onChange={(e) => setFilterText(e.target.value)}
91 className="w-full px-3 py-2 border rounded-md"
92 />
93 </div>
94 <table className="w-full">
95 <thead>
96 <tr className="border-b bg-muted/50">
97 <th className="p-3 text-left">
98 <input
99 type="checkbox"
100 checked={selectedIds.size === filteredData.length}
101 onChange={handleSelectAll}
102 />
103 </th>
104 <th className="p-3 text-left font-medium">Name</th>
105 <th className="p-3 text-left font-medium">Status</th>
106 <th className="p-3 text-left font-medium">Actions</th>
107 </tr>
108 </thead>
109 <tbody>
110 {filteredData.map((item) => (
111 <tr key={item.id} className="border-b hover:bg-muted/30">
112 <td className="p-3">
113 <input
114 type="checkbox"
115 checked={selectedIds.has(item.id)}
116 onChange={() => handleSelectItem(item.id)}
117 />
118 </td>
119 <td className="p-3">{item.name}</td>
120 <td className="p-3">
121 <span className={`px-2 py-1 rounded-full text-xs ${
122 item.status === "active" ? "bg-green-100 text-green-800" :
123 item.status === "inactive" ? "bg-gray-100 text-gray-800" :
124 "bg-yellow-100 text-yellow-800"
125 }`}>
126 {item.status}
127 </span>
128 </td>
129 <td className="p-3 flex gap-2">
130 <button onClick={() => onEdit?.(item)}>Edit</button>
131 <button onClick={() => onDelete?.(item.id)}>Delete</button>
132 </td>
133 </tr>
134 ))}
135 </tbody>
136 </table>
137 </div>
138 )
139}

Interactive Theme Demo

hello.go
1package main
2
3import "fmt"
4
5func main() {
6 fmt.Println("Hello, World!")
7}

Cycle through Prism themes with background toggle to compare bg-surface vs. useThemeBackground behavior.

Markdown Code Strings

Parse and render markdown-formatted code blocks:

greet.py
1def greet(name: str) -> str:
2 """Return a greeting message."""
3 return f"Hello, {name}!"
4
5if __name__ == "__main__":
6 message = greet("World")
7 print(message)

NPX Commands

Handle package manager commands in markdown:

npx shadcn@latest add button

Package Manager Commands

npm install @radix-ui/react-dropdown-menu

Alternatively, use individual props for package managers:

npx shadcn@latest add button

API Reference

Props

PropTypeDefaultDescription
Package Manager Props
npmstring-npm command
yarnstring-yarn command
pnpmstring-pnpm command
bunstring-bun command
defaultPackageManagerPackageManager"npm"Default tab selection
Code Highlighting Props
codestring-Code content (supports markdown-wrapped code blocks)
languagestring"typescript"Language for syntax highlighting
filenamestring-Optional filename display
showLineNumbersbooleantrueToggle line numbers
themePrismTheme-Prism theme object
adaptiveThemeobject-{ light: PrismTheme, dark: PrismTheme } for theme switching
useThemeBackgroundbooleanfalseApply theme's backgroundColor instead of bg-surface
scrollbarbooleanfalseShow scrollbars (applies .no-scrollbar class when false)
General Props
classNamestring-Additional CSS classes

Background Behavior

  • Default (useThemeBackground={false}): Uses bg-surface CSS variable
  • Theme Background (useThemeBackground={true}): Uses theme.plain.backgroundColor from Prism theme
  • Requires --surface CSS variable definition for default mode

Types

type PackageManager = "npm" | "yarn" | "pnpm" | "bun"
 
interface AdaptiveTheme {
  light: PrismTheme
  dark: PrismTheme
}

Theme Structure

Custom themes follow the Prism theme structure:

const customTheme: PrismTheme = {
  plain: {
    color: "#e6e6fa",
    backgroundColor: "#1a1a2e",
  },
  styles: [
    {
      types: ["comment"],
      style: {
        color: "#6a7b9a",
        fontStyle: "italic",
      },
    },
    // ... more styles
  ],
}