suiss/ui
  • Docs
  • Components
  • Patterns
  • Interactions
  • Charts
  • Blocks
  • AI
GitHubCreate
DocsComponentsPatternsInteractionsChartsBlocksAICreate
Components84
  • Overview
  • Accordion
  • Alert
  • Alert Dialog
  • Alert Sheet
  • Amount Input
  • Aspect Ratio
  • Attachment
  • Avatar
  • Badge
  • Bottom Bar
  • Breadcrumb
  • Bubble
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Chip
  • Code Block
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • FAB
  • Field
  • Fullscreen Menu
  • Haptics
  • Header
  • Hover Card
  • Icon
  • Input
  • Input Accessory Bar
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Keyboard Scroll View
  • Keypad
  • Label
  • Marker
  • Menubar
  • Message
  • Message Scroller
  • Native Select
  • Navigation Menu
  • Page Indicator
  • Pagination
  • Pin Pad
  • Popover
  • Progress
  • QR Scanner
  • Questionnaire
  • Radio Group
  • Resizable
  • Screen
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Signature Pad
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Tab Bar
  • Table
  • Tabs
  • Text
  • Textarea
  • Toast
  • Toggle
  • Toggle Group
  • Tooltip
  • Trace
  • Wheel Picker
  • Widget

suiss/ui · Component

Code Block

A surface for code, terminal output and diffs, with copy, line numbers, line states and streaming syntax highlighting.

Code Block is an open-source component from suiss/ui for React, React Native, SwiftUI and Jetpack Compose. The @esuiss/ui CLI copies its source into your project, so you own the code.

"use client"

import {
  DownloadSimpleIcon,
  FileCodeIcon,
  TextIndentIcon,
} from "@phosphor-icons/react"
import * as React from "react"
import { Button } from "@/components/ui/button"
import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
  CodeBlockHeader,
  CodeBlockLine,

CodeBlock shows code and command output in a rounded surface. Put the code in CodeBlockContent and, optionally, a CodeBlockHeader with a title and actions above it and a CodeBlockFooter below it.

Pass a string to CodeBlockContent for plain output, or a list of CodeBlockLine when lines need a state or highlighted tokens. useHighlightedCode highlights with Shiki and keeps working while the code streams in.

Installation

CLI

$ pnpm dlx shadcn@latest add @esuiss/code-block

Works in any project with a components.json. See Installation for the one-line registry setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add code-block

Manual

  1. Install the dependencies, add the cn helper and the Button component:
$ pnpm add class-variance-authority shiki
  1. Copy the source into components/ui/code-block.tsx:
components/ui/code-block.tsx
"use client"

import { CheckIcon, CopyIcon } from "@phosphor-icons/react"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react"

import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"

const TRAILING_NEWLINE = /\n$/

const codeBlockVariants = cva(
  "rounded-2xl text-sm data-[size=sm]:text-xs has-[>[data-slot=code-block-content]:focus-visible]:ring-3 has-[>[data-slot=code-block-content]:focus-visible]:ring-ring/50 data-[size=sm]:rounded-xl group/code-block relative flex w-full min-w-0 flex-col overflow-hidden",
  {
    variants: {
      variant: {
  1. For syntax highlighting, copy the highlighter into lib/highlighter.tsx:
lib/highlighter.tsx
import type * as React from "react"
import {
  createHighlighterCore,
  type GrammarState,
  type HighlighterCore,
} from "shiki/core"
import { createJavaScriptRegexEngine } from "shiki/engine/javascript"

const languages = {
  bash: () => import("shiki/langs/shellscript.mjs"),
  css: () => import("shiki/langs/css.mjs"),
  diff: () => import("shiki/langs/diff.mjs"),
  html: () => import("shiki/langs/html.mjs"),
  javascript: () => import("shiki/langs/javascript.mjs"),
  json: () => import("shiki/langs/json.mjs"),
  jsx: () => import("shiki/langs/jsx.mjs"),
  1. Copy the hook into hooks/use-highlighted-code.ts:
hooks/use-highlighted-code.ts
import * as React from "react"

import { loadHighlighter } from "@/lib/highlighter"

export function useHighlightedCode(value: string, language: string) {
  const code = value.endsWith("\n") ? value.slice(0, -1) : value
  const [loaded, setLoaded] = React.useState<{
    language: string
    highlighter: Awaited<ReturnType<typeof loadHighlighter>>
  } | null>(null)
  const highlighter = loaded?.language === language ? loaded.highlighter : null

  React.useEffect(() => {
    let cancelled = false

    loadHighlighter(language)
  1. Update the import paths to match your project.

Usage

import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
  CodeBlockFooter,
  CodeBlockHeader,
  CodeBlockLine,
  CodeBlockTitle,
} from "@/components/ui/code-block"
<CodeBlock>
  <CodeBlockHeader>
    <CodeBlockTitle>Terminal</CodeBlockTitle>
    <CodeBlockActions>
      <CodeBlockCopyButton value={output} />
    </CodeBlockActions>
  </CodeBlockHeader>
  <CodeBlockContent>{output}</CodeBlockContent>
</CodeBlock>

A string child renders as a single text node, so streamed output stays cheap. Line numbers and diff markers are left out of a selection, and the content always reads left to right, while the header follows the page direction.

Examples

Basic

A string passed straight to CodeBlockContent.

import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"

const output = `$ pnpm build
> next build

 ✓ Compiled successfully in 4.2s
 ✓ Generating static pages (12/12)

Route (app)                Size  First Load JS
┌ ○ /                    1.9 kB         112 kB
├ ○ /_not-found           993 B         103 kB
└ ƒ /api/chat             141 B         102 kB`

export default function CodeBlockBasic() {
  return (
    <div className="w-full max-w-md">

Header

A CodeBlockHeader with a title and a copy button. Long titles truncate.

import { TerminalWindowIcon } from "@phosphor-icons/react"
import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
  CodeBlockHeader,
  CodeBlockTitle,
} from "@/components/ui/code-block"

const command = "pnpm add ai @ai-sdk/react zod"

const output = `$ ${command}
Packages: +14
Progress: resolved 412, reused 398, downloaded 14, added 14, done
Done in 2.8s using pnpm v10.18.0`

Actions

Extra buttons next to the copy button. The wrap toggle keeps its own state and passes it to wrap.

"use client"

import {
  DownloadSimpleIcon,
  TerminalWindowIcon,
  TextIndentIcon,
} from "@phosphor-icons/react"
import * as React from "react"
import { Button } from "@/components/ui/button"
import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
  CodeBlockHeader,
  CodeBlockTitle,

Floating actions

Without a header, place CodeBlockActions directly in CodeBlock. The actions float at the top end and appear on hover or focus, and always on touch screens.

import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
} from "@/components/ui/code-block"

const output = `$ pnpm add ai @ai-sdk/react zod
Progress: resolved 412, reused 398, downloaded 14, added 14, done
dependencies:
+ @ai-sdk/react 3.0.12
+ ai 6.0.12
+ zod 4.1.12
Done in 2.8s using pnpm v10.18.0`

export default function CodeBlockFloatingActions() {

Line numbers

Set lineNumbers on CodeBlockContent. A string child is split into lines.

import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"

const code = `{
  "name": "chat",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build"
  }
}`

export default function CodeBlockLineNumbers() {
  return (
    <div className="w-full max-w-md">
      <CodeBlock>
        <CodeBlockContent lineNumbers>{code}</CodeBlockContent>

Start line

Set startLine to number a snippet from the middle of a file. The gutter widens to fit the largest number.

"use client"

import {
  CodeBlock,
  CodeBlockContent,
  CodeBlockHeader,
  CodeBlockLine,
  CodeBlockTitle,
} from "@/components/ui/code-block"
import { useHighlightedCode } from "@/hooks/use-highlighted-code"

const code = `  if (!session) {
    redirect("/login")
  }

  const projects = await db.project.findMany({

Wrap

Lines scroll by default. Set wrap for tool output and error messages.

import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"

const output = `Error: The model "anthropic/claude-sonnet-4.5" returned an error: Request too large. The prompt is 212,431 tokens and the context window is 200,000 tokens. Remove some messages or summarize the conversation and try again.`

export default function CodeBlockWrap() {
  return (
    <div className="flex w-full max-w-md flex-col gap-4">
      <CodeBlock>
        <CodeBlockContent>{output}</CodeBlockContent>
      </CodeBlock>
      <CodeBlock>
        <CodeBlockContent wrap>{output}</CodeBlockContent>
      </CodeBlock>
    </div>
  )
}

Highlighted lines

Render each line as a CodeBlockLine and set variant="highlighted" on the ones to call out.

import {
  CodeBlock,
  CodeBlockContent,
  CodeBlockLine,
} from "@/components/ui/code-block"

const code = `export default {
  experimental: {
    reactCompiler: true,
    viewTransition: true,
  },
  images: {
    remotePatterns: [{ hostname: "avatars.githubusercontent.com" }],
  },
}`

Diff

variant="added" and variant="removed" tint a line and add a + or − marker.

import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
  CodeBlockHeader,
  CodeBlockLine,
  CodeBlockTitle,
} from "@/components/ui/code-block"

const patch = ` export function Greeting({ name }: { name: string }) {
-  return <p>Hello, {name}</p>
+  return <p className="text-sm text-muted-foreground">
+    Hello, {name}!
+  </p>
 }`

Height

A max-h-* class on CodeBlockContent caps the block. The content scrolls inside and its edges fade with scroll-fade.

import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
  CodeBlockHeader,
  CodeBlockTitle,
} from "@/components/ui/code-block"

const logs = Array.from(
  { length: 40 },
  (_, index) =>
    `[${String(index + 1).padStart(2, "0")}] GET /api/chat 200 in ${12 + ((index * 7) % 40)}ms`
).join("\n")

export default function CodeBlockHeight() {

Show more

Cap the content while collapsed and toggle it from a ghost button in CodeBlockFooter.

"use client"

import { CaretDownIcon } from "@phosphor-icons/react"
import * as React from "react"
import { Button } from "@/components/ui/button"
import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
  CodeBlockFooter,
  CodeBlockHeader,
  CodeBlockLine,
  CodeBlockTitle,
} from "@/components/ui/code-block"
import { useHighlightedCode } from "@/hooks/use-highlighted-code"

Show more with max height

Raise the cap instead of removing it, so a long expanded block still scrolls inside the page.

"use client"

import { CaretDownIcon } from "@phosphor-icons/react"
import * as React from "react"
import { Button } from "@/components/ui/button"
import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
  CodeBlockFooter,
  CodeBlockHeader,
  CodeBlockLine,
  CodeBlockTitle,
} from "@/components/ui/code-block"
import { useHighlightedCode } from "@/hooks/use-highlighted-code"

Size

size="sm" tightens the padding and uses text-xs, for tool and trace output.

import { TerminalWindowIcon } from "@phosphor-icons/react"
import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
  CodeBlockHeader,
  CodeBlockTitle,
} from "@/components/ui/code-block"

const output = `$ pnpm add ai @ai-sdk/react zod
Progress: resolved 412, reused 398, downloaded 14, added 14, done
Done in 2.8s using pnpm v10.18.0`

export default function CodeBlockSize() {
  return (

Destructive

variant="destructive" for errors and failed output.

import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
  CodeBlockHeader,
  CodeBlockTitle,
} from "@/components/ui/code-block"

const error = `TypeError: Cannot read properties of undefined (reading 'map')
    at MessageList (components/message-list.tsx:18:22)
    at renderWithHooks (react-dom.development.js:16305:18)`

export default function CodeBlockDestructive() {
  return (
    <div className="w-full max-w-md">

Trace

A small, wrapping CodeBlock inside a collapsible trace step for terminal output.

import { TerminalWindowIcon } from "@phosphor-icons/react"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

const output = `$ node --version
v24.18.0`

const error = `$ pnpm dev

Tabs

Several files in Tabs, one CodeBlock per panel.

"use client"

import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
  CodeBlockHeader,
  CodeBlockLine,
  CodeBlockTitle,
} from "@/components/ui/code-block"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { useHighlightedCode } from "@/hooks/use-highlighted-code"

const files = [
  {

Message

A CodeBlock between paragraphs of a Message.

"use client"

import { Bubble, BubbleContent } from "@/components/ui/bubble"
import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
  CodeBlockHeader,
  CodeBlockLine,
  CodeBlockTitle,
} from "@/components/ui/code-block"
import { Message, MessageContent } from "@/components/ui/message"
import { useHighlightedCode } from "@/hooks/use-highlighted-code"

const code = `import { streamText } from "ai"

Syntax highlighting

useHighlightedCode returns one React node per line, ready for CodeBlockLine.

"use client"

import {
  CodeBlock,
  CodeBlockContent,
  CodeBlockHeader,
  CodeBlockLine,
  CodeBlockTitle,
} from "@/components/ui/code-block"
import { useHighlightedCode } from "@/hooks/use-highlighted-code"

const code = `{
  "location": "San Francisco",
  "temperature": 18,
  "unit": "celsius",
  "conditions": ["fog", "wind"]
const lines = useHighlightedCode(code, "json")

<CodeBlock>
  <CodeBlockContent>
    {lines.map((line, index) => (
      <CodeBlockLine key={index}>{line}</CodeBlockLine>
    ))}
  </CodeBlockContent>
</CodeBlock>

Shiki runs on its JavaScript regex engine, so there is no WASM to load.

Languages

Each language loads on first use. The list lives in lib/highlighter.tsx and ships with bash, css, diff, html, javascript, json, jsx, markdown, python, sql, tsx, typescript and yaml, plus aliases such as ts, js, sh and yml. Any other language renders as plain text.

"use client"

import {
  CodeBlock,
  CodeBlockContent,
  CodeBlockHeader,
  CodeBlockLine,
  CodeBlockTitle,
} from "@/components/ui/code-block"
import { useHighlightedCode } from "@/hooks/use-highlighted-code"

const samples = [
  {
    language: "css",
    title: "app/globals.css",
    code: `@import "tailwindcss";

Terminal output

Pass "ansi" as the language to turn escape codes into colors. Colors do not carry over from one line to the next.

"use client"

import { TerminalWindowIcon } from "@phosphor-icons/react"
import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
  CodeBlockHeader,
  CodeBlockLine,
  CodeBlockTitle,
} from "@/components/ui/code-block"
import { useHighlightedCode } from "@/hooks/use-highlighted-code"

const ANSI_CODES = new RegExp(`${String.fromCharCode(27)}\\[[\\d;]*m`, "g")

Streaming

While code streams in, the hook tokenizes only the new lines. Unchanged lines keep their nodes, so React skips them.

"use client"

import { ArrowCounterClockwiseIcon } from "@phosphor-icons/react"
import * as React from "react"
import { Button } from "@/components/ui/button"
import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
  CodeBlockHeader,
  CodeBlockLine,
  CodeBlockTitle,
} from "@/components/ui/code-block"
import { useHighlightedCode } from "@/hooks/use-highlighted-code"

Server rendering

highlightCode is async and runs in a Server Component, which keeps Shiki out of the client bundle.

import { highlightCode } from "@/lib/highlighter"

export async function ServerCode({ code }: { code: string }) {
  const lines = (await highlightCode(code, "tsx")) ?? code.split("\n")

  return (
    <CodeBlock>
      <CodeBlockContent lineNumbers>
        {lines.map((line, index) => (
          <CodeBlockLine key={index}>{line}</CodeBlockLine>
        ))}
      </CodeBlockContent>
    </CodeBlock>
  )
}

API Reference

Anatomy8 parts
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle />
<CodeBlockActions>
<CodeBlockCopyButton />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent>
<CodeBlockLine />
</CodeBlockContent>
<CodeBlockFooter />
</CodeBlock>
01

CodeBlock

The surface that holds the header, the content and the footer.

PropTypeDefaultDescription
variant"default" | "destructive""default"The tone of the block.
size"default" | "sm""default"sm uses tighter padding and text-xs, for tool output.

Also accepts the props of React.ComponentProps<"div">.

02

CodeBlockHeader

A row above the content for a title and actions.

Also accepts the props of React.ComponentProps<"div">.

03

CodeBlockTitle

A filename or label with an optional icon. It truncates.

Also accepts the props of React.ComponentProps<"div">.

04

CodeBlockActions

Buttons at the end of the header. As a direct child of CodeBlock, it floats over the content.

Also accepts the props of React.ComponentProps<"div">.

05

CodeBlockCopyButton

A ghost icon button that copies value and announces "Copied" in a polite live region.

PropTypeDefaultDescription
valuestring-Required. The text to copy.

Also accepts the props of Button.

06

CodeBlockContent

The pre and code pair that scrolls or wraps.

PropTypeDefaultDescription
childrenReact.ReactNode-A string, or a list of CodeBlockLine.
wrapbooleanfalseWraps long lines instead of scrolling.
lineNumbersbooleanfalseNumbers each line. A string child is split into lines.
startLinenumber1The number of the first line when lineNumbers is set.
tabIndexnumber0Keeps the content focusable so it scrolls by keyboard.

Also accepts the props of React.ComponentProps<"pre">.

07

CodeBlockLine

One line of code.

PropTypeDefaultDescription
variant"default" | "added" | "removed" | "highlighted""default"The state of the line.

Also accepts the props of React.ComponentProps<"span">.

08

CodeBlockFooter

A row below the content, such as for a Show more button.

Also accepts the props of React.ComponentProps<"div">.

useHighlightedCode

const lines = useHighlightedCode(code, language)

Returns one React node per line. Until the first pass finishes, and for a language outside the list, it returns plain lines.

ArgumentTypeDescription
codestringThe code to highlight. A trailing newline is ignored.
languagestringA language or alias from lib/highlighter.tsx, or "ansi".

Accessibility

  • CodeBlockContent renders a pre with a code element and always sets dir="ltr", so code reads left to right on a right-to-left page.
  • The content has tabIndex={0} so keyboard users can focus and scroll it. Pass tabIndex={-1} to remove the tab stop.
  • Floating actions appear when focus is inside the block, not only on hover.
  • Line numbers are hidden from screen readers.
  • Added and removed lines carry a + or − marker as well as a tint, so the change does not rely on color.
  • CodeBlockCopyButton is labelled "Copy" and announces "Copied" after a successful copy.
  • Give a wrap toggle aria-pressed, and a Show more button aria-expanded with aria-controls pointing at the content id.

Notes

  • Styling hooks: data-slot="code-block", code-block-header, code-block-title, code-block-actions, code-block-copy-button, code-block-content, code-block-code, code-block-line and code-block-footer. The root exposes data-variant and data-size, and each line exposes data-variant.
import { FileTsIcon } from "phosphor-react-native"
import { CodeBlock, CodeBlockActions, CodeBlockContent, CodeBlockCopyButton, CodeBlockHeader, CodeBlockTitle } from "@/components/ui/code-block"
import { Icon } from "@/components/ui/icon"
import { Text } from "@/components/ui/text"

const greeting = `export function Greeting({ name }: { name: string }) {
  // Say hello to the person signing in
  return <Text>Hello, {name}!</Text>
}`

export function CodeBlockExample() {
  return (
    <CodeBlock>
      <CodeBlockHeader>
        <Icon as={FileTsIcon} />
        <CodeBlockTitle>components/greeting.tsx</CodeBlockTitle>
import EsuissUI
import PhosphorSwift
import SwiftUI

struct CodeBlockExample: View {
  var body: some View {
    CodeBlock {
      CodeBlockHeader {
        Ph.fileTs.regular.frame(width: 16, height: 16)
        CodeBlockTitle(verbatim: "components/greeting.tsx")
        CodeBlockActions { CodeBlockCopyButton(value: greeting) }
      }
      CodeBlockContent(greeting, language: "tsx")
    }
  }
}
import androidx.compose.runtime.Composable
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.FileTs
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockActions
import com.esuiss.ui.components.CodeBlockContent
import com.esuiss.ui.components.CodeBlockCopyButton
import com.esuiss.ui.components.CodeBlockHeader
import com.esuiss.ui.components.CodeBlockTitle
import com.esuiss.ui.components.Icon

@Composable
fun CodeBlockDefault() {
    CodeBlock {
        CodeBlockHeader {

A CodeBlock shows code in a rounded, muted surface. Put a CodeBlockTitle and CodeBlockActions with a CodeBlockCopyButton in a CodeBlockHeader, the code in CodeBlockContent, and secondary text in a CodeBlockFooter.

CodeBlockContent takes a string or CodeBlockLine children. Long lines scroll sideways unless you set wrap. Pass language to color keywords, strings, numbers and comments with a small built-in highlighter that uses the theme colors, so it follows light and dark mode and every style.

Installation

$ pnpm dlx shadcn@latest add @esuiss-rn/code-block

See Installation for the components.json setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add code-block
$ pnpm dlx @esuiss/ui@latest add code-block --platform swift
$ pnpm dlx @esuiss/ui@latest add code-block --platform kotlin

The CLI also adds what it builds on: Icon, Text.

Manual

  1. Install the dependencies:
npx expo install class-variance-authority cn expo-clipboard phosphor-react-native react-native-svg
  1. Add the pieces it builds on: Icon, Text.

  2. Copy the source into components/ui/code-block.tsx:

uniwind/components/ui/code-block.tsx
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import { setStringAsync } from "expo-clipboard"
import { CheckIcon, CopyIcon } from "phosphor-react-native"
import * as React from "react"
import { Pressable, ScrollView, View, type ViewProps } from "react-native"
import { Icon, IconClassContext } from "@/components/ui/icon"
import { Text, TextClassContext } from "@/components/ui/text"

type CodeBlockSize = "default" | "sm"
type CodeBlockVariant = "default" | "destructive"
type TokenKind = "plain" | "keyword" | "string" | "number" | "comment"

const KEYWORDS: Record<string, string[]> = {
  script: [
    "as",
  1. Update the import paths to match your project.
  1. Install the dependencies:
npx expo install class-variance-authority cn expo-clipboard phosphor-react-native react-native-svg
  1. Add the pieces it builds on: Icon, Text.

  2. Copy the source into components/ui/code-block.tsx:

nativewind/components/ui/code-block.tsx
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import { setStringAsync } from "expo-clipboard"
import { CheckIcon, CopyIcon } from "phosphor-react-native"
import * as React from "react"
import { Pressable, ScrollView, View, type ViewProps } from "react-native"
import { Icon, IconClassContext } from "@/components/ui/icon"
import { Text, TextClassContext } from "@/components/ui/text"

type CodeBlockSize = "default" | "sm"
type CodeBlockVariant = "default" | "destructive"
type TokenKind = "plain" | "keyword" | "string" | "number" | "comment"

const KEYWORDS: Record<string, string[]> = {
  script: [
    "as",
  1. Update the import paths to match your project.
  1. Install the dependencies:
npx expo install expo-clipboard phosphor-react-native react-native-svg
  1. Add the pieces it builds on: Icon, Text.

  2. Copy the source into components/ui/code-block.tsx:

stylesheet/components/ui/code-block.tsx
import { setStringAsync } from "expo-clipboard"
import { CheckIcon, CopyIcon } from "phosphor-react-native"
import * as React from "react"
import {
  Pressable,
  ScrollView,
  StyleSheet,
  type TextStyle,
  View,
  type ViewProps,
} from "react-native"
import { Icon, IconSizeContext } from "@/components/ui/icon"
import { Text, TextStyleContext } from "@/components/ui/text"
import {
  fontSize,
  radius,
  1. Update the import paths to match your project.
  1. Target iOS 17 or later.

  2. Copy the theme files every view uses:

swift/Theme/Theme.swift
import CoreText
import SwiftUI
import UIKit

struct RGBA: Sendable {
  let red: CGFloat
  let green: CGFloat
  let blue: CGFloat
  let alpha: CGFloat

  init(_ hex: UInt32, _ alpha: CGFloat = 1) {
    red = CGFloat((hex >> 16) & 0xFF) / 255
    green = CGFloat((hex >> 8) & 0xFF) / 255
    blue = CGFloat(hex & 0xFF) / 255
    self.alpha = alpha
  }
swift/Theme/Tokens.swift
import SwiftUI

public enum EsuissColor {
  public static let background = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))
  public static let foreground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let card = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
  public static let cardForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let popover = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
  public static let popoverForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let primary = Color.adaptive(light: .init(0x0071E3), dark: .init(0x2997FF))
  public static let primaryForeground = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0xFFFFFF))
  public static let secondary = Color.adaptive(light: .init(0x000000, 0.04), dark: .init(0xFFFFFF, 0.1))
  public static let secondaryForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let muted = Color.adaptive(light: .init(0xF5F5F7), dark: .init(0x161617))
  public static let mutedForeground = Color.adaptive(light: .init(0x6E6E73), dark: .init(0xA1A1A6))
  public static let accent = Color.adaptive(light: .init(0x000000, 0.06), dark: .init(0xFFFFFF, 0.1))
  1. Copy the source and every file it uses:
swift/Components/CodeBlock.swift
import PhosphorSwift
import SwiftUI
import UIKit

public enum CodeBlockVariant: CaseIterable, Sendable {
  case `default`, destructive
}

public enum CodeBlockSize: CaseIterable, Sendable {
  case `default`, sm

  var padding: CGFloat { self == .sm ? 12 : 16 }
  var radius: CGFloat { self == .sm ? 12 : 18 }
  var text: TextStyle { self == .sm ? .xs : .sm }
}
  1. Add the files to your app target.
  1. Set minSdk to 24 or higher and add the dependencies:
build.gradle.kts
implementation(platform("androidx.compose:compose-bom:2026.09.00"))
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.ui:ui")
  1. Copy the theme files every view uses:
kotlin/theme/Theme.kt
package com.esuiss.ui.theme

import android.content.res.AssetManager
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.dropShadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.shadow.Shadow as DropShadow
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
kotlin/theme/Tokens.kt
package com.esuiss.ui.theme

import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp

@Immutable
data class Colors(
    val background: Color,
    val foreground: Color,
    val card: Color,
    val cardForeground: Color,
    val popover: Color,
    val popoverForeground: Color,
    val primary: Color,
  1. Copy the source and every file it uses:
kotlin/components/CodeBlock.kt
package com.esuiss.ui.components

import android.content.ClipData
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
kotlin/components/Button.kt
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
kotlin/components/ButtonGroup.kt
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Immutable
kotlin/components/Content.kt
package com.esuiss.ui.components

import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

val LocalContentStyle = compositionLocalOf { TextStyle.Default }

val LocalContentColor = compositionLocalOf { Color.Unspecified }

val LocalIconSize = compositionLocalOf { 20.dp }
kotlin/components/Icon.kt
package com.esuiss.ui.components

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Dp
import com.esuiss.ui.theme.EsuissTheme

@Composable
fun Icon(
    imageVector: ImageVector,
  1. Update the com.esuiss.ui package names to match your project.

Usage

import { CodeBlock, CodeBlockActions, CodeBlockContent, CodeBlockCopyButton, CodeBlockFooter, CodeBlockHeader, CodeBlockLine, CodeBlockTitle } from "@/components/ui/code-block"
<CodeBlock>
  <CodeBlockHeader>
    <CodeBlockTitle>components/greeting.tsx</CodeBlockTitle>
    <CodeBlockActions>
      <CodeBlockCopyButton value={code} />
    </CodeBlockActions>
  </CodeBlockHeader>
  <CodeBlockContent language="tsx">{code}</CodeBlockContent>
</CodeBlock>

In SwiftUI and Compose, pass the code as a string or a list of CodeBlockLine values to CodeBlockContent. The copy button uses expo-clipboard in React Native, UIPasteboard in SwiftUI and the Compose clipboard on Android.

Examples

Basic

A single line with no header.

import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"

export function CodeBlockBasicExample() {
  return (
    <CodeBlock>
      <CodeBlockContent>npx @esuiss/ui@latest add code-block</CodeBlockContent>
    </CodeBlock>
  )
}
import EsuissUI
import SwiftUI

struct CodeBlockBasicExample: View {
  var body: some View {
    CodeBlock {
      CodeBlockContent("npx @esuiss/ui@latest add code-block")
    }
  }
}
import androidx.compose.runtime.Composable
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent

@Composable
fun CodeBlockBasicExample() {
    CodeBlock { CodeBlockContent("npx @esuiss/ui@latest add code-block") }
}

Line numbers

Numbers every line with lineNumbers.

import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"

const config = `export default {
  experimental: {
    reactCompiler: true,
    viewTransition: true,
  },
  images: {
    remotePatterns: [{ hostname: "avatars.githubusercontent.com" }],
  },
}`

export function CodeBlockLineNumbersExample() {
  return (
    <CodeBlock>
      <CodeBlockContent language="ts" lineNumbers>
import EsuissUI
import SwiftUI

struct CodeBlockLineNumbersExample: View {
  var body: some View {
    CodeBlock {
      CodeBlockContent(config, language: "ts", lineNumbers: true)
    }
  }
}

let config = """
import androidx.compose.runtime.Composable
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent

@Composable
fun CodeBlockLineNumbersExample() {
    CodeBlock { CodeBlockContent(CONFIG, language = "ts", lineNumbers = true) }
}

Start line

Starts the numbering at startLine for an excerpt of a longer file.

import { CodeBlock, CodeBlockContent, CodeBlockHeader, CodeBlockLine, CodeBlockTitle } from "@/components/ui/code-block"

const config = `export default {
  experimental: {
    reactCompiler: true,
    viewTransition: true,
  },
  images: {
    remotePatterns: [{ hostname: "avatars.githubusercontent.com" }],
  },
}`

export function CodeBlockStartLineExample() {
  return (
    <CodeBlock>
      <CodeBlockHeader>
import EsuissUI
import SwiftUI

struct CodeBlockStartLineExample: View {
  var body: some View {
    CodeBlock {
      CodeBlockHeader { CodeBlockTitle(verbatim: "next.config.ts") }
      CodeBlockContent(
        lines: [CodeBlockLine("  images: {"), CodeBlockLine("    unoptimized: true,"), CodeBlockLine("  },")],
        lineNumbers: true,
        startLine: 12
      )
    }
  }
}
import androidx.compose.runtime.Composable
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent
import com.esuiss.ui.components.CodeBlockHeader
import com.esuiss.ui.components.CodeBlockLine
import com.esuiss.ui.components.CodeBlockTitle

@Composable
fun CodeBlockStartLine() {
    CodeBlock {
        CodeBlockHeader { CodeBlockTitle("next.config.ts") }
        CodeBlockContent(
            lines = listOf(CodeBlockLine("  images: {"), CodeBlockLine("    unoptimized: true,"), CodeBlockLine("  },")),
            lineNumbers = true,
            startLine = 12,
        )

Wrap

Wraps long lines instead of scrolling sideways.

import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"

export function CodeBlockWrapExample() {
  return (
    <CodeBlock>
      <CodeBlockContent wrap>
        ERR_PNPM_OUTDATED_LOCKFILE Cannot install with
        &quot;frozen-lockfile&quot; because pnpm-lock.yaml is not up to date
        with package.json
      </CodeBlockContent>
    </CodeBlock>
  )
}
import EsuissUI
import SwiftUI

struct CodeBlockWrapExample: View {
  var body: some View {
    CodeBlock {
      CodeBlockContent(
        "ERR_PNPM_OUTDATED_LOCKFILE Cannot install with \"frozen-lockfile\" because pnpm-lock.yaml is not up to date with package.json",
        wrap: true
      )
    }
  }
}
import androidx.compose.runtime.Composable
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent

@Composable
fun CodeBlockWrapExample() {
    CodeBlock {
        CodeBlockContent(
            "ERR_PNPM_OUTDATED_LOCKFILE Cannot install with \"frozen-lockfile\" because pnpm-lock.yaml is not up to date with package.json",
            wrap = true,
        )
    }
}

Highlighted lines

CodeBlockLine with variant="highlighted" tints the lines that matter.

import { CodeBlock, CodeBlockContent, CodeBlockLine } from "@/components/ui/code-block"

export function CodeBlockHighlightedLinesExample() {
  return (
    <CodeBlock>
      <CodeBlockContent lineNumbers>
        <CodeBlockLine>{"export default {"}</CodeBlockLine>
        <CodeBlockLine>{"  experimental: {"}</CodeBlockLine>
        <CodeBlockLine variant="highlighted">
          {"    reactCompiler: true,"}
        </CodeBlockLine>
        <CodeBlockLine variant="highlighted">
          {"    viewTransition: true,"}
        </CodeBlockLine>
        <CodeBlockLine>{"  },"}</CodeBlockLine>
        <CodeBlockLine>{"}"}</CodeBlockLine>
import EsuissUI
import SwiftUI

struct CodeBlockHighlightedLinesExample: View {
  var body: some View {
    CodeBlock {
      CodeBlockContent(
        lines: [
          CodeBlockLine("export default {"),
          CodeBlockLine("  experimental: {"),
          CodeBlockLine("    reactCompiler: true,", variant: .highlighted),
          CodeBlockLine("    viewTransition: true,", variant: .highlighted),
          CodeBlockLine("  },"),
          CodeBlockLine("}"),
        ],
        lineNumbers: true
import androidx.compose.runtime.Composable
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent
import com.esuiss.ui.components.CodeBlockLine
import com.esuiss.ui.components.CodeBlockLineVariant

@Composable
fun CodeBlockHighlighted() {
    CodeBlock {
        CodeBlockContent(
            lines = listOf(
                CodeBlockLine("export default {"),
                CodeBlockLine("  experimental: {"),
                CodeBlockLine("    reactCompiler: true,", CodeBlockLineVariant.Highlighted),
                CodeBlockLine("    viewTransition: true,", CodeBlockLineVariant.Highlighted),
                CodeBlockLine("  },"),

Diff

added and removed lines get a tint and a + or − marker.

import { CodeBlock, CodeBlockContent, CodeBlockHeader, CodeBlockLine, CodeBlockTitle } from "@/components/ui/code-block"
import { Text } from "@/components/ui/text"

const greeting = `export function Greeting({ name }: { name: string }) {
  // Say hello to the person signing in
  return <Text>Hello, {name}!</Text>
}`

export function CodeBlockDiffExample() {
  return (
    <CodeBlock>
      <CodeBlockHeader>
        <CodeBlockTitle>components/greeting.tsx</CodeBlockTitle>
      </CodeBlockHeader>
      <CodeBlockContent>
        <CodeBlockLine>
import EsuissUI
import SwiftUI

struct CodeBlockDiffExample: View {
  var body: some View {
    CodeBlock {
      CodeBlockHeader { CodeBlockTitle(verbatim: "components/greeting.tsx") }
      CodeBlockContent(lines: [
        CodeBlockLine("export function Greeting({ name }: { name: string }) {"),
        CodeBlockLine("  return <Text>Hello, {name}</Text>", variant: .removed),
        CodeBlockLine("  return <Text variant=\"muted\">", variant: .added),
        CodeBlockLine("    Hello, {name}!", variant: .added),
        CodeBlockLine("  </Text>", variant: .added),
        CodeBlockLine("}"),
      ])
    }
import androidx.compose.runtime.Composable
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent
import com.esuiss.ui.components.CodeBlockHeader
import com.esuiss.ui.components.CodeBlockLine
import com.esuiss.ui.components.CodeBlockLineVariant
import com.esuiss.ui.components.CodeBlockTitle
import com.esuiss.ui.components.Text

@Composable
fun CodeBlockDiff() {
    CodeBlock {
        CodeBlockHeader { CodeBlockTitle("components/greeting.tsx") }
        CodeBlockContent(
            lines = listOf(
                CodeBlockLine("export function Greeting({ name }: { name: string }) {"),

Size

The sm size for code inside a message or a trace.

import { View } from "react-native"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"

export function CodeBlockSizeExample() {
  return (
    <View className="gap-4">
      <CodeBlock>
        <CodeBlockContent>pnpm install</CodeBlockContent>
      </CodeBlock>
      <CodeBlock size="sm">
        <CodeBlockContent>pnpm install</CodeBlockContent>
      </CodeBlock>
    </View>
  )
}
import EsuissUI
import SwiftUI

struct CodeBlockSizeExample: View {
  var body: some View {
    VStack(spacing: 16) {
      CodeBlock { CodeBlockContent("pnpm install") }
      CodeBlock(size: .sm) { CodeBlockContent("pnpm install") }
    }
  }
}
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent
import com.esuiss.ui.components.CodeBlockSize

@Composable
fun CodeBlockSizes() {
    Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp)) {
        CodeBlock { CodeBlockContent("pnpm install") }
        CodeBlock(size = CodeBlockSize.Sm) { CodeBlockContent("pnpm install") }
    }

Destructive

The destructive variant for errors.

import { TerminalIcon } from "phosphor-react-native"
import { CodeBlock, CodeBlockContent, CodeBlockHeader, CodeBlockTitle } from "@/components/ui/code-block"
import { Icon } from "@/components/ui/icon"

export function CodeBlockDestructiveExample() {
  return (
    <CodeBlock variant="destructive" size="sm">
      <CodeBlockHeader>
        <Icon as={TerminalIcon} />
        <CodeBlockTitle>Failed to run pnpm lint</CodeBlockTitle>
      </CodeBlockHeader>
      <CodeBlockContent wrap>
        {"app/page.tsx:12:7 Unexpected any. Specify a different type."}
      </CodeBlockContent>
    </CodeBlock>
  )
import EsuissUI
import PhosphorSwift
import SwiftUI

struct CodeBlockDestructiveExample: View {
  var body: some View {
    CodeBlock(.destructive, size: .sm) {
      CodeBlockHeader {
        Ph.terminal.regular.frame(width: 16, height: 16)
        CodeBlockTitle("Failed to run pnpm lint")
      }
      CodeBlockContent("app/page.tsx:12:7 Unexpected any. Specify a different type.", wrap: true)
    }
  }
}
import androidx.compose.runtime.Composable
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.Terminal
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent
import com.esuiss.ui.components.CodeBlockHeader
import com.esuiss.ui.components.CodeBlockSize
import com.esuiss.ui.components.CodeBlockTitle
import com.esuiss.ui.components.CodeBlockVariant
import com.esuiss.ui.components.Icon

@Composable
fun CodeBlockDestructive() {
    CodeBlock(variant = CodeBlockVariant.Destructive, size = CodeBlockSize.Sm) {
        CodeBlockHeader {

Languages

The built-in highlighter colors TypeScript, JSON, shell, Swift and Kotlin with the chart colors of the theme.

import { View } from "react-native"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import { Text } from "@/components/ui/text"

export function CodeBlockLanguagesExample() {
  return (
    <View className="gap-4">
      <CodeBlock size="sm">
        <CodeBlockContent language="bash">
          {"# Add the component\npnpm dlx @esuiss/ui add code-block"}
        </CodeBlockContent>
      </CodeBlock>
      <CodeBlock size="sm">
        <CodeBlockContent language="json">
          {'{ "name": "acme", "private": true, "version": "1.0.0" }'}
        </CodeBlockContent>
import EsuissUI
import SwiftUI

struct CodeBlockLanguagesExample: View {
  var body: some View {
    VStack(spacing: 16) {
      CodeBlock(size: .sm) { CodeBlockContent("# Add the component\npnpm dlx @esuiss/ui add code-block", language: "bash") }
      CodeBlock(size: .sm) { CodeBlockContent("{ \"name\": \"acme\", \"private\": true, \"version\": \"1.0.0\" }", language: "json") }
      CodeBlock(size: .sm) {
        CodeBlockContent("struct Greeting: View {\n  let name: String\n  var body: some View { Text(\"Hello, \\(name)\") }\n}", language: "swift")
      }
      CodeBlock(size: .sm) {
        CodeBlockContent("@Composable\nfun Greeting(name: String) {\n  Text(\"Hello, $name\")\n}", language: "kotlin")
      }
    }
  }
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent
import com.esuiss.ui.components.CodeBlockSize
import com.esuiss.ui.components.Text

@Composable
fun CodeBlockLanguages() {
    Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp)) {
        CodeBlock(size = CodeBlockSize.Sm) { CodeBlockContent("# Add the component\npnpm dlx @esuiss/ui add code-block", language = "bash") }
        CodeBlock(size = CodeBlockSize.Sm) { CodeBlockContent("{ \"name\": \"acme\", \"private\": true, \"version\": \"1.0.0\" }", language = "json") }

Footer

A CodeBlockFooter with secondary text below the code.

import { CodeBlock, CodeBlockActions, CodeBlockContent, CodeBlockCopyButton, CodeBlockFooter, CodeBlockHeader, CodeBlockTitle } from "@/components/ui/code-block"
import { Text } from "@/components/ui/text"

export function CodeBlockFooterExample() {
  return (
    <CodeBlock>
      <CodeBlockHeader>
        <CodeBlockTitle>Logs</CodeBlockTitle>
        <CodeBlockActions>
          <CodeBlockCopyButton value="Compiled in 4.2s" />
        </CodeBlockActions>
      </CodeBlockHeader>
      <CodeBlockContent language="bash">
        {"$ pnpm build\nCompiled in 4.2s\nGenerated 12 static pages"}
      </CodeBlockContent>
      <CodeBlockFooter>
import EsuissUI
import SwiftUI

struct CodeBlockFooterExample: View {
  var body: some View {
    CodeBlock {
      CodeBlockHeader {
        CodeBlockTitle("Logs")
        CodeBlockActions { CodeBlockCopyButton(value: "Compiled in 4.2s") }
      }
      CodeBlockContent("$ pnpm build\nCompiled in 4.2s\nGenerated 12 static pages", language: "bash")
      CodeBlockFooter { Text("Exited with code 0") }
    }
  }
}
import androidx.compose.runtime.Composable
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockActions
import com.esuiss.ui.components.CodeBlockContent
import com.esuiss.ui.components.CodeBlockCopyButton
import com.esuiss.ui.components.CodeBlockFooter
import com.esuiss.ui.components.CodeBlockHeader
import com.esuiss.ui.components.CodeBlockTitle
import com.esuiss.ui.components.Text

@Composable
fun CodeBlockWithFooter() {
    CodeBlock {
        CodeBlockHeader {
            CodeBlockTitle("Logs")
            CodeBlockActions { CodeBlockCopyButton("Compiled in 4.2s") }

API Reference

01

CodeBlock

The surface that holds the header, code and footer.

PropTypeDefaultDescription
variant"default" | "destructive""default"Muted surface or a destructive tint for errors.
size"default" | "sm""default"Text size, padding and corner radius.

Also accepts the props of ViewProps.

02

CodeBlockHeader

A row above the code for an icon, a title and actions.

03

CodeBlockTitle

One line of text that truncates, usually a file name.

04

CodeBlockActions

Holds buttons at the end of the header.

05

CodeBlockCopyButton

Copies value and shows a check for two seconds.

PropTypeDefaultDescription
valuestring—The text to copy.
06

CodeBlockContent

The code, from a string or CodeBlockLine children.

PropTypeDefaultDescription
languagestring—Colors the code: ts, tsx, js, json, bash, python, swift, kotlin and their aliases.
wrapbooleanfalseWraps long lines instead of scrolling sideways.
lineNumbersbooleanfalseShows a number before each line.
startLinenumber1The number of the first line.
07

CodeBlockLine

One line of code.

PropTypeDefaultDescription
variant"default" | "added" | "removed" | "highlighted""default"Tints the line and adds a + or − marker for diffs.
08

CodeBlockFooter

Secondary text below the code.

Accessibility

  • The copy button has the label Copy, and Copied after a copy.
  • Line numbers and diff markers are hidden from assistive technology.
NextCollapsiblePreviousChip

On this page

  1. 01Installation
    • CLI
    • Manual
  2. 02Usage
  3. 03Examples
    • Basic
    • Header
    • Actions
    • Floating actions
    • Line numbers
    • Start line
    • Wrap
    • Highlighted lines
    • Diff
    • Height
    • Show more
    • Show more with max height
    • Size
    • Destructive
    • Trace
    • Tabs
    • Message
    • Syntax highlighting
    • Languages
    • Terminal output
    • Streaming
    • Server rendering
  4. 04API Reference
    • CodeBlock
    • CodeBlockHeader
    • CodeBlockTitle
    • CodeBlockActions
    • CodeBlockCopyButton
    • CodeBlockContent
    • CodeBlockLine
    • CodeBlockFooter
    • useHighlightedCode
  5. 05Accessibility
  6. 06Notes
  7. 07Installation
    • Manual
  8. 08Usage
  9. 09Examples
    • Basic
    • Line numbers
    • Start line
    • Wrap
    • Highlighted lines
    • Diff
    • Size
    • Destructive
    • Languages
    • Footer
  10. 10API Reference
    • CodeBlock
    • CodeBlockHeader
    • CodeBlockTitle
    • CodeBlockActions
    • CodeBlockCopyButton
    • CodeBlockContent
    • CodeBlockLine
    • CodeBlockFooter
  11. 11Accessibility