<div class="container">
<h1>Hello, world!</h1>
</div>'use client'
import { CodeBlock, createShikiAdapter } from '@chakra-ui/react'
import type { HighlighterGeneric } from 'shiki'
const file = {
code: `<div class="container">
<h1>Hello, world!</h1>
</div>`,
language: 'html',
title: 'index.html',
}
export const CodeBlockBasic = () => {
return (
<CodeBlock.AdapterProvider value={shikiAdapter}>
<CodeBlock.Root code={file.code} language={file.language}>
<CodeBlock.Content>
<CodeBlock.Code>
<CodeBlock.CodeText />
</CodeBlock.Code>
</CodeBlock.Content>
</CodeBlock.Root>
</CodeBlock.AdapterProvider>
)
}
const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
async load() {
const { createHighlighter } = await import('shiki')
return createHighlighter({
langs: ['html'],
themes: ['github-dark'],
})
},
theme: 'github-dark',
})
Anatomy
import { CodeBlock } from '@chakra-ui/react'<CodeBlock.AdapterProvider value={adapter}>
<CodeBlock.Root code="..." language="tsx">
<CodeBlock.Header>
<CodeBlock.Title />
<CodeBlock.Control>
<CodeBlock.CopyTrigger />
<CodeBlock.CollapseTrigger />
</CodeBlock.Control>
</CodeBlock.Header>
<CodeBlock.Content>
<CodeBlock.Code>
<CodeBlock.CodeText />
</CodeBlock.Code>
</CodeBlock.Content>
</CodeBlock.Root>
</CodeBlock.AdapterProvider>Adapters
Syntax highlighting is provided by an adapter. Create one, pass it to
CodeBlock.AdapterProvider at the top level of your app, and render
CodeBlock.Root within it. Without an adapter the code block falls back to
plain text.
Shiki
Install the shiki package.
npm install shikiThen create the adapter, which dynamically loads the highlighter for the languages you need.
import { CodeBlock, createShikiAdapter } from '@chakra-ui/react'
import type { HighlighterGeneric } from 'shiki'
const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
async load() {
const { createHighlighter } = await import('shiki')
return createHighlighter({
langs: ['tsx', 'html', 'bash', 'json'],
themes: ['github-dark'],
})
},
theme: 'github-dark',
})
const App = () => (
<CodeBlock.AdapterProvider value={shikiAdapter}>
{/* ... */}
</CodeBlock.AdapterProvider>
)The examples on this page use the github-dark theme. Pass a { light, dark }
theme map together with meta.colorScheme to follow the active color mode, see
the themes example.
Once an adapter is provided, the meta prop on CodeBlock.Root unlocks
highlighting features such as showLineNumbers, highlightLines,
focusedLineNumbers, addedLineNumbers and removedLineNumbers.
Highlight.js
Highlight.js is supported as well through
createHighlightJsAdapter. Install the highlight.js package and register the
languages you need.
import { createHighlightJsAdapter } from '@chakra-ui/react'
import hljs from 'highlight.js/lib/core'
const highlightJsAdapter = createHighlightJsAdapter<typeof hljs>({
async load() {
const languages = {
tsx: () => import('highlight.js/lib/languages/typescript'),
html: () => import('highlight.js/lib/languages/xml'),
}
await Promise.all(
Object.entries(languages).map(async ([language, file]) => {
const { default: langModule } = await file()
hljs.registerLanguage(language, langModule)
}),
)
return hljs
},
})Examples
Sizes
Use the size prop to change the size of the code block component.
<div class="container">
<h1>Hello, world!</h1>
</div><div class="container">
<h1>Hello, world!</h1>
</div><div class="container">
<h1>Hello, world!</h1>
</div>'use client'
import { CodeBlock, For, Stack, createShikiAdapter } from '@chakra-ui/react'
import type { HighlighterGeneric } from 'shiki'
const file = {
code: `<div class="container">
<h1>Hello, world!</h1>
</div>`,
language: 'html',
title: 'index.html',
}
export const CodeBlockWithSizes = () => {
return (
<CodeBlock.AdapterProvider value={shikiAdapter}>
<Stack gap="8">
<For each={['sm', 'md', 'lg']}>
{(size) => (
<CodeBlock.Root
key={size}
code={file.code}
language={file.language}
size={size}
>
<CodeBlock.Header>
<CodeBlock.Title>(size={size})</CodeBlock.Title>
</CodeBlock.Header>
<CodeBlock.Content>
<CodeBlock.Code>
<CodeBlock.CodeText />
</CodeBlock.Code>
</CodeBlock.Content>
</CodeBlock.Root>
)}
</For>
</Stack>
</CodeBlock.AdapterProvider>
)
}
const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
async load() {
const { createHighlighter } = await import('shiki')
return createHighlighter({
langs: ['html'],
themes: ['github-dark'],
})
},
theme: 'github-dark',
})
Title
Render the CodeBlock.Title component within the CodeBlock.Header component
to add a title to the code block component.
<div class="container">
<h1>Hello, world!</h1>
</div>'use client'
import { CodeBlock, Icon, createShikiAdapter } from '@chakra-ui/react'
import { FaHtml5 } from 'react-icons/fa'
import type { HighlighterGeneric } from 'shiki'
const file = {
code: `<div class="container">
<h1>Hello, world!</h1>
</div>`,
language: 'html',
title: 'index.html',
}
export const CodeBlockWithTitle = () => {
return (
<CodeBlock.AdapterProvider value={shikiAdapter}>
<CodeBlock.Root code={file.code} language={file.language}>
<CodeBlock.Header>
<CodeBlock.Title>
<Icon as={FaHtml5} color="orange.400" />
{file.title}
</CodeBlock.Title>
</CodeBlock.Header>
<CodeBlock.Content>
<CodeBlock.Code>
<CodeBlock.CodeText />
</CodeBlock.Code>
</CodeBlock.Content>
</CodeBlock.Root>
</CodeBlock.AdapterProvider>
)
}
const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
async load() {
const { createHighlighter } = await import('shiki')
return createHighlighter({
langs: ['html'],
themes: ['github-dark'],
})
},
theme: 'github-dark',
})
Copy button
Render the CodeBlock.CopyTrigger component to add a copy button to the code
block component.
<div class="container">
<h1>Hello, world!</h1>
</div>'use client'
import { CodeBlock, IconButton, createShikiAdapter } from '@chakra-ui/react'
import type { HighlighterGeneric } from 'shiki'
const file = {
code: `<div class="container">
<h1>Hello, world!</h1>
</div>`,
language: 'html',
title: 'index.html',
}
export const CodeBlockWithCopyButton = () => {
return (
<CodeBlock.AdapterProvider value={shikiAdapter}>
<CodeBlock.Root code={file.code} language={file.language}>
<CodeBlock.Header>
<CodeBlock.Title>{file.title}</CodeBlock.Title>
<CodeBlock.CopyTrigger asChild>
<IconButton variant="ghost" size="2xs">
<CodeBlock.CopyIndicator />
</IconButton>
</CodeBlock.CopyTrigger>
</CodeBlock.Header>
<CodeBlock.Content>
<CodeBlock.Code>
<CodeBlock.CodeText />
</CodeBlock.Code>
</CodeBlock.Content>
</CodeBlock.Root>
</CodeBlock.AdapterProvider>
)
}
const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
async load() {
const { createHighlighter } = await import('shiki')
return createHighlighter({
langs: ['html'],
themes: ['github-dark'],
})
},
theme: 'github-dark',
})
Floating copy button
Here's an example that adds a floating copy button to the code block component.
<div class="container">
<h1>Hello, world!</h1>
</div>'use client'
import {
CodeBlock,
Float,
IconButton,
createShikiAdapter,
} from '@chakra-ui/react'
import type { HighlighterGeneric } from 'shiki'
const file = {
code: `<div class="container">
<h1>Hello, world!</h1>
</div>`,
language: 'html',
title: 'index.html',
}
export const CodeBlockWithFloatingCopyButton = () => {
return (
<CodeBlock.AdapterProvider value={shikiAdapter}>
<CodeBlock.Root code={file.code} language={file.language}>
<CodeBlock.Content>
<Float placement="top-end" offset="5" zIndex="1">
<CodeBlock.CopyTrigger asChild>
<IconButton variant="ghost" size="2xs">
<CodeBlock.CopyIndicator />
</IconButton>
</CodeBlock.CopyTrigger>
</Float>
<CodeBlock.Code>
<CodeBlock.CodeText />
</CodeBlock.Code>
</CodeBlock.Content>
</CodeBlock.Root>
</CodeBlock.AdapterProvider>
)
}
const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
async load() {
const { createHighlighter } = await import('shiki')
return createHighlighter({
langs: ['html'],
themes: ['github-dark'],
})
},
theme: 'github-dark',
})
Line numbers
Line numbers make it easier to reference specific lines of code. Pass the
meta.showLineNumbers prop to show line numbers in the code block component.
<div class="container">
<h1>Hello, world!</h1>
</div>'use client'
import { CodeBlock, createShikiAdapter } from '@chakra-ui/react'
import type { HighlighterGeneric } from 'shiki'
const file = {
code: `<div class="container">
<h1>Hello, world!</h1>
</div>`,
language: 'html',
title: 'index.html',
}
export const CodeBlockWithLineNumbers = () => {
return (
<CodeBlock.AdapterProvider value={shikiAdapter}>
<CodeBlock.Root
code={file.code}
language={file.language}
meta={{ showLineNumbers: true }}
>
<CodeBlock.Content>
<CodeBlock.Code>
<CodeBlock.CodeText />
</CodeBlock.Code>
</CodeBlock.Content>
</CodeBlock.Root>
</CodeBlock.AdapterProvider>
)
}
const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
async load() {
const { createHighlighter } = await import('shiki')
return createHighlighter({
langs: ['html'],
themes: ['github-dark'],
})
},
theme: 'github-dark',
})
Line highlighting
Pass the meta.highlightLines prop to the CodeBlock.Root component to
highlight specific lines of code. The prop accepts an array of 1-based line
numbers.
const config = {
name: "saas-ui",
version: "3.0.0",
private: true,
}'use client'
import { CodeBlock, createShikiAdapter } from '@chakra-ui/react'
import type { HighlighterGeneric } from 'shiki'
const file = {
code: `const config = {
name: "saas-ui",
version: "3.0.0",
private: true,
}`,
language: 'tsx',
title: 'config.ts',
}
export const CodeBlockWithLineHighlight = () => {
return (
<CodeBlock.AdapterProvider value={shikiAdapter}>
<CodeBlock.Root
code={file.code}
language={file.language}
meta={{ highlightLines: [2, 3] }}
>
<CodeBlock.Content>
<CodeBlock.Code>
<CodeBlock.CodeText />
</CodeBlock.Code>
</CodeBlock.Content>
</CodeBlock.Root>
</CodeBlock.AdapterProvider>
)
}
const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
async load() {
const { createHighlighter } = await import('shiki')
return createHighlighter({
langs: ['tsx'],
themes: ['github-dark'],
})
},
theme: 'github-dark',
})
Line focus
Pass the meta.focusedLineNumbers prop to the CodeBlock.Root component to
focus specific lines of code. The other lines are dimmed until the code block is
hovered.
const greeting = "Hello, World!"
function sayHello() {
console.log(greeting)
}
sayHello()'use client'
import { CodeBlock, createShikiAdapter } from '@chakra-ui/react'
import type { HighlighterGeneric } from 'shiki'
const file = {
code: `const greeting = "Hello, World!"
function sayHello() {
console.log(greeting)
}
sayHello()`,
language: 'tsx',
title: 'index.tsx',
}
export const CodeBlockWithLineFocus = () => {
return (
<CodeBlock.AdapterProvider value={shikiAdapter}>
<CodeBlock.Root
code={file.code}
language={file.language}
meta={{ focusedLineNumbers: [3, 4, 5] }}
>
<CodeBlock.Content>
<CodeBlock.Code>
<CodeBlock.CodeText />
</CodeBlock.Code>
</CodeBlock.Content>
</CodeBlock.Root>
</CodeBlock.AdapterProvider>
)
}
const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
async load() {
const { createHighlighter } = await import('shiki')
return createHighlighter({
langs: ['tsx'],
themes: ['github-dark'],
})
},
theme: 'github-dark',
})
Diff
Diffs are useful for highlighting source code changes. Use the
meta.addedLineNumbers and meta.removedLineNumbers props to mark lines as
added or removed.
const greeting = "Hello, World!"
function sayHello() {
console.log("Hello, World!")
console.log(greeting)
}
sayHello()'use client'
import { CodeBlock, createShikiAdapter } from '@chakra-ui/react'
import type { HighlighterGeneric } from 'shiki'
const file = {
code: `const greeting = "Hello, World!"
function sayHello() {
console.log("Hello, World!")
console.log(greeting)
}
sayHello()`,
language: 'tsx',
title: 'index.tsx',
}
export const CodeBlockWithDiff = () => {
return (
<CodeBlock.AdapterProvider value={shikiAdapter}>
<CodeBlock.Root
code={file.code}
language={file.language}
meta={{
showLineNumbers: true,
removedLineNumbers: [4],
addedLineNumbers: [5],
}}
>
<CodeBlock.Content>
<CodeBlock.Code>
<CodeBlock.CodeText />
</CodeBlock.Code>
</CodeBlock.Content>
</CodeBlock.Root>
</CodeBlock.AdapterProvider>
)
}
const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
async load() {
const { createHighlighter } = await import('shiki')
return createHighlighter({
langs: ['tsx'],
themes: ['github-dark'],
})
},
theme: 'github-dark',
})
Max lines
Use the maxLines prop to limit the number of lines in the code block
component. By default, the code block component will expand to fit the content.
{
"name": "my-app",
"version": "1.0.0",
"description": "A simple web application",
"main": "index.js",
"scripts": {
"start": "node server.js",
"dev": "next dev",
"build": "next build",
"test": "vitest"
},
"dependencies": {
"@chakra-ui/react": "^3.36.1",
"@saas-ui/react": "^3.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
"author": "Developer",
"license": "MIT"
}'use client'
import { CodeBlock, IconButton, createShikiAdapter } from '@chakra-ui/react'
import type { HighlighterGeneric } from 'shiki'
const file = {
code: `{
"name": "my-app",
"version": "1.0.0",
"description": "A simple web application",
"main": "index.js",
"scripts": {
"start": "node server.js",
"dev": "next dev",
"build": "next build",
"test": "vitest"
},
"dependencies": {
"@chakra-ui/react": "^3.36.1",
"@saas-ui/react": "^3.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
"author": "Developer",
"license": "MIT"
}`,
language: 'json',
title: 'package.json',
}
export const CodeBlockWithMaxLines = () => {
return (
<CodeBlock.AdapterProvider value={shikiAdapter}>
<CodeBlock.Root code={file.code} language={file.language} maxLines={10}>
<CodeBlock.Header>
<CodeBlock.Title>{file.title}</CodeBlock.Title>
<CodeBlock.Control>
<CodeBlock.CollapseTrigger asChild>
<IconButton variant="ghost" size="2xs">
<CodeBlock.CollapseIndicator />
</IconButton>
</CodeBlock.CollapseTrigger>
<CodeBlock.CopyTrigger asChild>
<IconButton variant="ghost" size="2xs">
<CodeBlock.CopyIndicator />
</IconButton>
</CodeBlock.CopyTrigger>
</CodeBlock.Control>
</CodeBlock.Header>
<CodeBlock.Content>
<CodeBlock.Code>
<CodeBlock.CodeText />
</CodeBlock.Code>
<CodeBlock.Overlay>
<CodeBlock.CollapseTrigger>
<CodeBlock.CollapseText textStyle="sm" />
</CodeBlock.CollapseTrigger>
</CodeBlock.Overlay>
</CodeBlock.Content>
</CodeBlock.Root>
</CodeBlock.AdapterProvider>
)
}
const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
async load() {
const { createHighlighter } = await import('shiki')
return createHighlighter({
langs: ['json'],
themes: ['github-dark'],
})
},
theme: 'github-dark',
})
Wrap overflow
Use the meta.wordWrap prop to wrap the code block component.
const greeting = "Hello, World! I am a long line of text that will wrap to the next line."
function sayHello() {
console.log(greeting)
}
sayHello()'use client'
import { CodeBlock, IconButton, createShikiAdapter } from '@chakra-ui/react'
import type { HighlighterGeneric } from 'shiki'
const file = {
code: `const greeting = "Hello, World! I am a long line of text that will wrap to the next line."
function sayHello() {
console.log(greeting)
}
sayHello()`,
language: 'tsx',
title: 'index.tsx',
}
export const CodeBlockWithWordWrap = () => {
return (
<CodeBlock.AdapterProvider value={shikiAdapter}>
<CodeBlock.Root
maxW="md"
code={file.code}
language={file.language}
meta={{ wordWrap: true }}
>
<CodeBlock.Header>
<CodeBlock.Title>{file.title}</CodeBlock.Title>
<CodeBlock.CopyTrigger asChild>
<IconButton variant="ghost" size="2xs">
<CodeBlock.CopyIndicator />
</IconButton>
</CodeBlock.CopyTrigger>
</CodeBlock.Header>
<CodeBlock.Content>
<CodeBlock.Code>
<CodeBlock.CodeText />
</CodeBlock.Code>
</CodeBlock.Content>
</CodeBlock.Root>
</CodeBlock.AdapterProvider>
)
}
const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
async load() {
const { createHighlighter } = await import('shiki')
return createHighlighter({
langs: ['tsx'],
themes: ['github-dark'],
})
},
theme: 'github-dark',
})
Tabs
Here's an example that composes the CodeBlock component with the Tabs
component to create a code block with tabs.
npm install @saas-ui/react'use client'
import {
CodeBlock,
IconButton,
Tabs,
createShikiAdapter,
useTabs,
} from '@chakra-ui/react'
import type { HighlighterGeneric } from 'shiki'
const files = [
{
value: 'npm',
title: 'npm',
language: 'bash',
code: 'npm install @saas-ui/react',
},
{
value: 'pnpm',
title: 'pnpm',
language: 'bash',
code: 'pnpm add @saas-ui/react',
},
{
value: 'yarn',
title: 'yarn',
language: 'bash',
code: 'yarn add @saas-ui/react',
},
]
export const CodeBlockWithTabs = () => {
const tabs = useTabs({
defaultValue: files[0].value,
})
const activeTab = files.find((file) => file.value === tabs.value) ?? files[0]
const otherTabs = files.filter((file) => file.value !== activeTab.value)
return (
<CodeBlock.AdapterProvider value={shikiAdapter}>
<Tabs.RootProvider value={tabs} size="sm" variant="line">
<CodeBlock.Root code={activeTab.code} language={activeTab.language}>
<CodeBlock.Header borderBottomWidth="1px">
<Tabs.List w="full" border="0" ms="-1">
{files.map((file) => (
<Tabs.Trigger
key={file.value}
value={file.value}
textStyle="xs"
>
{file.title}
</Tabs.Trigger>
))}
</Tabs.List>
<CodeBlock.CopyTrigger asChild>
<IconButton variant="ghost" size="2xs">
<CodeBlock.CopyIndicator />
</IconButton>
</CodeBlock.CopyTrigger>
</CodeBlock.Header>
<CodeBlock.Content>
{otherTabs.map((file) => (
<Tabs.Content key={file.value} value={file.value} />
))}
<Tabs.Content pt="1" value={activeTab.value}>
<CodeBlock.Code>
<CodeBlock.CodeText />
</CodeBlock.Code>
</Tabs.Content>
</CodeBlock.Content>
</CodeBlock.Root>
</Tabs.RootProvider>
</CodeBlock.AdapterProvider>
)
}
const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
async load() {
const { createHighlighter } = await import('shiki')
return createHighlighter({
langs: ['bash'],
themes: ['github-dark'],
})
},
theme: 'github-dark',
})
Themes
Pass a { light, dark } theme map to the adapter and set the meta.colorScheme
prop to switch themes. In this example the color scheme is derived from the
useColorMode hook.
'use client'
import { ClientOnly, CodeBlock, createShikiAdapter } from '@chakra-ui/react'
import { useColorMode } from '#components/setup/color-mode/color-mode'
import type { HighlighterGeneric } from 'shiki'
const file = {
code: `<div class="container">
<h1>Hello, world!</h1>
</div>`,
language: 'html',
title: 'index.html',
}
export const CodeBlockWithThemes = () => {
const { colorMode } = useColorMode()
return (
<CodeBlock.AdapterProvider value={shikiAdapter}>
<ClientOnly fallback={<div>Loading...</div>}>
{() => (
<CodeBlock.Root
code={file.code}
language={file.language}
meta={{ colorScheme: colorMode }}
>
<CodeBlock.Content bg="bg">
<CodeBlock.Code>
<CodeBlock.CodeText />
</CodeBlock.Code>
</CodeBlock.Content>
</CodeBlock.Root>
)}
</ClientOnly>
</CodeBlock.AdapterProvider>
)
}
const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
async load() {
const { createHighlighter } = await import('shiki')
return createHighlighter({
langs: ['html'],
themes: ['github-dark', 'github-light'],
})
},
theme: {
light: 'github-light',
dark: 'github-dark',
},
})
Props
Root
| Prop | Default | Type |
|---|---|---|
colorPalette | 'gray' | 'base' | 'gray' | 'zinc' | 'neutral' | 'stone' | 'red' | 'orange' | 'amber' | 'yellow' | 'lime' | 'green' | 'emerald' | 'teal' | 'cyan' | 'sky' | 'blue' | 'indigo' | 'violet' | 'purple' | 'fuchsia' | 'pink' | 'rose' | 'sidebar' | 'sidebar.accent' | 'interaction' | 'accent' | 'presence' | 'status' | 'slate'The color palette of the component |
size | 'md' | 'sm' | 'md' | 'lg'The size of the component |