Display

Tile Grid Skeleton

A gallery skeleton that counts its own columns: auto-fill with minmax measures the block's width rather than the viewport, so a narrow card gets two tiles and a wide page gets six.

  • skeleton
  • grid
  • gallery
  • display

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/skeleton-007?lang=en

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 "skeleton-007" (Tile Grid Skeleton) 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/skeleton-007.json

Registry item: https://vibeui.ru/r/skeleton-007.json
Installs to: components/vibeui/skeleton-007.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 gallery skeleton that counts its own columns: auto-fill with minmax measures the block's width rather than the viewport, so a narrow card gets two tiles and a wide page gets six.

A tile grid skeleton: auto-fill derives the column count from the block width, and tiles stay square by aspect ratio. Zero dependencies, one file, server rendered.

## 3. How to use it
import { Skeleton007 } from "@/components/vibeui/skeleton-007"

<Skeleton007 tiles={8} minTile="6rem" label="Gallery is loading" />

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-skeleton-007-* palette — do not swap it for your theme tokens
- auto-fill with minmax: the layout must measure the block, not the viewport
- the square via aspect-ratio rather than a fixed height — otherwise the tiles stop stretching
- the wrapped shimmer delay: a linear wave across twenty tiles looks broken
- role="status" and aria-busy on the root, or the skeleton is silent to screen readers
- its own light surface: on a dark gallery the grey tiles are invisible otherwise

## 6. You may change
- the tile count through tiles
- the minimum tile width through minTile
- the screen-reader caption through label
- the grid gap and tile radii in CSS

## 7. Rules
- minTile sets a lower bound, not a width: the real tile stretches to the 1fr column.
- Match the tile count to your first page size, otherwise the grid jerks when data lands.
- The component caps at twenty-four tiles: a skeleton heavier than the gallery serves no one.
- 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/skeleton-007.json
https://vibeui.ru/r/skeleton-007.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-skeleton-007-*. Серверный: хуков нет. Сетка объявлена как repeat(auto-fill, minmax(var(--vibeui-skeleton-007-min), 1fr)): число колонок считается от собственной ширины блока, поэтому раскладка одинаково честна и в карточке каталога, и на полной странице. Плитка квадратная по aspect-ratio, а не по высоте, поэтому тянется вместе с колонкой. Задержка блика раздаётся по кругу из шести значений: волна идёт по сетке, но не растягивается бесконечно при росте числа плиток. Корень помечен role="status" и aria-busy.

Component source

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

import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Skeleton007Props = ComponentPropsWithoutRef<"div"> & {
  tiles?: number
  /** Минимальная ширина плитки: от неё сетка сама считает число колонок. */
  minTile?: string
  label?: string
}

// Идея компонента: заглушка галереи считает колонки сама — auto-fill с
// minmax от собственной ширины блока, а не от ширины окна. Поэтому в узкой
// колонке карточки каталога плиток будет две, а на широкой странице шесть,
// и настоящая галерея встанет в ту же сетку без прыжка.
const STYLES = `
:where([data-vibeui-block="skeleton-007"]){
--vibeui-skeleton-007-bg:oklch(1 0 0);
--vibeui-skeleton-007-border:oklch(0.9 0.006 265);
--vibeui-skeleton-007-base:oklch(0.93 0.005 265);
--vibeui-skeleton-007-shine:oklch(0.97 0.003 265);
--vibeui-skeleton-007-min:6rem;
--vibeui-skeleton-007-delay:0s;
}
[data-vibeui-block="skeleton-007"]{
width:100%;max-width:30rem;box-sizing:border-box;padding:0.875rem;
background:var(--vibeui-skeleton-007-bg);
border:1px solid var(--vibeui-skeleton-007-border);border-radius:1rem;
}
/* Колонки считает сама сетка: число плиток в ряду зависит от ширины блока. */
[data-vibeui-block="skeleton-007"] [data-part="grid"]{
display:grid;gap:0.625rem;
grid-template-columns:repeat(auto-fill,minmax(var(--vibeui-skeleton-007-min),1fr));
}
[data-vibeui-block="skeleton-007"] [data-part="tile"]{
display:flex;flex-direction:column;gap:0.375rem;
animation-delay:var(--vibeui-skeleton-007-delay);
}
[data-vibeui-block="skeleton-007"] [data-part="thumb"],
[data-vibeui-block="skeleton-007"] [data-part="cap"]{
background:linear-gradient(90deg,
var(--vibeui-skeleton-007-base) 0%,
var(--vibeui-skeleton-007-shine) 50%,
var(--vibeui-skeleton-007-base) 100%) 0 0 / 200% 100%;
animation:vibeui-skeleton-007-sweep 1.5s ease-in-out infinite;
animation-delay:inherit;
}
/* Квадрат задан пропорцией, а не высотой: плитка тянется вместе с колонкой. */
[data-vibeui-block="skeleton-007"] [data-part="thumb"]{
aspect-ratio:1 / 1;border-radius:0.625rem;
}
[data-vibeui-block="skeleton-007"] [data-part="cap"]{
height:0.5625rem;width:74%;border-radius:0.1875rem;
}
[data-vibeui-block="skeleton-007"] [data-part="tile"]:nth-child(3n) [data-part="cap"]{width:52%}
[data-vibeui-block="skeleton-007"] [data-part="tile"]:nth-child(4n) [data-part="cap"]{width:88%}
@keyframes vibeui-skeleton-007-sweep{
0%{background-position:120% 0}
100%{background-position:-20% 0}
}
@media (prefers-reduced-motion:reduce){
[data-vibeui-block="skeleton-007"] *{animation:none!important;transition:none!important}
[data-vibeui-block="skeleton-007"] [data-part="thumb"],
[data-vibeui-block="skeleton-007"] [data-part="cap"]{background:var(--vibeui-skeleton-007-base)}
}
`

/**
 * Заглушка сетки плиток: число колонок считает auto-fill.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Skeleton007({
  tiles = 8,
  minTile = "6rem",
  label = "Галерея загружается",
  className,
  style,
  ...props
}: Skeleton007Props) {
  const count = Math.min(24, Math.max(1, tiles))
  const palette = {
    "--vibeui-skeleton-007-min": minTile,
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-skeleton-007" precedence="medium">
        {STYLES}
      </style>
      <div
        {...props}
        data-vibeui-block="skeleton-007"
        role="status"
        aria-busy="true"
        aria-label={label}
        className={className}
        style={palette}
      >
        <div data-part="grid">
          {Array.from({ length: count }, (_, index) => (
            <div
              key={index}
              data-part="tile"
              style={
                {
                  "--vibeui-skeleton-007-delay": `${(index % 6) * 0.08}s`,
                } as CSSProperties
              }
            >
              <span data-part="thumb" />
              <span data-part="cap" />
            </div>
          ))}
        </div>
      </div>
    </>
  )
}