Display

Masked Paragraph Skeleton

Paragraph skeletons drawn as a single element: the lines are cut out by a repeating mask, so their count is one variable and the leading matches the future text by construction.

  • skeleton
  • text
  • mask
  • 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-006?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-006" (Masked Paragraph 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-006.json

Registry item: https://vibeui.ru/r/skeleton-006.json
Installs to: components/vibeui/skeleton-006.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
Paragraph skeletons drawn as a single element: the lines are cut out by a repeating mask, so their count is one variable and the leading matches the future text by construction.

A paragraph skeleton: lines are cut by a mask inside one element, and the last line's tail is covered by a surface-coloured patch. Zero dependencies, one file, server rendered.

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

<Skeleton006 paragraphs={[3, 4, 2]} label="Text 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-006-* palette — do not swap it for your theme tokens
- the repeating-linear-gradient mask as the way to draw lines: it keeps the leading in one value
- the tail patch as a sibling rather than a child — inside the mask it would be cut away too
- the fixed tail widths: random ones would mismatch between server and client
- role="status" and aria-busy on the root, or the skeleton is silent to screen readers
- its own light surface: both the tail patch and the bar contrast are measured against it

## 6. You may change
- the paragraph count and lines per paragraph through paragraphs
- the screen-reader caption through label
- the line height and leading step through the variables
- the set of tail widths in the TAILS array

## 7. Rules
- The mask step must match the real text's line-height, otherwise the paragraph jumps when data lands.
- The tail patch is painted in the surface colour: on a transparent background it becomes a visible rectangle.
- mask still needs a prefix in very old WebKit builds — there the lines collapse into a solid block.
- 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-006.json
https://vibeui.ru/r/skeleton-006.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-skeleton-006-*. Серверный: хуков нет. Абзац — один элемент, у которого фон-блик вырезан repeating-linear-gradient в свойстве mask: число строк задаёт переменная, а не количество узлов, и высота считается формулой строки и шага. Недописанная последняя строка нарисована накладкой цвета подложки, которая лежит соседом, а не ребёнком: внутри маскированного элемента её вырезало бы вместе со строками. Длины хвостов берутся из фиксированного массива, а не случайно, — случайные разъехались бы между сервером и клиентом при гидратации. Корень помечен 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 Skeleton006Props = ComponentPropsWithoutRef<"div"> & {
  /** Число строк в каждом абзаце: длина списка задаёт число абзацев. */
  paragraphs?: number[]
  label?: string
}

// Идея компонента: абзац-заглушка нарисован одним элементом, а не пачкой
// полос. Строки вырезаны повторяющейся маской, поэтому их число меняется
// одной переменной, а интерлиньяж по определению совпадает с будущим текстом.
// Недописанная последняя строка — накладка цвета подложки поверх маски:
// внутрь маски её положить нельзя, она сама была бы вырезана.
const STYLES = `
:where([data-vibeui-block="skeleton-006"]){
--vibeui-skeleton-006-bg:oklch(1 0 0);
--vibeui-skeleton-006-border:oklch(0.9 0.006 265);
--vibeui-skeleton-006-base:oklch(0.93 0.005 265);
--vibeui-skeleton-006-shine:oklch(0.97 0.003 265);
--vibeui-skeleton-006-line:0.6875rem;
--vibeui-skeleton-006-step:1.375rem;
--vibeui-skeleton-006-lines:3;
--vibeui-skeleton-006-tail:32%;
}
[data-vibeui-block="skeleton-006"]{
display:flex;flex-direction:column;gap:1.125rem;
width:100%;max-width:26rem;box-sizing:border-box;padding:1.125rem 1.25rem;
background:var(--vibeui-skeleton-006-bg);
border:1px solid var(--vibeui-skeleton-006-border);border-radius:1rem;
}
[data-vibeui-block="skeleton-006"] [data-part="para"]{position:relative}
/* Строки вырезаны маской: их число — одна переменная, а не количество узлов. */
[data-vibeui-block="skeleton-006"] [data-part="lines"]{
display:block;
height:calc(var(--vibeui-skeleton-006-lines) * var(--vibeui-skeleton-006-step) - (var(--vibeui-skeleton-006-step) - var(--vibeui-skeleton-006-line)));
background:linear-gradient(90deg,
var(--vibeui-skeleton-006-base) 0%,
var(--vibeui-skeleton-006-shine) 50%,
var(--vibeui-skeleton-006-base) 100%) 0 0 / 200% 100%;
mask:repeating-linear-gradient(
to bottom,
#000 0 var(--vibeui-skeleton-006-line),
transparent var(--vibeui-skeleton-006-line) var(--vibeui-skeleton-006-step));
animation:vibeui-skeleton-006-sweep 1.6s ease-in-out infinite;
}
/* Хвост последней строки: накладка подложки поверх — маска съела бы ребёнка. */
[data-vibeui-block="skeleton-006"] [data-part="cut"]{
position:absolute;right:0;bottom:0;
width:var(--vibeui-skeleton-006-tail);height:var(--vibeui-skeleton-006-line);
background:var(--vibeui-skeleton-006-bg);
}
@keyframes vibeui-skeleton-006-sweep{
0%{background-position:120% 0}
100%{background-position:-20% 0}
}
@media (prefers-reduced-motion:reduce){
[data-vibeui-block="skeleton-006"] *{animation:none!important;transition:none!important}
[data-vibeui-block="skeleton-006"] [data-part="lines"]{background:var(--vibeui-skeleton-006-base)}
}
`

const TAILS = ["34%", "18%", "47%", "26%", "39%"]

/**
 * Заглушка абзацев: строки вырезаны повторяющейся маской.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Skeleton006({
  paragraphs = [3, 4, 2],
  label = "Текст загружается",
  className,
  style,
  ...props
}: Skeleton006Props) {
  return (
    <>
      <style href="vibeui-skeleton-006" precedence="medium">
        {STYLES}
      </style>
      <div
        {...props}
        data-vibeui-block="skeleton-006"
        role="status"
        aria-busy="true"
        aria-label={label}
        className={className}
        style={style as CSSProperties}
      >
        {paragraphs.map((lines, index) => (
          <div
            key={index}
            data-part="para"
            style={
              {
                "--vibeui-skeleton-006-lines": Math.max(1, lines),
                "--vibeui-skeleton-006-tail": TAILS[index % TAILS.length],
              } as CSSProperties
            }
          >
            <span data-part="lines" />
            <span data-part="cut" />
          </div>
        ))}
      </div>
    </>
  )
}