Data Grid

Density Switch

A grid with a density switch: compact, regular and roomy change the padding, the type size and the row height at once, and the roomy setting reveals a note.

  • datagrid
  • density
  • settings
  • table

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/datagrid-027?lang=en

Документы сделки

Плотность
Density changes padding, type size and row height at once
ДокументАвторИзменёнРазмер
Договор поставки 2026Ким12 марта412 КБ
Спецификация партии №14Лапина11 марта88 КБ
Акт сверки за февральМещеряков9 марта57 КБ
Регламент приёмкиГараев6 марта1,2 МБ
Протокол встречи 04.03Ким4 марта24 КБ

Плотность: обычно, высота строки ≈36 px, примечание скрыто

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 "datagrid-027" (Density Switch) 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/datagrid-027.json

Registry item: https://vibeui.ru/r/datagrid-027.json
Installs to: components/vibeui/datagrid-027.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
A grid with a density switch: compact, regular and roomy change the padding, the type size and the row height at once, and the roomy setting reveals a note.

A grid with three density levels: padding, type size and row height change together. Zero dependencies, one file.

## 3. How to use it
import { Datagrid027 } from "@/components/vibeui/datagrid-027"

<Datagrid027 caption="Deal documents" density="compact" />

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-datagrid-027-* palette — do not swap it for your theme tokens
- the switch as radio buttons inside a fieldset: this is one choice of three, not three buttons
- changing the type size and leading along with the padding — row height alone is not density
- the density variables on the root through data-density rather than per-cell rules
- repeating the state in text with role=status, or a density change is never announced
- the grid's own light surface: on the dark catalog card dark text disappears without it

## 6. You may change
- the line under the toolbar through caption
- the initial density through density: compact, regular or roomy
- the document list through rows
- the accent color through accent

## 7. Rules
- The note only appears at roomy density: it is part of the mode, not a separate toggle.
- Compact drops the type to 12 px — do not go lower, the text stops being readable.
- Density lives in state and resets on reload: persist the reader's choice yourself.
- 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/datagrid-027.json
https://vibeui.ru/r/datagrid-027.json

Компонент самодостаточен: один файл, без зависимостей, палитра в --vibeui-datagrid-027-*. Клиентский: useState хранит режим, который попадает в data-density на корне; три переменные — отступ, кегль и интерлиньяж — переопределяются одним селектором на режим, поэтому новых правил для ячеек не нужно. Плотность здесь не только про отступ: на просторном режиме показывается вторая строка с примечанием, которой в компактном нет, — иначе «просторно» это просто воздух. Переключатель собран радиокнопками внутри fieldset: это выбор одного из трёх, а не три независимые кнопки. Текущее состояние продублировано текстом с role=status.

Component source

The same file your agent installs. Here in case you would rather copy it by hand.

"use client"

import { useState } from "react"
import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Datagrid027Row = {
  id: string
  document: string
  author: string
  note: string
  size: string
  changed: string
}

export type Datagrid027Props = Omit<
  ComponentPropsWithoutRef<"section">,
  "children"
> & {
  rows?: Datagrid027Row[]
  caption?: string
  density?: "compact" | "regular" | "roomy"
  accent?: string
}

// Идея компонента: плотность — это не только отступ. Переключатель меняет
// сразу три переменные: вертикальный отступ, размер шрифта и высоту
// строки, а на просторной плотности показывает вторую строку с примечанием,
// которой в компактном режиме нет. Переключатель собран радиокнопками в
// fieldset: это выбор одного из трёх, а не три независимые кнопки.
const STYLES = `
:where([data-vibeui-block="datagrid-027"]){
--vibeui-datagrid-027-bg:oklch(1 0 0);
--vibeui-datagrid-027-fg:oklch(0.23 0.014 285);
--vibeui-datagrid-027-muted:oklch(0.55 0.014 285);
--vibeui-datagrid-027-border:oklch(0.92 0.006 285);
--vibeui-datagrid-027-head:oklch(0.975 0.003 285);
--vibeui-datagrid-027-accent:oklch(0.5 0.14 145);
--vibeui-datagrid-027-chip:oklch(0.96 0.03 145);
--vibeui-datagrid-027-pad:0.5rem;
--vibeui-datagrid-027-size:0.8125rem;
--vibeui-datagrid-027-lead:1.4;
--vibeui-datagrid-027-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="datagrid-027"]{
box-sizing:border-box;width:100%;max-width:50rem;margin:0 auto;
background:var(--vibeui-datagrid-027-bg);color:var(--vibeui-datagrid-027-fg);
border:1px solid var(--vibeui-datagrid-027-border);border-radius:0.875rem;
font-family:var(--vibeui-datagrid-027-font);overflow:hidden;
}
[data-vibeui-block="datagrid-027"] *{box-sizing:border-box}
[data-vibeui-block="datagrid-027"][data-density="compact"]{
--vibeui-datagrid-027-pad:0.25rem;--vibeui-datagrid-027-size:0.75rem;--vibeui-datagrid-027-lead:1.25;
}
[data-vibeui-block="datagrid-027"][data-density="roomy"]{
--vibeui-datagrid-027-pad:0.875rem;--vibeui-datagrid-027-size:0.875rem;--vibeui-datagrid-027-lead:1.55;
}
[data-vibeui-block="datagrid-027"] [data-part="bar"]{
display:flex;flex-wrap:wrap;align-items:center;gap:0.5rem;
padding:0.75rem 0.875rem;border-bottom:1px solid var(--vibeui-datagrid-027-border);
}
[data-vibeui-block="datagrid-027"] [data-part="title"]{margin:0;font-size:0.875rem;font-weight:650;margin-inline-end:auto}
[data-vibeui-block="datagrid-027"] [data-part="switch"]{
border:1px solid var(--vibeui-datagrid-027-border);border-radius:0.5rem;
margin:0;padding:0.1875rem;display:flex;gap:0.1875rem;
}
[data-vibeui-block="datagrid-027"] [data-part="switch"] legend{
padding:0 0.25rem;font-size:0.625rem;font-weight:600;letter-spacing:0.04em;text-transform:uppercase;
color:var(--vibeui-datagrid-027-muted);
}
[data-vibeui-block="datagrid-027"] [data-part="switch"] label{
display:inline-flex;align-items:center;gap:0.3125rem;cursor:pointer;
padding:0.25rem 0.5rem;border-radius:0.375rem;font-size:0.75rem;color:var(--vibeui-datagrid-027-muted);
}
[data-vibeui-block="datagrid-027"] [data-part="switch"] label:has(input:checked){
background:var(--vibeui-datagrid-027-chip);color:var(--vibeui-datagrid-027-accent);font-weight:600;
}
[data-vibeui-block="datagrid-027"] [data-part="switch"] input{accent-color:var(--vibeui-datagrid-027-accent);margin:0;width:0.8125rem;height:0.8125rem}
[data-vibeui-block="datagrid-027"] [data-part="switch"] input:focus-visible{outline:2px solid var(--vibeui-datagrid-027-accent);outline-offset:2px}
[data-vibeui-block="datagrid-027"] [data-part="scroll"]{overflow-x:auto}
[data-vibeui-block="datagrid-027"] [data-part="scroll"]:focus-visible{outline:2px solid var(--vibeui-datagrid-027-accent);outline-offset:-2px}
[data-vibeui-block="datagrid-027"] table{
width:100%;border-collapse:collapse;
font-size:var(--vibeui-datagrid-027-size);line-height:var(--vibeui-datagrid-027-lead);
}
[data-vibeui-block="datagrid-027"] caption{
padding:0.625rem 0.875rem;text-align:left;font-size:0.75rem;color:var(--vibeui-datagrid-027-muted);caption-side:top;
}
[data-vibeui-block="datagrid-027"] th,
[data-vibeui-block="datagrid-027"] td{
padding:var(--vibeui-datagrid-027-pad) 0.875rem;text-align:left;white-space:nowrap;
border-top:1px solid var(--vibeui-datagrid-027-border);
}
[data-vibeui-block="datagrid-027"] thead th{background:var(--vibeui-datagrid-027-head);font-weight:600}
[data-vibeui-block="datagrid-027"] [data-align="end"]{text-align:right;font-variant-numeric:tabular-nums}
[data-vibeui-block="datagrid-027"] [data-part="note"]{
display:block;margin-top:0.1875rem;font-size:0.6875rem;font-weight:400;color:var(--vibeui-datagrid-027-muted);
white-space:normal;
}
[data-vibeui-block="datagrid-027"] [data-part="author"]{color:var(--vibeui-datagrid-027-muted)}
[data-vibeui-block="datagrid-027"] [data-part="metrics"]{
margin:0;padding:0.5rem 0.875rem 0.625rem;border-top:1px solid var(--vibeui-datagrid-027-border);
font-size:0.6875rem;color:var(--vibeui-datagrid-027-muted);
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="datagrid-027"] *{animation:none!important;transition:none!important}}
`

const DEFAULT_ROWS: Datagrid027Row[] = [
  {
    id: "f1",
    document: "Договор поставки 2026",
    author: "Ким",
    note: "Согласован с юристами, ждём подпись контрагента",
    size: "412 КБ",
    changed: "12 марта",
  },
  {
    id: "f2",
    document: "Спецификация партии №14",
    author: "Лапина",
    note: "Обновлены цены по позициям 3 и 7",
    size: "88 КБ",
    changed: "11 марта",
  },
  {
    id: "f3",
    document: "Акт сверки за февраль",
    author: "Мещеряков",
    note: "Расхождение 14 200 ₽, отправлено на уточнение",
    size: "57 КБ",
    changed: "9 марта",
  },
  {
    id: "f4",
    document: "Регламент приёмки",
    author: "Гараев",
    note: "Черновик, раздел про возвраты не дописан",
    size: "1,2 МБ",
    changed: "6 марта",
  },
  {
    id: "f5",
    document: "Протокол встречи 04.03",
    author: "Ким",
    note: "Решения зафиксированы, задачи заведены",
    size: "24 КБ",
    changed: "4 марта",
  },
]

const MODES = [
  { value: "compact", label: "Компактно", height: "≈28 px" },
  { value: "regular", label: "Обычно", height: "≈36 px" },
  { value: "roomy", label: "Просторно", height: "≈48 px" },
] as const

/**
 * Сетка с переключателем плотности: отступ, кегль и высота строки меняются
 * вместе, на просторной плотности появляется примечание. Один файл.
 */
export function Datagrid027({
  rows = DEFAULT_ROWS,
  caption = "Плотность меняет отступ, кегль и высоту строки одновременно",
  density = "regular",
  accent,
  className,
  style,
  ...props
}: Datagrid027Props) {
  const [mode, setMode] = useState<(typeof MODES)[number]["value"]>(density)

  const current = MODES.find((item) => item.value === mode) ?? MODES[1]

  const palette = {
    ...(accent ? { "--vibeui-datagrid-027-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-datagrid-027" precedence="medium">
        {STYLES}
      </style>
      <section
        {...props}
        data-vibeui-block="datagrid-027"
        data-density={mode}
        className={className}
        style={palette}
      >
        <div data-part="bar">
          <h3 data-part="title">Документы сделки</h3>
          <fieldset data-part="switch">
            <legend>Плотность</legend>
            {MODES.map((item) => (
              <label key={item.value}>
                <input
                  type="radio"
                  name="vibeui-datagrid-027-density"
                  value={item.value}
                  checked={mode === item.value}
                  onChange={() => setMode(item.value)}
                />
                {item.label}
              </label>
            ))}
          </fieldset>
        </div>
        <div
          data-part="scroll"
          role="region"
          aria-label="Таблица документов, прокручивается вбок"
          tabIndex={0}
        >
          <table>
            <caption>{caption}</caption>
            <thead>
              <tr>
                <th scope="col">Документ</th>
                <th scope="col">Автор</th>
                <th scope="col">Изменён</th>
                <th scope="col" data-align="end">
                  Размер
                </th>
              </tr>
            </thead>
            <tbody>
              {rows.map((row) => (
                <tr key={row.id}>
                  <th scope="row">
                    {row.document}
                    {mode === "roomy" ? (
                      <span data-part="note">{row.note}</span>
                    ) : null}
                  </th>
                  <td data-part="author">{row.author}</td>
                  <td>{row.changed}</td>
                  <td data-align="end">{row.size}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
        <p data-part="metrics" role="status" aria-live="polite">
          Плотность: {current.label.toLowerCase()}, высота строки{" "}
          {current.height}
          {mode === "roomy" ? ", примечание показано" : ", примечание скрыто"}
        </p>
      </section>
    </>
  )
}