Rendered from the shared Frontfriend stories with React Base, the same
preview Studio shows. Styling comes from your Design System; the code below
is native to Reka UI.
Installation
Complete project setup first. Your configuration
must select vue/reka and include your Design System ID.
pnpm exec frontfriend download code-block -f vue --primitive reka
Review dependencies reported by the downloader and keep your source
changes in version control.
Usage
<script setup lang="ts">
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockHeader } from "@/components/ui/code-block";
import { CodeBlockTitle } from "@/components/ui/code-block";
import { CodeBlockActions } from "@/components/ui/code-block";
import { CodeBlockCopyButton } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
import { CodeBlockFooter } from "@/components/ui/code-block";
const code = 'const message = "Hello, Frontfriend";\n\nconsole.log(message);';
const args = {
"variant": "default",
"size": "default",
"wrap": false,
"lineNumbers": false,
"startLine": 1,
"text": code,
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown> & {
variant?: "default" | "destructive";
size?: "default" | "sm";
};
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<CodeBlock :variant="args.variant" :size="args.size"
><CodeBlockHeader
><CodeBlockTitle>example.ts</CodeBlockTitle
><CodeBlockActions
><CodeBlockCopyButton
:value="args.text" /></CodeBlockActions></CodeBlockHeader
><CodeBlockContent
:wrap="args.wrap"
:line-numbers="args.lineNumbers"
:start-line="args.startLine"
aria-label="Editable code"
>{{ args.text }}</CodeBlockContent
><CodeBlockFooter>Plain text</CodeBlockFooter></CodeBlock
>
</div>
</template>
Compose a header, title, actions, content, lines, and footer. Pass the original text to the copy button. Line numbers and diff markers are display only. Copy feedback appears after the Clipboard promise resolves and resets after two seconds. Failed copies keep the Copy label. Use line numbers with wrapping to keep the gutter aligned. Give the focusable code area a useful title or accessible label. Code stays left-to-right inside a right-to-left page.
Syntax highlighting
Highlighting is optional. Plain code does not load Shiki. A supported language loads when first used. GitHub Light and Dark supply syntax colors; the design system controls surfaces, borders, and actions. Unsupported languages and failed language loads remain plain text. Token text is escaped by the framework. The diff language colors a diff; it does not set added or removed line variants.
Helper imports
Import highlightCode and loadHighlighter from @/components/ui/code-block. Their existing helper subpaths also remain valid. highlightCode(code, language) is async and returns one highlighted line per input line, or null for an unsupported language or a failed load. It works on the server and retains final newlines. loadHighlighter(language) returns a tokenizer with highlight(code, state?) for grammar-state continuation.
Import useHighlightedCode from @/components/ui/code-block. It accepts strings, refs, or getters and returns a readonly computed value of token lines. Use CodeBlockToken with :token="token" inside each CodeBlockLine. The composable removes exactly one final newline. It caches complete lines and updates the last line as text arrives. The server renders plain lines until the composable mounts. The async helper can also be called during server rendering. Vue templates have an additional vue grammar.
Put CodeBlockLine components in the default slot of CodeBlockContent. Use string interpolation inside CodeBlockLine to preserve indentation. Bind the copy text with :value. The copy button emits click; use @click for a handler and call event.preventDefault() to cancel copying. Other Button and native pre attributes are forwarded.
Replace a stock installation
Use --replace when the CLI reports a conflict with existing files. The install replaces the UI files, CodeBlockToken.vue, highlighter.ts, and useHighlightedCode.ts in the code-block folder. The barrel exports all three runtime helpers, as shadcn-vue does. Files outside the install manifest remain unchanged.
Highlighted Vue template
<script setup lang="ts">
import { CodeBlock, CodeBlockContent, CodeBlockLine, CodeBlockToken, useHighlightedCode } from "@/components/ui/code-block";
const code = 'const message = "Hello";';
const lines = useHighlightedCode(code, "typescript");
</script>
<template>
<CodeBlock>
<CodeBlockContent line-numbers aria-label="TypeScript example">
<CodeBlockLine v-for="(line, index) in lines" :key="index">
<CodeBlockToken v-for="(token, tokenIndex) in line" :key="tokenIndex" :token="token" />
</CodeBlockLine>
</CodeBlockContent>
</CodeBlock>
</template>
Examples
Default
<script setup lang="ts">
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
const code = 'const message = "Hello, Frontfriend";\n\nconsole.log(message);';
const longCode =
'const description = "A long line of plain text wraps inside the code block while each line number stays aligned with the first row of its content.";\nconsole.log(description);';
const diffCode =
"const config = {\n retries: 1,\n retries: 3,\n timeout: 2000,\n};\n";
const errorCode =
"Error: Cannot find module ./config\n at build (build.ts:12:3)";
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<CodeBlock
><CodeBlockContent aria-label="Plain code">{{
code
}}</CodeBlockContent></CodeBlock
>
</div>
</template>
<script setup lang="ts">
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockHeader } from "@/components/ui/code-block";
import { CodeBlockTitle } from "@/components/ui/code-block";
import { CodeBlockActions } from "@/components/ui/code-block";
import { CodeBlockCopyButton } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
import { CodeBlockFooter } from "@/components/ui/code-block";
const code = 'const message = "Hello, Frontfriend";\n\nconsole.log(message);';
const longCode =
'const description = "A long line of plain text wraps inside the code block while each line number stays aligned with the first row of its content.";\nconsole.log(description);';
const diffCode =
"const config = {\n retries: 1,\n retries: 3,\n timeout: 2000,\n};\n";
const errorCode =
"Error: Cannot find module ./config\n at build (build.ts:12:3)";
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<CodeBlock
><CodeBlockHeader
><CodeBlockTitle>example.ts</CodeBlockTitle
><CodeBlockActions
><CodeBlockCopyButton
:value="code" /></CodeBlockActions></CodeBlockHeader
><CodeBlockContent aria-label="Example source">{{
code
}}</CodeBlockContent
><CodeBlockFooter>TypeScript · Plain text</CodeBlockFooter></CodeBlock
>
</div>
</template>
Floating Actions
<script setup lang="ts">
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockActions } from "@/components/ui/code-block";
import { CodeBlockCopyButton } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
const code = 'const message = "Hello, Frontfriend";\n\nconsole.log(message);';
const longCode =
'const description = "A long line of plain text wraps inside the code block while each line number stays aligned with the first row of its content.";\nconsole.log(description);';
const diffCode =
"const config = {\n retries: 1,\n retries: 3,\n timeout: 2000,\n};\n";
const errorCode =
"Error: Cannot find module ./config\n at build (build.ts:12:3)";
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<CodeBlock
><CodeBlockActions><CodeBlockCopyButton :value="code" /></CodeBlockActions
><CodeBlockContent aria-label="Code with floating actions">{{
code
}}</CodeBlockContent></CodeBlock
>
</div>
</template>
Small
<script setup lang="ts">
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockHeader } from "@/components/ui/code-block";
import { CodeBlockTitle } from "@/components/ui/code-block";
import { CodeBlockActions } from "@/components/ui/code-block";
import { CodeBlockCopyButton } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
import { CodeBlockFooter } from "@/components/ui/code-block";
const code = 'const message = "Hello, Frontfriend";\n\nconsole.log(message);';
const longCode =
'const description = "A long line of plain text wraps inside the code block while each line number stays aligned with the first row of its content.";\nconsole.log(description);';
const diffCode =
"const config = {\n retries: 1,\n retries: 3,\n timeout: 2000,\n};\n";
const errorCode =
"Error: Cannot find module ./config\n at build (build.ts:12:3)";
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<CodeBlock size="sm"
><CodeBlockHeader
><CodeBlockTitle>compact.ts</CodeBlockTitle
><CodeBlockActions
><CodeBlockCopyButton
:value="code" /></CodeBlockActions></CodeBlockHeader
><CodeBlockContent line-numbers aria-label="Compact code">{{
code
}}</CodeBlockContent
><CodeBlockFooter>3 lines</CodeBlockFooter></CodeBlock
>
</div>
</template>
Wrap
<script setup lang="ts">
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
const code = 'const message = "Hello, Frontfriend";\n\nconsole.log(message);';
const longCode =
'const description = "A long line of plain text wraps inside the code block while each line number stays aligned with the first row of its content.";\nconsole.log(description);';
const diffCode =
"const config = {\n retries: 1,\n retries: 3,\n timeout: 2000,\n};\n";
const errorCode =
"Error: Cannot find module ./config\n at build (build.ts:12:3)";
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<CodeBlock
><CodeBlockContent wrap aria-label="Wrapped code">{{
longCode
}}</CodeBlockContent></CodeBlock
>
</div>
</template>
Line Numbers
<script setup lang="ts">
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
const code = 'const message = "Hello, Frontfriend";\n\nconsole.log(message);';
const longCode =
'const description = "A long line of plain text wraps inside the code block while each line number stays aligned with the first row of its content.";\nconsole.log(description);';
const diffCode =
"const config = {\n retries: 1,\n retries: 3,\n timeout: 2000,\n};\n";
const errorCode =
"Error: Cannot find module ./config\n at build (build.ts:12:3)";
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<CodeBlock
><CodeBlockContent line-numbers aria-label="Numbered code">{{
code
}}</CodeBlockContent></CodeBlock
>
</div>
</template>
Start Offset
<script setup lang="ts">
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
const code = 'const message = "Hello, Frontfriend";\n\nconsole.log(message);';
const longCode =
'const description = "A long line of plain text wraps inside the code block while each line number stays aligned with the first row of its content.";\nconsole.log(description);';
const diffCode =
"const config = {\n retries: 1,\n retries: 3,\n timeout: 2000,\n};\n";
const errorCode =
"Error: Cannot find module ./config\n at build (build.ts:12:3)";
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<CodeBlock
><CodeBlockContent
line-numbers
:start-line="10000"
aria-label="Code starting at line 10000"
>{{ code }}</CodeBlockContent
></CodeBlock
>
</div>
</template>
Wrap And Numbers
<script setup lang="ts">
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
const code = 'const message = "Hello, Frontfriend";\n\nconsole.log(message);';
const longCode =
'const description = "A long line of plain text wraps inside the code block while each line number stays aligned with the first row of its content.";\nconsole.log(description);';
const diffCode =
"const config = {\n retries: 1,\n retries: 3,\n timeout: 2000,\n};\n";
const errorCode =
"Error: Cannot find module ./config\n at build (build.ts:12:3)";
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<CodeBlock
><CodeBlockContent
wrap
line-numbers
:start-line="98"
aria-label="Wrapped numbered code"
>{{ longCode }}</CodeBlockContent
></CodeBlock
>
</div>
</template>
Diff Lines
<script setup lang="ts">
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockHeader } from "@/components/ui/code-block";
import { CodeBlockTitle } from "@/components/ui/code-block";
import { CodeBlockActions } from "@/components/ui/code-block";
import { CodeBlockCopyButton } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
import { CodeBlockLine } from "@/components/ui/code-block";
const code = 'const message = "Hello, Frontfriend";\n\nconsole.log(message);';
const longCode =
'const description = "A long line of plain text wraps inside the code block while each line number stays aligned with the first row of its content.";\nconsole.log(description);';
const diffCode =
"const config = {\n retries: 1,\n retries: 3,\n timeout: 2000,\n};\n";
const errorCode =
"Error: Cannot find module ./config\n at build (build.ts:12:3)";
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<CodeBlock
><CodeBlockHeader
><CodeBlockTitle>configuration.diff</CodeBlockTitle
><CodeBlockActions
><CodeBlockCopyButton
:value="diffCode" /></CodeBlockActions></CodeBlockHeader
><CodeBlockContent line-numbers aria-label="Configuration diff"
><CodeBlockLine>const config = {</CodeBlockLine
><CodeBlockLine variant="removed">{{ " retries: 1," }}</CodeBlockLine
><CodeBlockLine variant="added">{{ " retries: 3," }}</CodeBlockLine
><CodeBlockLine variant="highlighted">{{
" timeout: 2000,"
}}</CodeBlockLine
><CodeBlockLine>};</CodeBlockLine><CodeBlockLine /></CodeBlockContent
></CodeBlock>
</div>
</template>
Destructive
<script setup lang="ts">
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockHeader } from "@/components/ui/code-block";
import { CodeBlockTitle } from "@/components/ui/code-block";
import { CodeBlockActions } from "@/components/ui/code-block";
import { CodeBlockCopyButton } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
import { CodeBlockFooter } from "@/components/ui/code-block";
const code = 'const message = "Hello, Frontfriend";\n\nconsole.log(message);';
const longCode =
'const description = "A long line of plain text wraps inside the code block while each line number stays aligned with the first row of its content.";\nconsole.log(description);';
const diffCode =
"const config = {\n retries: 1,\n retries: 3,\n timeout: 2000,\n};\n";
const errorCode =
"Error: Cannot find module ./config\n at build (build.ts:12:3)";
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<CodeBlock variant="destructive"
><CodeBlockHeader
><CodeBlockTitle>Build failed</CodeBlockTitle
><CodeBlockActions
><CodeBlockCopyButton
:value="errorCode" /></CodeBlockActions></CodeBlockHeader
><CodeBlockContent aria-label="Build error">{{
errorCode
}}</CodeBlockContent
><CodeBlockFooter
>Check the file path and try again.</CodeBlockFooter
></CodeBlock
>
</div>
</template>
Copy
<script setup lang="ts">
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockHeader } from "@/components/ui/code-block";
import { CodeBlockTitle } from "@/components/ui/code-block";
import { CodeBlockActions } from "@/components/ui/code-block";
import { CodeBlockCopyButton } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
const code = 'const message = "Hello, Frontfriend";\n\nconsole.log(message);';
const longCode =
'const description = "A long line of plain text wraps inside the code block while each line number stays aligned with the first row of its content.";\nconsole.log(description);';
const diffCode =
"const config = {\n retries: 1,\n retries: 3,\n timeout: 2000,\n};\n";
const errorCode =
"Error: Cannot find module ./config\n at build (build.ts:12:3)";
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<CodeBlock
><CodeBlockHeader
><CodeBlockTitle>Copy example</CodeBlockTitle
><CodeBlockActions
><CodeBlockCopyButton
:value="code" /></CodeBlockActions></CodeBlockHeader
><CodeBlockContent line-numbers aria-label="Code to copy">{{
code
}}</CodeBlockContent></CodeBlock
>
</div>
</template>
Blank Lines
<script setup lang="ts">
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
const code = 'const message = "Hello, Frontfriend";\n\nconsole.log(message);';
const longCode =
'const description = "A long line of plain text wraps inside the code block while each line number stays aligned with the first row of its content.";\nconsole.log(description);';
const diffCode =
"const config = {\n retries: 1,\n retries: 3,\n timeout: 2000,\n};\n";
const errorCode =
"Error: Cannot find module ./config\n at build (build.ts:12:3)";
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<CodeBlock
><CodeBlockContent line-numbers aria-label="Code with blank lines">{{
"first\n\nlast\n\n"
}}</CodeBlockContent></CodeBlock
>
</div>
</template>
Right To Left
<script setup lang="ts">
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockHeader } from "@/components/ui/code-block";
import { CodeBlockTitle } from "@/components/ui/code-block";
import { CodeBlockActions } from "@/components/ui/code-block";
import { CodeBlockCopyButton } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
import { CodeBlockFooter } from "@/components/ui/code-block";
const code = 'const message = "Hello, Frontfriend";\n\nconsole.log(message);';
const longCode =
'const description = "A long line of plain text wraps inside the code block while each line number stays aligned with the first row of its content.";\nconsole.log(description);';
const diffCode =
"const config = {\n retries: 1,\n retries: 3,\n timeout: 2000,\n};\n";
const errorCode =
"Error: Cannot find module ./config\n at build (build.ts:12:3)";
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<div dir="rtl">
<CodeBlock
><CodeBlockHeader
><CodeBlockTitle>مثال الشفرة</CodeBlockTitle
><CodeBlockActions
><CodeBlockCopyButton
:value="code" /></CodeBlockActions></CodeBlockHeader
><CodeBlockContent line-numbers aria-label="Left-to-right code">{{
code
}}</CodeBlockContent
><CodeBlockFooter>نص عادي</CodeBlockFooter></CodeBlock
>
</div>
</div>
</template>
Highlighted
<script setup lang="ts">
import { defineComponent } from "vue";
import { useHighlightedCode } from "@/components/ui/code-block";
import { h } from "vue";
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockHeader } from "@/components/ui/code-block";
import { CodeBlockTitle } from "@/components/ui/code-block";
import { CodeBlockActions } from "@/components/ui/code-block";
import { CodeBlockCopyButton } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
import { CodeBlockLine } from "@/components/ui/code-block";
import { CodeBlockToken } from "@/components/ui/code-block";
import { CodeBlockFooter } from "@/components/ui/code-block";
const highlightedSource =
'// Fetch one user and keep the response typed.\n// The caller handles network errors.\ntype User = { id: number; name: string };\n\n/** Read the user record from the API. */\nexport async function getUser(id: number): Promise<User> {\n // Use the same endpoint for cached and fresh records.\n const response = await fetch(`/api/users/${id}`);\n if (!response.ok) {\n throw new Error("User not found");\n }\n\n // Keep JSON parsing in the typed request function.\n return response.json();\n}';
const HighlightedFile = defineComponent({
props: {
value: { type: String, required: true },
language: { type: String, required: true },
title: { type: String, required: true },
wrap: { type: Boolean, default: false },
},
setup(props) {
const lines = useHighlightedCode(
() => props.value,
() => props.language,
);
return () =>
h(CodeBlock, () => [
h(CodeBlockHeader, () => [
h(CodeBlockTitle, () => props.title),
h(CodeBlockActions, () =>
h(CodeBlockCopyButton, { value: props.value }),
),
]),
h(
CodeBlockContent,
{ lineNumbers: true, wrap: props.wrap, "aria-label": props.title },
() =>
lines.value.map((line, index) =>
h(CodeBlockLine, { key: index }, () =>
line.map((token, tokenIndex) =>
h(CodeBlockToken, { key: tokenIndex, token }),
),
),
),
),
h(CodeBlockFooter, () => props.language),
]);
},
});
const args = {
"wrap": false,
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<HighlightedFile
:value="highlightedSource"
language="typescript"
title="users.ts"
:wrap="args.wrap"
/>
</div>
</template>
Server Helper
<script setup lang="ts">
import { shallowRef } from "vue";
import type { HighlightedToken } from "@/components/ui/code-block";
import { onMounted } from "vue";
import { highlightCode } from "@/components/ui/code-block";
import { onBeforeUnmount } from "vue";
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockHeader } from "@/components/ui/code-block";
import { CodeBlockTitle } from "@/components/ui/code-block";
import { CodeBlockActions } from "@/components/ui/code-block";
import { CodeBlockCopyButton } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
import { CodeBlockLine } from "@/components/ui/code-block";
import { CodeBlockToken } from "@/components/ui/code-block";
import { CodeBlockFooter } from "@/components/ui/code-block";
const highlightedSource =
'// Fetch one user and keep the response typed.\n// The caller handles network errors.\ntype User = { id: number; name: string };\n\n/** Read the user record from the API. */\nexport async function getUser(id: number): Promise<User> {\n // Use the same endpoint for cached and fresh records.\n const response = await fetch(`/api/users/${id}`);\n if (!response.ok) {\n throw new Error("User not found");\n }\n\n // Keep JSON parsing in the typed request function.\n return response.json();\n}';
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
const lines = shallowRef<HighlightedToken[][]>(
highlightedSource.split("\n").map((content) => [{ content }]),
);
let active = true;
onMounted(async () => {
const next = await highlightCode(highlightedSource, "typescript");
if (active && next) lines.value = next;
});
onBeforeUnmount(() => {
active = false;
});
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<CodeBlock
><CodeBlockHeader
><CodeBlockTitle>Full-file helper</CodeBlockTitle
><CodeBlockActions
><CodeBlockCopyButton
:value="highlightedSource" /></CodeBlockActions></CodeBlockHeader
><CodeBlockContent line-numbers aria-label="Full highlighted source"
><CodeBlockLine v-for="(line, index) in lines" :key="index"
><CodeBlockToken
v-for="(token, tokenIndex) in line"
:key="tokenIndex"
:token="token" /></CodeBlockLine></CodeBlockContent
><CodeBlockFooter
>highlightCode returns token records for each line.</CodeBlockFooter
></CodeBlock
>
</div>
</template>
Streaming
<script setup lang="ts">
import { ref } from "vue";
import { computed } from "vue";
import { useHighlightedCode } from "@/components/ui/code-block";
import { onBeforeUnmount } from "vue";
import { Button } from "@/components/ui/button";
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockHeader } from "@/components/ui/code-block";
import { CodeBlockTitle } from "@/components/ui/code-block";
import { CodeBlockActions } from "@/components/ui/code-block";
import { CodeBlockCopyButton } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
import { CodeBlockLine } from "@/components/ui/code-block";
import { CodeBlockToken } from "@/components/ui/code-block";
import { CodeBlockFooter } from "@/components/ui/code-block";
const highlightedSource =
'// Fetch one user and keep the response typed.\n// The caller handles network errors.\ntype User = { id: number; name: string };\n\n/** Read the user record from the API. */\nexport async function getUser(id: number): Promise<User> {\n // Use the same endpoint for cached and fresh records.\n const response = await fetch(`/api/users/${id}`);\n if (!response.ok) {\n throw new Error("User not found");\n }\n\n // Keep JSON parsing in the typed request function.\n return response.json();\n}';
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
const length = ref(58);
const running = ref(false);
const value = computed(() => highlightedSource.slice(0, length.value));
const lines = useHighlightedCode(value, "typescript");
let timer: ReturnType<typeof setInterval> | undefined;
function toggle() {
clearInterval(timer);
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
length.value = highlightedSource.length;
running.value = false;
return;
}
running.value = !running.value;
if (!running.value) return;
if (length.value === highlightedSource.length) length.value = 0;
timer = setInterval(() => {
length.value = Math.min(length.value + 3, highlightedSource.length);
if (length.value === highlightedSource.length) {
clearInterval(timer);
running.value = false;
}
}, 45);
}
onBeforeUnmount(() => clearInterval(timer));
</script>
<template>
<div
style="
display: grid;
gap: 1rem;
width: min(44rem, calc(100vw - 2rem));
max-width: 100%;
"
>
<form style="display: flex; gap: 0.5rem" @submit.prevent="toggle">
<Button type="submit" variant="outline">{{
running ? "Pause stream" : "Start stream"
}}</Button>
</form>
<CodeBlock
><CodeBlockHeader
><CodeBlockTitle>Generated users.ts</CodeBlockTitle
><CodeBlockActions
><CodeBlockCopyButton
:value="value" /></CodeBlockActions></CodeBlockHeader
><CodeBlockContent line-numbers aria-label="Streamed TypeScript"
><CodeBlockLine v-for="(line, index) in lines" :key="index"
><CodeBlockToken
v-for="(token, tokenIndex) in line"
:key="tokenIndex"
:token="token" /></CodeBlockLine></CodeBlockContent
><CodeBlockFooter>{{
running
? "Receiving code…"
: length === highlightedSource.length
? "Complete"
: "Paused"
}}</CodeBlockFooter></CodeBlock
>
</div>
</template>
Multiple Languages
<script setup lang="ts">
import { defineComponent } from "vue";
import { useHighlightedCode } from "@/components/ui/code-block";
import { h } from "vue";
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockHeader } from "@/components/ui/code-block";
import { CodeBlockTitle } from "@/components/ui/code-block";
import { CodeBlockActions } from "@/components/ui/code-block";
import { CodeBlockCopyButton } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
import { CodeBlockLine } from "@/components/ui/code-block";
import { CodeBlockToken } from "@/components/ui/code-block";
import { CodeBlockFooter } from "@/components/ui/code-block";
const highlightedSource =
'// Fetch one user and keep the response typed.\n// The caller handles network errors.\ntype User = { id: number; name: string };\n\n/** Read the user record from the API. */\nexport async function getUser(id: number): Promise<User> {\n // Use the same endpoint for cached and fresh records.\n const response = await fetch(`/api/users/${id}`);\n if (!response.ok) {\n throw new Error("User not found");\n }\n\n // Keep JSON parsing in the typed request function.\n return response.json();\n}';
const pythonSource =
'from dataclasses import dataclass\n\n@dataclass\nclass User:\n id: int\n name: str\n\ndef greet(user: User) -> str:\n return f"Hello, {user.name}!"\n\nusers = [User(1, "Ada"), User(2, "Lin")]\nfor user in users:\n print(greet(user))';
const jsonSource =
'{\n "service": "users",\n "version": 2,\n "endpoints": [\n { "method": "GET", "path": "/api/users/:id" },\n { "method": "POST", "path": "/api/users" }\n ],\n "cache": {\n "enabled": true,\n "ttl": 300\n }\n}';
const HighlightedFile = defineComponent({
props: {
value: { type: String, required: true },
language: { type: String, required: true },
title: { type: String, required: true },
wrap: { type: Boolean, default: false },
},
setup(props) {
const lines = useHighlightedCode(
() => props.value,
() => props.language,
);
return () =>
h(CodeBlock, () => [
h(CodeBlockHeader, () => [
h(CodeBlockTitle, () => props.title),
h(CodeBlockActions, () =>
h(CodeBlockCopyButton, { value: props.value }),
),
]),
h(
CodeBlockContent,
{ lineNumbers: true, wrap: props.wrap, "aria-label": props.title },
() =>
lines.value.map((line, index) =>
h(CodeBlockLine, { key: index }, () =>
line.map((token, tokenIndex) =>
h(CodeBlockToken, { key: tokenIndex, token }),
),
),
),
),
h(CodeBlockFooter, () => props.language),
]);
},
});
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
</script>
<template>
<div
style="
display: grid;
gap: 1rem;
width: min(48rem, calc(100vw - 2rem));
max-width: 100%;
"
>
<HighlightedFile
:value="highlightedSource"
language="typescript"
title="users.ts"
/><HighlightedFile
:value="pythonSource"
language="python"
title="greet.py"
/><HighlightedFile
:value="jsonSource"
language="json"
title="service.json"
/>
</div>
</template>
Unsupported Language
<script setup lang="ts">
import { defineComponent } from "vue";
import { useHighlightedCode } from "@/components/ui/code-block";
import { h } from "vue";
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockHeader } from "@/components/ui/code-block";
import { CodeBlockTitle } from "@/components/ui/code-block";
import { CodeBlockActions } from "@/components/ui/code-block";
import { CodeBlockCopyButton } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
import { CodeBlockLine } from "@/components/ui/code-block";
import { CodeBlockToken } from "@/components/ui/code-block";
import { CodeBlockFooter } from "@/components/ui/code-block";
const HighlightedFile = defineComponent({
props: {
value: { type: String, required: true },
language: { type: String, required: true },
title: { type: String, required: true },
wrap: { type: Boolean, default: false },
},
setup(props) {
const lines = useHighlightedCode(
() => props.value,
() => props.language,
);
return () =>
h(CodeBlock, () => [
h(CodeBlockHeader, () => [
h(CodeBlockTitle, () => props.title),
h(CodeBlockActions, () =>
h(CodeBlockCopyButton, { value: props.value }),
),
]),
h(
CodeBlockContent,
{ lineNumbers: true, wrap: props.wrap, "aria-label": props.title },
() =>
lines.value.map((line, index) =>
h(CodeBlockLine, { key: index }, () =>
line.map((token, tokenIndex) =>
h(CodeBlockToken, { key: tokenIndex, token }),
),
),
),
),
h(CodeBlockFooter, () => props.language),
]);
},
});
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
const value = 'TASK deploy\n WHEN checks = passed\n THEN publish "preview"';
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<HighlightedFile
:value="value"
language="custom-dsl"
title="deploy.workflow"
/>
</div>
</template>
Highlighted Diff
<script setup lang="ts">
import { useHighlightedCode } from "@/components/ui/code-block";
import { CodeBlock } from "@/components/ui/code-block";
import { CodeBlockHeader } from "@/components/ui/code-block";
import { CodeBlockTitle } from "@/components/ui/code-block";
import { CodeBlockActions } from "@/components/ui/code-block";
import { CodeBlockCopyButton } from "@/components/ui/code-block";
import { CodeBlockContent } from "@/components/ui/code-block";
import { CodeBlockLine } from "@/components/ui/code-block";
import { CodeBlockToken } from "@/components/ui/code-block";
import { CodeBlockFooter } from "@/components/ui/code-block";
const highlightedDiffSource =
"const config = {\n retries: 1, // Stop after the first failure.\n retries: 3, // Retry temporary failures.\n timeout: 2000, // Keep the request deadline.\n};\n";
const args = {
} satisfies Partial<
InstanceType<typeof CodeBlock> extends { $props: infer Props } ? Props : {}
> &
Record<string, unknown>;
const lines = useHighlightedCode(highlightedDiffSource, "typescript");
</script>
<template>
<div style="width: min(44rem, calc(100vw - 2rem)); max-width: 100%">
<CodeBlock
><CodeBlockHeader
><CodeBlockTitle>config.ts</CodeBlockTitle
><CodeBlockActions
><CodeBlockCopyButton
:value="
highlightedDiffSource
" /></CodeBlockActions></CodeBlockHeader
><CodeBlockContent
line-numbers
aria-label="Highlighted configuration diff"
><CodeBlockLine
v-for="(line, index) in lines"
:key="index"
:variant="
index === 1
? 'removed'
: index === 2
? 'added'
: index === 3
? 'highlighted'
: 'default'
"
><CodeBlockToken
v-for="(token, tokenIndex) in line"
:key="tokenIndex"
:token="token" /></CodeBlockLine></CodeBlockContent
><CodeBlockFooter
>Syntax colors and explicit line variants.</CodeBlockFooter
></CodeBlock
>
</div>
</template>
API reference
Frontfriend-owned props. Native HTML and inherited primitive props
remain documented by Reka UI.
highlightCode
| Prop | Type | Default | Description |
code | string | — | Source text to highlight. |
language | string | — | A supported language name or alias. Returns null for an unsupported language or a failed load. |
loadHighlighter
| Prop | Type | Default | Description |
language | string | — | A supported language name or alias. Returns null for an unsupported language or a failed load. |
useHighlightedCode
| Prop | Type | Default | Description |
language | MaybeRefOrGetter<string> | — | A supported language name or alias. Unsupported names return plain text in the hook. |
value | MaybeRefOrGetter<string> | — | Source text to highlight. |
CodeBlockToken
| Prop | Type | Default | Description |
token | HighlightedToken | — | A token record from highlightCode or useHighlightedCode. Text is escaped; htmlStyle contains the syntax CSS variables. |
CodeBlock
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
size | "default" | "sm" | "default" | The code text size and spacing. |
variant | "default" | "destructive" | "default" | The color style of the block. |
CodeBlockActions
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
CodeBlockContent
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
lineNumbers | boolean | false | Show line numbers. Numbers are excluded from copied text. |
startLine | number | 1 | The first line number. |
wrap | boolean | false | Wrap long lines within the block. |
CodeBlockCopyButton
| Prop | Type | Default | Description |
as | keyof HTMLElementTagNameMap | — | |
asChild | boolean | — | |
class | HTMLAttributes["class"] | — | |
click | (event: MouseEvent) => void | — | Emitted when the copy button is clicked. Prevent the default event to cancel copying. |
size | ButtonVariants["size"] | "icon-xs" | The inherited Button size. |
value | string | — | The exact text to copy. Required. |
variant | ButtonVariants["variant"] | "ghost" | The inherited Button style. |
CodeBlockFooter
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
CodeBlockHeader
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
CodeBlockLine
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
variant | "default" | "added" | "removed" | "highlighted" | "default" | The style of this line. Diff markers are excluded from copied text. |
CodeBlockTitle
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
Configuration determines the actual classes and available styling hooks.
See Theming & component configuration before introducing
a repeated local override.