CodeBlock
Shows a snippet of code with its file name, line numbers and a copy button, for install steps, API references and setup guides.
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>;
}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.
import { Button } from "@camaleo/react";
export function SaveButton() {
return <Button type="submit">Save changes</Button>;
}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.
pnpm add @camaleo/react @camaleo/stylesTerminal command
Turn off line numbers for a one-line command: there is nothing to point at, and the command reads as one piece.
{
"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.
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.
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.
pnpm add @camaleo/react @camaleo/stylesOne 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.
pnpm add @camaleo/react @camaleo/stylesName 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.
pnpm add @camaleo/react @camaleo/stylesList invoices
Returns the 20 most recent invoices, newest first.
curl https://api.example.com/v1/invoices \
-H "Authorization: Bearer $API_TOKEN" \
-d limit=20Pick a skin
Load the base styles and one skin, then wrap your app in StyleProvider.
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>
);
}Guidelines
export const revalidate = 3600;A file path like app/blog/page.tsx tells people which file to open before they paste.
export const revalidate = 3600;People have to guess whether the line goes in a page, a layout or a config file.
const config: NextConfig = {
// …
images: { formats: ["image/avif", "image/webp"] },
};Cut the rest with a // … comment. People see the change at once and copy less by mistake.
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;People have to hunt for the line you mean, and the copy button copies all of it.
Props
# for shell, Python, YAML and the like, // otherwise.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
Tokens it reads
Color
Shape & size
Type & motion
This component only
Accessibility
- The copy button's text is its name. With several blocks on a page, make copyLabel say what it copies.
- After a successful copy the button reads copiedLabel for 1.8 seconds, and a status message announces it. If the clipboard is blocked, nothing changes and the code stays selectable.
- Line numbers are aria-hidden, so screen readers read the code without them.
- Code wider than the block becomes a scroll region (role="region") named by the filename, the language or "Code" (from the MessagesProvider), so keyboard users can scroll it.