Code Block
Split Compare
Two versions of a file side by side: before on the left, after on the right, line facing line, and an empty slot is an explicit placeholder, not a lost piece of code.
- diff
- compare
- split
- review
Preview
Use it with AI
- 1. Copy the link.
- 2. Write to your agent in your own words and drop the link into the sentence.
- 3. The agent opens the link and installs the component from the registry.
put this in the header: https://vibeui.ru/c/codeblock-024?lang=en
export function total(items) { let sum = 0 for (const item of items) { sum += item.price } return sum}export function total(items) { return items.reduce( (sum, item) => sum + item.price, 0, ) }Show the full instructions
What the link says, spelled out. Use it when your agent cannot open links — paste this text instead.
# Install and place "codeblock-024" (Split Compare) from VibeUI
## 1. Install first — do not skip, do not recreate
Run this exact command before writing any code:
npx shadcn@latest add https://vibeui.ru/r/codeblock-024.json
Registry item: https://vibeui.ru/r/codeblock-024.json
Installs to: components/vibeui/codeblock-024.tsx (the exact path follows this project's components.json aliases).
npm dependencies: none.
Registry dependencies: none.
Install it from the registry. Do not recreate it from the description,
do not substitute a similar component from another library, and do not
rewrite it to match the project's existing style.
## 2. What it is
Two versions of a file side by side: before on the left, after on the right, line facing line, and an empty slot is an explicit placeholder, not a lost piece of code.
A two-pane comparison of file versions: rows aligned horizontally, gaps shown as placeholders. Zero dependencies, no client JS.
## 3. How to use it
import { Codeblock024 } from "@/components/vibeui/codeblock-024"
<Codeblock024
path="lib/total.ts"
beforeLabel="Before"
afterLabel="After"
/>
Read the installed file for the full prop list.
## 4. Where to place it
This is a small inline component. Put it exactly where the user asked,
inside the existing markup. Do not create a new page, section or
wrapper for it. If a similar control already sits in that spot,
replace it instead of adding a second one.
Placement: ___
(The user fills this line in. If it is still blank, ask where to put it
instead of guessing.)
## 5. Keep exactly as installed
- the local --vibeui-codeblock-024-* palette — do not swap it for your theme tokens
- the placeholders for missing rows: without them the other pane slides up and the comparison breaks
- the sign in the pane caption next to the colour: a tint alone is unreadable for colour blindness
- the counter that skips placeholders — otherwise the numbering drifts from the real file
- the layout rules on the inner shell: a container query never applies to the container itself
- the dark surface of the block — light monospaced text is unreadable without it
## 6. You may change
- the compared rows through the rows array
- the file path through path
- the pane captions through beforeLabel and afterLabel
- the removed and added hues to match your palette
## 7. Rules
- Matching the lines is the caller's job: rows arrives already aligned into pairs.
- On a narrow width the panes stack, and the comparison turns from horizontal into vertical.
- A change inside a line is not highlighted: the whole line is marked instead.
- Do not lift the CSS variables into globals.css: the component has to stay a single file.
## 8. Verify
- it renders with no console errors;
- it looks like the preview on the VibeUI page you copied this from;
- if it does not, you changed something listed in section 5 — put it back.For developers
npx shadcn@latest add https://vibeui.ru/r/codeblock-024.jsonhttps://vibeui.ru/r/codeblock-024.jsonКомпонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-codeblock-024-*. Серверный: хуков нет. Панели получают один и тот же массив rows, поэтому строки всегда стоят напротив друг друга; отсутствующая строка рисуется заглушкой с другой подложкой, и счётчик номеров её пропускает — нумерация остаётся верной для каждой версии отдельно. Раскладка считается от собственной ширины: до 34rem панели встают друг под друга, дальше делят ширину пополам, и правила лежат на внутреннем [data-part="shell"].
Component source
The same file your agent installs. Here in case you would rather copy it by hand.
import type { CSSProperties } from "react"
export type Codeblock024Row = {
before: string | null
after: string | null
}
export type Codeblock024Props = {
path?: string
beforeLabel?: string
afterLabel?: string
rows?: Codeblock024Row[]
className?: string
style?: CSSProperties
}
// Идея компонента: две версии файла рядом, строка напротив строки. Пустые
// места слева и справа — это не пропуск, а явная заглушка, поэтому глаз ведёт
// по одной горизонтали; на узкой ширине панели встают друг под друга.
const STYLES = `
:where([data-vibeui-block="codeblock-024"]){
--vibeui-codeblock-024-bg:oklch(0.19 0.014 265);
--vibeui-codeblock-024-head:oklch(0.23 0.018 265);
--vibeui-codeblock-024-fg:oklch(0.93 0.008 265);
--vibeui-codeblock-024-muted:oklch(0.66 0.016 265);
--vibeui-codeblock-024-gutter:oklch(0.5 0.02 265);
--vibeui-codeblock-024-border:oklch(1 0 0 / 12%);
--vibeui-codeblock-024-del:oklch(0.78 0.16 25);
--vibeui-codeblock-024-del-bg:oklch(0.5 0.16 25 / 18%);
--vibeui-codeblock-024-add:oklch(0.82 0.15 152);
--vibeui-codeblock-024-add-bg:oklch(0.5 0.14 152 / 18%);
--vibeui-codeblock-024-void:oklch(1 0 0 / 4%);
--vibeui-codeblock-024-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;
--vibeui-codeblock-024-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
container-type:inline-size;
}
[data-vibeui-block="codeblock-024"]{
display:block;width:100%;max-width:44rem;box-sizing:border-box;margin:0;
overflow:hidden;border:1px solid var(--vibeui-codeblock-024-border);
border-radius:0.75rem;
background:var(--vibeui-codeblock-024-bg);color:var(--vibeui-codeblock-024-fg);
font-family:var(--vibeui-codeblock-024-font);
}
[data-vibeui-block="codeblock-024"] [data-part="head"]{
padding:0.5rem 0.875rem;
background:var(--vibeui-codeblock-024-head);
border-bottom:1px solid var(--vibeui-codeblock-024-border);
font-family:var(--vibeui-codeblock-024-mono);font-size:0.75rem;
color:var(--vibeui-codeblock-024-muted);
}
[data-vibeui-block="codeblock-024"] [data-part="shell"]{
display:grid;grid-template-columns:1fr;
}
[data-vibeui-block="codeblock-024"] [data-part="pane"]{min-width:0}
[data-vibeui-block="codeblock-024"] [data-part="pane"] + [data-part="pane"]{
border-top:1px solid var(--vibeui-codeblock-024-border);
}
[data-vibeui-block="codeblock-024"] [data-part="label"]{
display:flex;align-items:center;gap:0.375rem;
padding:0.3125rem 0.875rem;
border-bottom:1px solid var(--vibeui-codeblock-024-border);
font-size:0.6875rem;font-weight:650;letter-spacing:0.04em;text-transform:uppercase;
color:var(--vibeui-codeblock-024-muted);
}
[data-vibeui-block="codeblock-024"] [data-part="label"]::before{
content:"−";flex:none;color:var(--vibeui-codeblock-024-del);font-weight:700;
}
[data-vibeui-block="codeblock-024"] [data-side="after"] [data-part="label"]::before{
content:"+";color:var(--vibeui-codeblock-024-add);
}
[data-vibeui-block="codeblock-024"] pre{margin:0;padding:0.5rem 0;overflow-x:auto}
[data-vibeui-block="codeblock-024"] code{
display:block;min-width:max-content;
font-family:var(--vibeui-codeblock-024-mono);
font-size:0.75rem;line-height:1.75;white-space:pre;
counter-reset:line;
}
[data-vibeui-block="codeblock-024"] [data-part="row"]{
display:block;position:relative;padding:0 0.75rem 0 2.5rem;min-height:1.75em;
}
[data-vibeui-block="codeblock-024"] [data-part="row"]:not([data-void="true"])::before{
counter-increment:line;content:counter(line);
position:absolute;left:0;width:1.75rem;text-align:right;
color:var(--vibeui-codeblock-024-gutter);font-variant-numeric:tabular-nums;
user-select:none;-webkit-user-select:none;pointer-events:none;
}
/* Заглушка вместо строки: без неё соседняя панель уехала бы вверх и
сравнение по горизонтали перестало бы работать. */
[data-vibeui-block="codeblock-024"] [data-part="row"][data-void="true"]{
background:var(--vibeui-codeblock-024-void);
}
[data-vibeui-block="codeblock-024"] [data-side="before"] [data-part="row"][data-changed="true"]{
background:var(--vibeui-codeblock-024-del-bg);
box-shadow:inset 0.125rem 0 0 var(--vibeui-codeblock-024-del);
}
[data-vibeui-block="codeblock-024"] [data-side="after"] [data-part="row"][data-changed="true"]{
background:var(--vibeui-codeblock-024-add-bg);
box-shadow:inset 0.125rem 0 0 var(--vibeui-codeblock-024-add);
}
@container (min-width: 34rem){
[data-vibeui-block="codeblock-024"] [data-part="shell"]{grid-template-columns:1fr 1fr}
[data-vibeui-block="codeblock-024"] [data-part="pane"] + [data-part="pane"]{
border-top:0;border-left:1px solid var(--vibeui-codeblock-024-border);
}
}
`
const ROWS: Codeblock024Row[] = [
{
before: "export function total(items) {",
after: "export function total(items) {",
},
{ before: " let sum = 0", after: " return items.reduce(" },
{
before: " for (const item of items) {",
after: " (sum, item) => sum + item.price,",
},
{ before: " sum += item.price", after: " 0," },
{ before: " }", after: " )" },
{ before: " return sum", after: null },
{ before: "}", after: "}" },
]
/** Две версии файла рядом: было слева, стало справа, строка напротив строки. */
export function Codeblock024({
path = "lib/total.ts",
beforeLabel = "Было",
afterLabel = "Стало",
rows = ROWS,
className,
style,
}: Codeblock024Props) {
const panes = [
{ side: "before" as const, label: beforeLabel },
{ side: "after" as const, label: afterLabel },
]
return (
<>
<style href="vibeui-codeblock-024" precedence="medium">
{STYLES}
</style>
<figure
data-vibeui-block="codeblock-024"
className={className}
style={style}
>
<figcaption data-part="head">{path}</figcaption>
<div data-part="shell">
{panes.map((pane) => (
<div key={pane.side} data-part="pane" data-side={pane.side}>
<div data-part="label">{pane.label}</div>
<pre>
<code>
{rows.map((row, index) => {
const text = row[pane.side]
const changed = row.before !== row.after
return (
<span
key={index}
data-part="row"
data-void={text === null || undefined}
data-changed={(changed && text !== null) || undefined}
>
{text ?? " "}
</span>
)
})}
</code>
</pre>
</div>
))}
</div>
</figure>
</>
)
}