Code Block
Shows code from a reply, a canvas, a workflow step, or a file. It colors every language Shiki knows, over 240, picks the language from the file name, and copies the exact source with one button.
Usage
import { CodeBlock } from "@tulipfarm/sprout/code-block";
<CodeBlock
code={step.source}
language="python"
filename="score_lead.py"
/>language takes a name, an alias, or an extension, such astypescript, ts, or py, so a Markdown fence name can go straight in. Leave it out and the language comes from filename: main.rs,Dockerfile, and .env.local all work.text, txt, and unknown names show plain text. The header shows filename, or the language’s name.lineNumbers is on by default. wrap wraps long lines for prose such as notes, and hides line numbers.
Syntax colors
Shiki tokenizes the code with its JavaScript regex engine, so there is no WebAssembly and no inline styles. Each grammar loads the first time a block needs it, and the block shows plain text until then. Markdown also loads the languages of its fences. Code over 200,000 characters stays plain so a huge file can’t freeze the page.
[data-theme="sunset"] {
--sprout-syntax-keyword: #c2410c;
--sprout-syntax-string: #15803d;
--sprout-syntax-comment: #78716c;
}Tokens get the classes sprout-syntax-keyword,-string, -constant, -function,-parameter, -comment, -punctuation,-link, and, for Markdown and diffs, -heading,-inserted, and -deleted. Each reads a matching--sprout-syntax-* variable and falls back to the theme’s info, success, warning, danger, and muted colors, so every theme works without setup.
import { preloadCodeLanguages } from "@tulipfarm/sprout/code-block";
await preloadCodeLanguages("tsx", "python");Await preloadCodeLanguages before server rendering to send colored HTML. languageForFilename,codeLanguageName, and isHighlightedLanguageexpose the same lookup the block uses.
Variants
inline is a bordered card that scrolls after 24rem.file has no frame and fills its parent, so give the parent a height.preview drops the header and copy button and fades out at its parent’s height, for hover cards and thumbnails.
In a chat reply
Render fenced code from Markdown with the fence language and no filename. A fence without a language shows as text.
pre({ node, children }) {
const code = node?.children[0];
if (code?.type !== "element" || code.tagName !== "code") return <pre>{children}</pre>;
const language = languageClass(code)?.slice("language-".length) ?? "text";
return <CodeBlock code={textOf(code).replace(/\n$/, "")} language={language} />;
}Canvas and workflow steps
Step 1: run JavaScript code with the trigger input
calculate.jsStep 2: run Python code with Lead from Step 1
score_lead.py
File pane
<div className="flex h-full min-h-0 flex-col">
<CodeBlock
key={`${file.revision}:${file.path}`}
code={file.content}
filename={file.path}
variant="file"
/>
{file.content === "" && <p>This file is empty.</p>}
</div>A new key per file and revision resets the copy state and scroll position. Long paths truncate in the header; the region and the copy button still use the full path.
Shell examples
Turn off line numbers for shell commands so a copied command runs as is.
POST https://hooks.tulipfarm.app/w/wf_8k2m4qz7/run
Set TULIPFARM_WEBHOOK_KEY to your key before you run the example. The example never contains the key.
Accessibility
The code scrolls in a focusable region named after the file, or “Language code”, such as “TypeScript code”. The preview variant is not focusable. Line numbers are hidden from screen readers. The copy button is named “Copy filename” or “Copy code”, stays focusable while it copies, shows Copied for 2 seconds, and announces “Code copied to clipboard.” When the clipboard is blocked an alert says to select the code instead, and Copy tries again. On touch screens the button is 44px tall. The error fades in over 180ms, or appears at once with reduced motion.