// preview in
saved for every page

CodeBlock

Shows a snippet of code with its file name, line numbers and a copy button, for install steps, API references and setup guides.

Built on HTML pre and codePattern Code sample with a copy buttonClient componentSource ↗
minimal · light · #3fa34d
like-button.tsxTSX
import { useState } from "react";
import { Button } from "@camaleo/react";

export function LikeButton() {
  const [likes, setLikes] = useState(0);
  return <Button onClick={() => setLikes(likes + 1)}>Like · {likes}</Button>;
}
showLineNumbers

Installation

Set up Camaleo once (Installation), then import what you need. It is a client component, because of the copy button.

import { CodeBlock } from "@camaleo/react";

Anatomy

A header with the file name, the language and the copy button, then a <pre> that scrolls sideways when lines are long. Every part has a data-slot you can target from CSS.

Examples

Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.

minimal · light · #3fa34d
components/save-button.tsxTSX
import { Button } from "@camaleo/react";

export function SaveButton() {
  return <Button type="submit">Save changes</Button>;
}
app/layout.tsxTSX
import "@camaleo/styles/base.css";
import "@camaleo/styles/editorial.css";

File name and language

When people must paste the code into a specific file, put its path in filename. The language badge tells them the syntax at a glance.

Bash
pnpm add @camaleo/react @camaleo/styles

Terminal command

Turn off line numbers for a one-line command: there is nothing to point at, and the command reads as one piece.

package.jsonJSON
{
  "name": "billing-dashboard",
  "private": true,
  "dependencies": {
    "@camaleo/react": "^0.4.0",
    "@camaleo/styles": "^0.4.0"
  }
}

JSON and other formats

The built-in highlighter colors strings, comments and JavaScript keywords. That is enough for JSON or a shell command. language also sets the comments: # for Bash, Python or YAML, // for the rest.

app/layout.tsxDiff
import "@camaleo/styles/base.css";
- import "@camaleo/styles/minimal.css";
+ import "@camaleo/styles/editorial.css";

Marked lines

Pass highlight to render each line yourself, like the lines of a diff. Keep the + and - in the text, so the change doesn't depend on color.

Bash
curl "https://api.example.com/v1/invoices?status=overdue&limit=20" -H "Authorization: Bearer $API_TOKEN"

Long lines scroll

Long lines scroll sideways instead of wrapping, so a command stays copyable as it is. Keyboard users Tab into the code and scroll with the arrow keys.

Bash
pnpm add @camaleo/react @camaleo/styles

One command per package manager

Pick a tab. Tabs show one command at a time and keep the choice clear, instead of four blocks stacked on top of each other.

Bash
pnpm add @camaleo/react @camaleo/styles

Name the copy and announce it

Say what the button copies in copyLabel, like "Copy install command". After the copy, copiedLabel shows on the button and screen readers hear it too.

In context

Code blocks usually sit in a card or a guide, right after the sentence that explains them. These are whole pieces of UI you can copy, in the skin you picked.

Install Camaleo

Run this in the root of your project. It adds the components and the skins.

Bash
pnpm add @camaleo/react @camaleo/styles
Install stepCard · CodeBlock · Button
GET

List invoices

Returns the 20 most recent invoices, newest first.

Bash
curl https://api.example.com/v1/invoices \
  -H "Authorization: Bearer $API_TOKEN" \
  -d limit=20
API endpointCard · Badge · CodeBlock

Pick a skin

Load the base styles and one skin, then wrap your app in StyleProvider.

app/layout.tsxTSX
import "@camaleo/styles/base.css";
import "@camaleo/styles/glass.css";
import { StyleProvider } from "@camaleo/react";

export default function RootLayout({ children }) {
  return (
    <StyleProvider styleName="glass" mode="system">
      {children}
    </StyleProvider>
  );
}
Setup guideCard · CodeBlock

Guidelines

app/blog/page.tsxTSX
export const revalidate = 3600;
DoSay where the code goes.

A file path like app/blog/page.tsx tells people which file to open before they paste.

export const revalidate = 3600;
Don’tA snippet with no file.

People have to guess whether the line goes in a page, a layout or a config file.

next.config.tsTS
const config: NextConfig = {
  // …
  images: { formats: ["image/avif", "image/webp"] },
};
DoShow only the lines that matter.

Cut the rest with a // … comment. People see the change at once and copy less by mistake.

next.config.tsTS
import type { NextConfig } from "next";

const config: NextConfig = {
  reactStrictMode: true,
  poweredByHeader: false,
  trailingSlash: false,
  experimental: { typedRoutes: true },
  logging: { fetches: { fullUrl: true } },
  eslint: { ignoreDuringBuilds: false },
  typescript: { ignoreBuildErrors: false },
  images: { formats: ["image/avif", "image/webp"] },
  redirects: async () => [],
};

export default config;
Don’tThe whole file for a one-line change.

People have to hunt for the line you mean, and the copy button copies all of it.

Props

PropTypeDefaultDescription
code*string—The code to show. The copy button copies this exact string.
languagestring—Short language name shown as a badge in the header, like "TSX" or "Bash". It also sets the built-in highlighter's comments: # for shell, Python, YAML and the like, // otherwise.
filenamestring—File path shown in the header, like "app/layout.tsx".
showLineNumbersbooleantrueShows a number before each line. Screen readers skip the numbers and selecting text leaves them out.
copyLabelReactNode"Copy"Text of the copy button, which is also its accessible name. Say what it copies when there are several blocks.
copiedLabelReactNode"Copied"Text the copy button shows for 1.8 seconds after a click.
highlight(line, lineNumber) => ReactNode—Renders each line yourself; gets the line and its number (from 1). Replaces the built-in highlighter, which colors strings, comments, JavaScript keywords and function calls.

Native <div> attributes go to the root. There is no prop for the <pre> or a callback for the copy.

Styling hooks

Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the block itself.

Attributes

data-copiedon the copy button, for 1.8 s after a click
data-tokencomment · string · keyword · function, from the built-in highlighter

Tokens it reads

Color

--cml-border-control--cml-card-shadow--cml-focus-offset--cml-focus-ring--cml-focus-width

Shape & size

--cml-border-width--cml-radius-popover

Type & motion

--cml-font-control

This component only

--cml-code-bg--cml-code-border--cml-code-comment--cml-code-fg--cml-code-function--cml-code-header-bg--cml-code-header-border--cml-code-header-fg--cml-code-keyword--cml-code-radius--cml-code-shadow--cml-code-string--cml-corner-shape

Accessibility

TabMoves to the copy button, then to the code, but only when the code is wider than the block. Code that fits adds no Tab stop.
Enter / SpaceCopies the code while the copy button has focus.
← / →Scroll long lines sideways while the code has focus.
Was this page helpful? · Edit this page on GitHub