Display
Content Skeleton
Заглушка загрузки, повторяющая метрику будущего содержимого: те же высоты строк и промежутки, поэтому при появлении данных страница не прыгает.
- skeleton
- loading
- placeholder
- shimmer
Превью
Использовать с AI
- 1. Скопируйте ссылку.
- 2. Напишите агенту своими словами и вставьте её в предложение.
- 3. Агент откроет ссылку и поставит компонент из registry.
размести это в шапке: https://vibeui.ru/c/skeleton-001
Показать полную инструкцию
То, что лежит по ссылке в развёрнутом виде. Нужна, если агент не может открыть ссылку — тогда вставьте этот текст целиком.
# Установка и размещение «skeleton-001» (Content Skeleton) из VibeUI
## 1. Сначала установка — не пропускай, не пересоздавай
Выполни эту команду до того, как писать код:
npx shadcn@latest add https://vibeui.ru/r/skeleton-001.json
Registry item: https://vibeui.ru/r/skeleton-001.json
Ставится в: components/vibeui/skeleton-001.tsx (точный путь следует алиасам components.json этого проекта).
npm-зависимости: нет.
Registry-зависимости: нет.
Ставь из реестра. Не пересоздавай по описанию, не подменяй похожим
компонентом из другой библиотеки и не переписывай под стиль проекта.
## 2. Что это
Заглушка загрузки, повторяющая метрику будущего содержимого: те же высоты строк и промежутки, поэтому при появлении данных страница не прыгает.
Заглушка загрузки трёх форм: абзац текста, строка со аватаром и блок под изображение. Высоты и промежутки повторяют метрику будущего содержимого, последняя строка короче — заглушка читается как текст. Блик идёт по градиенту и полностью гаснет при prefers-reduced-motion. Ноль зависимостей, один файл, своя палитра.
## 3. Как использовать
import { Skeleton001 } from "@/components/vibeui/skeleton-001"
<Skeleton001 shape="text" lines={3} label="Загружается статья" />
Полный список пропсов — в установленном файле.
## 4. Куда поставить
Это мелкий inline-компонент. Поставь его ровно туда, куда просил
пользователь, внутрь существующей разметки. Не создавай ради него
новую страницу, секцию или обёртку. Если на этом месте уже стоит похожий
контрол — замени его, а не добавляй второй.
Куда поставить: ___
(Эту строку заполняет пользователь. Если она осталась пустой — спроси,
а не угадывай.)
## 5. Сохранить как установлено
- локальную палитру --vibeui-skeleton-001-* и не подменять её токенами вашей темы (bg-muted, animate-pulse и подобными)
- совпадение метрики с будущим содержимым: высота строки и промежутки — это и есть смысл компонента, иначе страница прыгнет при загрузке
- укороченную последнюю строку: без неё абзац-заглушка выглядит как таблица
- блик через background-position у градиента 200% — отдельный движущийся слой заставляет браузер перерисовывать больше
- role="status", aria-busy и подпись: без них загрузка для скринридера просто не существует
- правило prefers-reduced-motion, которое гасит блик и оставляет ровную заливку
- блок <style> внутри компонента — в нём вся палитра, формы и анимация
## 6. Можно менять
- shape: text для абзаца, avatar для строки списка, block под изображение или карту
- lines — сколько строк рисует форма text
- label — что именно грузится; текст читает скринридер
- ширину и внешние отступы через className: по умолчанию заглушка занимает всю ширину родителя
## 7. Правила
- Не показывайте заглушку дольше пары секунд как единственный ответ: для долгих операций нужен прогресс или объяснение.
- Не мигайте заглушкой на быстрых запросах — вспышка на 100 мс раздражает сильнее пустоты.
- Число строк должно примерно совпадать с реальным содержимым: заглушка на восемь строк перед двухстрочным абзацем выглядит как ошибка.
- Не выносите CSS-переменные в globals.css: компонент обязан оставаться одним файлом.
## 8. Проверка
- рендерится без ошибок в консоли;
- выглядит так же, как превью на странице VibeUI, откуда взята ссылка;
- если нет — ты изменил что-то из раздела 5, верни как было.Для разработчика
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 и подписью, поэтому скринридер сообщает о загрузке, а не молчит.
Исходник компонента
Тот же файл, который поставит агент. Нужен, если вы предпочитаете скопировать код руками.
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>
</>
)
}