Data Grid

Load Error

A grid with a load-error state: the header stays put while the body carries the explanation, the error code, a retry button and an attempt counter.

  • datagrid
  • error-state
  • retry
  • 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-029?lang=en

Узлы сети

Данные не загружены

The header stays put: it is clear what failed to load
УзелРегионЗадержка, мсДоступность

Не удалось загрузить список узлов

Шлюз мониторинга не ответил за отведённое время. Данные в таблице могли устареть, поэтому она пуста, а не показывает старую выдачу.

ERR_GATEWAY_504

Повтор не запускался

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-029" (Load Error) 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-029.json

Registry item: https://vibeui.ru/r/datagrid-029.json
Installs to: components/vibeui/datagrid-029.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 load-error state: the header stays put while the body carries the explanation, the error code, a retry button and an attempt counter.

A grid with a load-error state and a retry: the error code, an explanation and an attempt counter. Zero dependencies, one file.

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

<Datagrid029
  caption="Node list"
  errorCode="ERR_TIMEOUT"
/>

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-029-* palette — do not swap it for your theme tokens
- the table header during the error: without it the table looks gone entirely
- role=alert on the error block — the reader may have looked away before the failure
- the error code beside the explanation: without it there is nothing to give support
- the attempt counter with aria-live, or the retry button looks broken
- the glyph drawn from borders instead of an image: the component must stay one file with no assets

## 6. You may change
- the line under the toolbar through caption
- the error code through errorCode
- the node list through rows
- the accent color through accent

## 7. Rules
- The «simulate failure» button exists for the catalog: in production the state comes from the request, so drop it.
- The third attempt succeeds in the demo — wire the button to a real retry in production.
- There is no «retrying» state: add a flag for a real request, or the retry fires twice.
- 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-029.json
https://vibeui.ru/r/datagrid-029.json

Компонент самодостаточен: один файл, без зависимостей, палитра в --vibeui-datagrid-029-*. Клиентский: одно булево состояние переключает сбой и данные, второе считает попытки повтора. Ошибка показана строкой внутри tbody с colSpan на всю ширину: шапка остаётся, поэтому видно, что именно не загрузилось — иначе кажется, что таблица пропала. Блок помечен role=alert, потому что пользователь мог уже увести взгляд. Счётчик попыток объявлен aria-live: без него кнопка повтора выглядит неработающей. Значок нарисован рамками и псевдоэлементами, чтобы компонент оставался одним файлом без картинок; третья попытка в демонстрации завершается успехом.

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 Datagrid029Row = {
  id: string
  node: string
  region: string
  latency: number
  uptime: string
}

export type Datagrid029Props = Omit<
  ComponentPropsWithoutRef<"section">,
  "children"
> & {
  rows?: Datagrid029Row[]
  caption?: string
  errorCode?: string
  accent?: string
}

// Идея компонента: неудачная загрузка — это состояние таблицы, а не пустой
// экран вместо неё. Шапка остаётся на месте, а в теле стоит объяснение с
// кодом ошибки и кнопкой повтора. Блок помечен role=alert, потому что
// пользователь мог уже увести взгляд; счётчик попыток показывает, что
// повтор действительно происходил, а не кнопка «ничего не делает».
const STYLES = `
:where([data-vibeui-block="datagrid-029"]){
--vibeui-datagrid-029-bg:oklch(1 0 0);
--vibeui-datagrid-029-fg:oklch(0.23 0.014 285);
--vibeui-datagrid-029-muted:oklch(0.55 0.014 285);
--vibeui-datagrid-029-border:oklch(0.92 0.006 285);
--vibeui-datagrid-029-head:oklch(0.975 0.003 285);
--vibeui-datagrid-029-accent:oklch(0.5 0.15 250);
--vibeui-datagrid-029-bad:oklch(0.53 0.19 27);
--vibeui-datagrid-029-badbg:oklch(0.975 0.025 27);
--vibeui-datagrid-029-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="datagrid-029"]{
box-sizing:border-box;width:100%;max-width:48rem;margin:0 auto;
background:var(--vibeui-datagrid-029-bg);color:var(--vibeui-datagrid-029-fg);
border:1px solid var(--vibeui-datagrid-029-border);border-radius:0.875rem;
font-family:var(--vibeui-datagrid-029-font);overflow:hidden;
}
[data-vibeui-block="datagrid-029"] *{box-sizing:border-box}
[data-vibeui-block="datagrid-029"] [data-part="bar"]{
display:flex;flex-wrap:wrap;align-items:center;gap:0.5rem;min-height:3rem;
padding:0.625rem 0.875rem;border-bottom:1px solid var(--vibeui-datagrid-029-border);
}
[data-vibeui-block="datagrid-029"] [data-part="title"]{margin:0;font-size:0.875rem;font-weight:650;margin-inline-end:auto}
[data-vibeui-block="datagrid-029"] [data-part="state"]{margin:0;font-size:0.75rem;color:var(--vibeui-datagrid-029-muted)}
[data-vibeui-block="datagrid-029"] [data-part="state"][data-bad="true"]{color:var(--vibeui-datagrid-029-bad);font-weight:600}
[data-vibeui-block="datagrid-029"] [data-part="sim"]{
appearance:none;cursor:pointer;font:inherit;font-size:0.75rem;
padding:0.3125rem 0.625rem;border-radius:0.5rem;
border:1px solid var(--vibeui-datagrid-029-border);
background:var(--vibeui-datagrid-029-bg);color:var(--vibeui-datagrid-029-fg);
}
[data-vibeui-block="datagrid-029"] [data-part="sim"]:focus-visible{outline:2px solid var(--vibeui-datagrid-029-accent);outline-offset:2px}
[data-vibeui-block="datagrid-029"] [data-part="scroll"]{overflow-x:auto}
[data-vibeui-block="datagrid-029"] [data-part="scroll"]:focus-visible{outline:2px solid var(--vibeui-datagrid-029-accent);outline-offset:-2px}
[data-vibeui-block="datagrid-029"] table{width:100%;border-collapse:collapse;font-size:0.8125rem}
[data-vibeui-block="datagrid-029"] caption{
padding:0.625rem 0.875rem;text-align:left;font-size:0.75rem;color:var(--vibeui-datagrid-029-muted);caption-side:top;
}
[data-vibeui-block="datagrid-029"] th,
[data-vibeui-block="datagrid-029"] td{
padding:0.5rem 0.875rem;text-align:left;white-space:nowrap;
border-top:1px solid var(--vibeui-datagrid-029-border);
}
[data-vibeui-block="datagrid-029"] thead th{background:var(--vibeui-datagrid-029-head);font-weight:600}
[data-vibeui-block="datagrid-029"] [data-align="end"]{text-align:right;font-variant-numeric:tabular-nums}
[data-vibeui-block="datagrid-029"] [data-part="node"]{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:0.75rem}
[data-vibeui-block="datagrid-029"] [data-part="fail-cell"]{padding:0;border-top:1px solid var(--vibeui-datagrid-029-border)}
[data-vibeui-block="datagrid-029"] [data-part="fail"]{
display:flex;flex-direction:column;align-items:center;gap:0.4375rem;
padding:1.75rem 1.25rem;text-align:center;background:var(--vibeui-datagrid-029-badbg);white-space:normal;
}
/* Значок нарисован рамками: компонент обязан оставаться одним файлом без картинок. */
[data-vibeui-block="datagrid-029"] [data-part="glyph"]{
position:relative;width:2rem;height:2rem;border-radius:999px;
border:2px solid var(--vibeui-datagrid-029-bad);color:var(--vibeui-datagrid-029-bad);
}
[data-vibeui-block="datagrid-029"] [data-part="glyph"]::before{
content:"";position:absolute;inset-inline-start:50%;inset-block-start:0.375rem;
width:2px;height:0.75rem;transform:translateX(-1px);background:currentColor;
}
[data-vibeui-block="datagrid-029"] [data-part="glyph"]::after{
content:"";position:absolute;inset-inline-start:50%;inset-block-end:0.375rem;
width:2px;height:2px;transform:translateX(-1px);background:currentColor;
}
[data-vibeui-block="datagrid-029"] [data-part="fail"] h4{margin:0;font-size:0.875rem;font-weight:650}
[data-vibeui-block="datagrid-029"] [data-part="fail"] p{margin:0;font-size:0.8125rem;color:var(--vibeui-datagrid-029-muted);max-width:26rem;line-height:1.45}
[data-vibeui-block="datagrid-029"] [data-part="code"]{
font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:0.6875rem;
padding:0.125rem 0.4375rem;border-radius:0.375rem;
border:1px solid var(--vibeui-datagrid-029-bad);color:var(--vibeui-datagrid-029-bad);
}
[data-vibeui-block="datagrid-029"] [data-part="retry"]{
appearance:none;cursor:pointer;font:inherit;font-size:0.8125rem;font-weight:600;margin-top:0.1875rem;
padding:0.4375rem 0.9375rem;border-radius:0.5rem;border:1px solid transparent;
background:var(--vibeui-datagrid-029-accent);color:oklch(1 0 0);
}
[data-vibeui-block="datagrid-029"] [data-part="retry"]:focus-visible{outline:2px solid var(--vibeui-datagrid-029-accent);outline-offset:2px}
[data-vibeui-block="datagrid-029"] [data-part="tries"]{font-size:0.6875rem!important}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="datagrid-029"] *{animation:none!important;transition:none!important}}
`

const DEFAULT_ROWS: Datagrid029Row[] = [
  {
    id: "s1",
    node: "eu-node-01",
    region: "Франкфурт",
    latency: 24,
    uptime: "99,98 %",
  },
  {
    id: "s2",
    node: "eu-node-02",
    region: "Амстердам",
    latency: 31,
    uptime: "99,91 %",
  },
  {
    id: "s3",
    node: "ru-node-11",
    region: "Москва",
    latency: 12,
    uptime: "99,99 %",
  },
  {
    id: "s4",
    node: "ru-node-12",
    region: "Новосибирск",
    latency: 48,
    uptime: "99,84 %",
  },
]

/**
 * Сетка с состоянием ошибки загрузки и повтором: шапка остаётся,
 * в теле стоит код ошибки и кнопка «Повторить». Один файл.
 */
export function Datagrid029({
  rows = DEFAULT_ROWS,
  caption = "Шапка остаётся на месте: понятно, что именно не загрузилось",
  errorCode = "ERR_GATEWAY_504",
  accent,
  className,
  style,
  ...props
}: Datagrid029Props) {
  const [failed, setFailed] = useState(true)
  const [tries, setTries] = useState(0)

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

  return (
    <>
      <style href="vibeui-datagrid-029" precedence="medium">
        {STYLES}
      </style>
      <section
        {...props}
        data-vibeui-block="datagrid-029"
        className={className}
        style={palette}
      >
        <div data-part="bar">
          <h3 data-part="title">Узлы сети</h3>
          <p data-part="state" data-bad={failed ? "true" : undefined}>
            {failed ? "Данные не загружены" : `Узлов: ${rows.length}`}
          </p>
          <button
            type="button"
            data-part="sim"
            aria-pressed={failed}
            onClick={() => {
              setFailed((value) => !value)
              setTries(0)
            }}
          >
            Симулировать сбой
          </button>
        </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" data-align="end">
                  Задержка, мс
                </th>
                <th scope="col" data-align="end">
                  Доступность
                </th>
              </tr>
            </thead>
            <tbody>
              {failed ? (
                <tr>
                  <td colSpan={4} data-part="fail-cell">
                    <div data-part="fail" role="alert">
                      <span data-part="glyph" aria-hidden="true" />
                      <h4>Не удалось загрузить список узлов</h4>
                      <p>
                        Шлюз мониторинга не ответил за отведённое время. Данные
                        в таблице могли устареть, поэтому она пуста, а не
                        показывает старую выдачу.
                      </p>
                      <span data-part="code">{errorCode}</span>
                      <button
                        type="button"
                        data-part="retry"
                        onClick={() => {
                          setTries((value) => value + 1)

                          if (tries >= 1) {
                            setFailed(false)
                          }
                        }}
                      >
                        Повторить запрос
                      </button>
                      <p data-part="tries" aria-live="polite">
                        {tries === 0
                          ? "Повтор не запускался"
                          : `Попыток повтора: ${tries}`}
                      </p>
                    </div>
                  </td>
                </tr>
              ) : (
                rows.map((row) => (
                  <tr key={row.id}>
                    <th scope="row" data-part="node">
                      {row.node}
                    </th>
                    <td>{row.region}</td>
                    <td data-align="end">{row.latency}</td>
                    <td data-align="end">{row.uptime}</td>
                  </tr>
                ))
              )}
            </tbody>
          </table>
        </div>
      </section>
    </>
  )
}