Code Block

Manager Tabs

The install command for npm, pnpm, yarn and bun at once: the tabs are radio buttons, so switching works without a single line of JavaScript.

  • code
  • tabs
  • package-manager
  • no-js

Preview

1440pxHost theme

Use it with AI

  1. 1. Copy the link.
  2. 2. Write to your agent in your own words and drop the link into the sentence.
  3. 3. The agent opens the link and installs the component from the registry.

put this in the header: https://vibeui.ru/c/codeblock-003?lang=en

npm install @vibeui/button --save-exact
pnpm add @vibeui/button --save-exact
yarn add @vibeui/button --exact
bun add @vibeui/button --exact
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-003" (Manager Tabs) 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-003.json

Registry item: https://vibeui.ru/r/codeblock-003.json
Installs to: components/vibeui/codeblock-003.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
The install command for npm, pnpm, yarn and bun at once: the tabs are radio buttons, so switching works without a single line of JavaScript.

An install command across four package managers, with tabs built on radio buttons and :has(). Zero dependencies, zero client JS, works before hydration.

## 3. How to use it
import { Codeblock003 } from "@/components/vibeui/codeblock-003"

<Codeblock003
  packageName="@vibeui/button"
  group="install-button"
/>

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-003-* palette — do not swap it for your theme tokens
- the radio buttons as the source of state: rewriting them to useState loses the no-JS behaviour
- hiding the input with clip-path rather than display:none — otherwise the tabs leave the tab order
- the focus ring through label:has(input:focus-visible): the input itself is invisible and shows nothing
- the order of managers: npm first, because it is preselected and shown immediately
- the dark surface of the block — it carries the contrast for the light monospaced text

## 6. You may change
- the package name through packageName
- the radio group name through group when a page holds several such blocks
- the set of managers and their flags in the constant inside the file
- the tab colours and the block width

## 7. Rules
- Two blocks sharing the same group on one page form a single radio group and switch each other.
- The choice is not remembered between pages: that needs client state and storage.
- There is deliberately no copy button — it would bring back the client JS this block exists to avoid.
- 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-003.json
https://vibeui.ru/r/codeblock-003.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-codeblock-003-*. Серверный: состояние держит нативная радиогруппа, а нужная панель выбирается селектором :has(input[value=...]:checked), поэтому вкладки переключаются до гидратации и без JS вообще. Радиокнопки спрятаны clip-path, а не display:none, чтобы остаться в порядке обхода клавиатурой; фокус и выбранная вкладка подсвечиваются через label:has(). Имя радиогруппы вынесено в пропс group: два блока на одной странице с общим именем переключали бы друг друга.

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 Codeblock003Props = {
  packageName?: string
  group?: string
  className?: string
  style?: CSSProperties
}

// Идея компонента: четыре менеджера пакетов в одном блоке без единой строки
// JS. Вкладки — это радиокнопки, а панель выбирается через :has() по
// отмеченному значению, поэтому переключение работает и до гидратации.
const STYLES = `
:where([data-vibeui-block="codeblock-003"]){
--vibeui-codeblock-003-bg:oklch(0.2 0.02 210);
--vibeui-codeblock-003-head:oklch(0.24 0.024 210);
--vibeui-codeblock-003-fg:oklch(0.94 0.006 210);
--vibeui-codeblock-003-muted:oklch(0.68 0.016 210);
--vibeui-codeblock-003-border:oklch(1 0 0 / 13%);
--vibeui-codeblock-003-active:oklch(0.98 0.01 210);
--vibeui-codeblock-003-accent:oklch(0.78 0.13 195);
--vibeui-codeblock-003-flag:oklch(0.8 0.12 195);
--vibeui-codeblock-003-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;
--vibeui-codeblock-003-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="codeblock-003"]{
display:block;width:100%;max-width:30rem;box-sizing:border-box;
font-family:var(--vibeui-codeblock-003-font);
}
[data-vibeui-block="codeblock-003"] [data-part="shell"]{
overflow:hidden;border:1px solid var(--vibeui-codeblock-003-border);border-radius:0.75rem;
background:var(--vibeui-codeblock-003-bg);color:var(--vibeui-codeblock-003-fg);
}
[data-vibeui-block="codeblock-003"] [data-part="tabs"]{
display:flex;gap:0.125rem;padding:0.3125rem 0.375rem;
background:var(--vibeui-codeblock-003-head);
border-bottom:1px solid var(--vibeui-codeblock-003-border);
}
[data-vibeui-block="codeblock-003"] label{
flex:1 1 0;cursor:pointer;text-align:center;
padding:0.375rem 0.5rem;border-radius:0.4375rem;
font-size:0.75rem;font-weight:650;color:var(--vibeui-codeblock-003-muted);
transition:background-color .16s ease,color .16s ease;
}
[data-vibeui-block="codeblock-003"] input{
position:absolute;width:1px;height:1px;margin:-1px;padding:0;
border:0;clip-path:inset(50%);overflow:hidden;white-space:nowrap;
}
[data-vibeui-block="codeblock-003"] label:hover{color:var(--vibeui-codeblock-003-fg)}
[data-vibeui-block="codeblock-003"] label:has(input:checked){
background:oklch(1 0 0 / 12%);color:var(--vibeui-codeblock-003-active);
}
[data-vibeui-block="codeblock-003"] label:has(input:focus-visible){
outline:2px solid var(--vibeui-codeblock-003-accent);outline-offset:2px;
}
[data-vibeui-block="codeblock-003"] pre{
display:none;margin:0;padding:0.875rem;overflow-x:auto;
font-family:var(--vibeui-codeblock-003-mono);font-size:0.8125rem;line-height:1.5;
}
[data-vibeui-block="codeblock-003"] code{font:inherit;white-space:pre}
[data-vibeui-block="codeblock-003"] [data-token="flag"]{color:var(--vibeui-codeblock-003-flag)}
[data-vibeui-block="codeblock-003"] [data-token="entity"]{color:var(--vibeui-codeblock-003-accent)}
/* Панель выбирается отмеченной радиокнопкой — без состояния в JS. */
[data-vibeui-block="codeblock-003"] [data-part="shell"]:has(input[value="npm"]:checked) pre[data-manager="npm"],
[data-vibeui-block="codeblock-003"] [data-part="shell"]:has(input[value="pnpm"]:checked) pre[data-manager="pnpm"],
[data-vibeui-block="codeblock-003"] [data-part="shell"]:has(input[value="yarn"]:checked) pre[data-manager="yarn"],
[data-vibeui-block="codeblock-003"] [data-part="shell"]:has(input[value="bun"]:checked) pre[data-manager="bun"]{display:block}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="codeblock-003"] *{animation:none!important;transition:none!important}}
`

const MANAGERS = [
  { id: "npm", binary: "npm", verb: "install", flag: "--save-exact" },
  { id: "pnpm", binary: "pnpm", verb: "add", flag: "--save-exact" },
  { id: "yarn", binary: "yarn", verb: "add", flag: "--exact" },
  { id: "bun", binary: "bun", verb: "add", flag: "--exact" },
] as const

/** Команда установки в четырёх менеджерах пакетов, переключение без JS. */
export function Codeblock003({
  packageName = "@vibeui/button",
  group = "vibeui-codeblock-003",
  className,
  style,
}: Codeblock003Props) {
  return (
    <>
      <style href="vibeui-codeblock-003" precedence="medium">
        {STYLES}
      </style>
      <div
        data-vibeui-block="codeblock-003"
        className={className}
        style={style}
      >
        <div data-part="shell">
          <div data-part="tabs" role="group" aria-label="Менеджер пакетов">
            {MANAGERS.map((manager, index) => (
              <label key={manager.id}>
                <input
                  type="radio"
                  name={group}
                  value={manager.id}
                  defaultChecked={index === 0}
                />
                {manager.binary}
              </label>
            ))}
          </div>
          {MANAGERS.map((manager) => (
            <pre key={manager.id} data-manager={manager.id}>
              <code>
                <span data-token="entity">{manager.binary}</span>
                {` ${manager.verb} ${packageName} `}
                <span data-token="flag">{manager.flag}</span>
              </code>
            </pre>
          ))}
        </div>
      </div>
    </>
  )
}