Display
Content Skeleton
A loading placeholder that mirrors the metrics of the content to come — same line heights, same gaps — so the page does not jump when the data arrives.
- skeleton
- loading
- placeholder
- shimmer
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/skeleton-001?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-001" (Content 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-001.json
Registry item: https://vibeui.ru/r/skeleton-001.json
Installs to: components/vibeui/skeleton-001.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 loading placeholder that mirrors the metrics of the content to come — same line heights, same gaps — so the page does not jump when the data arrives.
A loading placeholder in three shapes: a paragraph, an avatar row and a block for an image. Heights and gaps mirror the content that will replace it, and the last line is shorter so the placeholder reads as text. The shimmer rides a gradient and goes away entirely under prefers-reduced-motion. Zero dependencies, one file, its own palette.
## 3. How to use it
import { Skeleton001 } from "@/components/vibeui/skeleton-001"
<Skeleton001 shape="text" lines={3} label="Loading the article" />
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-001-* palette — do not swap it for your theme tokens (bg-muted, animate-pulse and the like)
- matching the metrics of the real content: the line height and gaps are the whole point, otherwise the page jumps on load
- the shortened last line: without it the paragraph placeholder looks like a table
- the shimmer through background-position on a 200% gradient — a separate moving layer makes the browser repaint more
- role="status", aria-busy and the label: without them the loading state does not exist for a screen reader
- the prefers-reduced-motion rule that kills the shimmer and leaves a flat fill
- the <style> block inside the component — it holds the palette, the shapes and the animation
## 6. You may change
- shape: text for a paragraph, avatar for a list row, block for an image or a map
- lines — how many rows the text shape draws
- label — what is loading; a screen reader announces this text
- width and outer spacing through className: by default it fills its parent
## 7. Rules
- Do not leave a skeleton as the only answer for more than a couple of seconds: long operations need progress or an explanation.
- Do not flash a skeleton on fast requests — a 100 ms flicker is worse than empty space.
- Keep the row count close to the real content: eight rows in front of a two-line paragraph reads as a bug.
- 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-001.jsonhttps://vibeui.ru/r/skeleton-001.jsonКомпонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-skeleton-001-*. Блик — движение background-position у градиента шириной 200%, а не отдельный слой: перерисовывается только фон. Три формы (text, avatar, block) переключаются data-атрибутом. Контейнер объявлен как 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 Skeleton001Shape = "text" | "avatar" | "block"
export type Skeleton001Props = ComponentPropsWithoutRef<"div"> & {
shape?: Skeleton001Shape
/** Сколько строк рисовать в форме text. Последняя всегда короче. */
lines?: number
/** Подпись для скринридера: что именно грузится. */
label?: string
}
// Идея компонента: заглушка повторяет метрику будущего текста, а не рисует
// серые прямоугольники наугад. Высота строки и промежутки те же, что у
// абзаца, поэтому при появлении данных страница не прыгает.
const STYLES = `
:where([data-vibeui-block="skeleton-001"]){
--vibeui-skeleton-001-base:oklch(0.93 0.005 265);
--vibeui-skeleton-001-shine:oklch(0.97 0.003 265);
--vibeui-skeleton-001-radius:0.375rem;
--vibeui-skeleton-001-line:1rem;
--vibeui-skeleton-001-gap:0.625rem;
}
[data-vibeui-block="skeleton-001"]{
display:flex;flex-direction:column;gap:var(--vibeui-skeleton-001-gap);
width:100%;
}
[data-vibeui-block="skeleton-001"] [data-part="bar"]{
height:var(--vibeui-skeleton-001-line);
border-radius:var(--vibeui-skeleton-001-radius);
background:
linear-gradient(90deg,var(--vibeui-skeleton-001-base) 0%,var(--vibeui-skeleton-001-shine) 50%,var(--vibeui-skeleton-001-base) 100%)
0 0 / 200% 100%;
animation:vibeui-skeleton-001-sweep 1.4s ease-in-out infinite;
}
/* Последняя строка короче: так абзац-заглушка читается как текст. */
[data-vibeui-block="skeleton-001"] [data-part="bar"]:last-child{width:62%}
[data-vibeui-block="skeleton-001"][data-shape="avatar"]{flex-direction:row;align-items:center;gap:0.75rem}
[data-vibeui-block="skeleton-001"][data-shape="avatar"] [data-part="circle"]{
width:2.5rem;height:2.5rem;flex:none;border-radius:9999px;
background:
linear-gradient(90deg,var(--vibeui-skeleton-001-base) 0%,var(--vibeui-skeleton-001-shine) 50%,var(--vibeui-skeleton-001-base) 100%)
0 0 / 200% 100%;
animation:vibeui-skeleton-001-sweep 1.4s ease-in-out infinite;
}
[data-vibeui-block="skeleton-001"][data-shape="avatar"] [data-part="stack"]{
display:flex;flex-direction:column;gap:0.5rem;flex:1 1 auto;min-width:0;
}
[data-vibeui-block="skeleton-001"][data-shape="avatar"] [data-part="bar"]:first-child{width:45%}
[data-vibeui-block="skeleton-001"][data-shape="block"] [data-part="bar"]{
height:auto;aspect-ratio:16 / 9;width:100%;
border-radius:0.75rem;
}
@keyframes vibeui-skeleton-001-sweep{
0%{background-position:120% 0}
100%{background-position:-20% 0}
}
@media (prefers-reduced-motion:reduce){
[data-vibeui-block="skeleton-001"] *{animation:none!important;transition:none!important}
[data-vibeui-block="skeleton-001"] [data-part="bar"],
[data-vibeui-block="skeleton-001"] [data-part="circle"]{background:var(--vibeui-skeleton-001-base)}
}
`
/**
* Заглушка загрузки, повторяющая метрику будущего содержимого.
* Один файл, ноль зависимостей, собственная палитра.
*/
export function Skeleton001({
shape = "text",
lines = 3,
label = "Загружается",
className,
style,
...props
}: Skeleton001Props) {
const count = shape === "text" ? Math.max(1, lines) : 2
return (
<>
<style href="vibeui-skeleton-001" precedence="medium">
{STYLES}
</style>
<div
{...props}
data-vibeui-block="skeleton-001"
data-shape={shape}
role="status"
aria-busy="true"
aria-label={label}
className={className}
style={style as CSSProperties}
>
{shape === "avatar" ? (
<>
<div data-part="circle" />
<div data-part="stack">
<div data-part="bar" />
<div data-part="bar" />
</div>
</>
) : shape === "block" ? (
<div data-part="bar" />
) : (
Array.from({ length: count }, (_, index) => (
<div key={index} data-part="bar" />
))
)}
</div>
</>
)
}