Card

Empty State Card

An empty state with exactly one way out: a three-sheet drawing, an explanation and a single button. The hint below is text, not a second button.

  • card
  • empty-state
  • onboarding
  • cta

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/card-019?lang=en

No projects yet

A project holds pages, blocks and a shared theme. Create the first one and the rest appears inside it.

Или перенесите существующий: импорт из папки занимает минуту.

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 "card-019" (Empty State Card) 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/card-019.json

Registry item: https://vibeui.ru/r/card-019.json
Installs to: components/vibeui/card-019.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
An empty state with exactly one way out: a three-sheet drawing, an explanation and a single button. The hint below is text, not a second button.

An empty state: a three-sheet drawing, a heading, an explanation, one button and a quiet hint beneath it. Zero dependencies, one file, no client JS.

## 3. How to use it
import { Card019 } from "@/components/vibeui/card-019"

<Card019
  title="No projects yet"
  description="Create the first one — everything else lives inside it."
  actionLabel="Create a project"
  hint="Or import an existing one."
/>

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-card-019-* palette — do not swap it for your theme tokens
- exactly one action: two equal buttons in an empty state turn the way out into a question
- the hint as text rather than a second button — it is an alternative, not an equal path
- the explanation of what appears after the click: "empty" with no reason reads as a failed load
- aria-hidden on the drawing: three blank rectangles mean nothing aloud
- the light surface and border: dark text on the dark showcase background is unreadable

## 6. You may change
- the heading through title and the explanation through description
- the button text through actionLabel
- the hint copy through hint — an empty string removes it
- the accent through the accent prop — the button and the top sheet of the drawing

## 7. Rules
- An empty state caused by permissions and one caused by no data need different copy: do not reuse one for both.
- If the data failed to load, this is not an empty state: that needs a reason and a retry.
- The drawing is three sheets and does not scale with the text: a very large type size outgrows it.
- 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/card-019.json
https://vibeui.ru/r/card-019.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-card-019-*. Серверный: хуков нет. Рисунок собран из трёх пустых «листов» на обычных span с поворотами transform — картинка не нужна, иконочный шрифт тоже. Весь блок помечен aria-hidden, потому что смысл несёт текст. Действие ровно одно, а альтернатива дана строкой текста: две равные кнопки в пустом состоянии превращают выход в вопрос.

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 Card019Props = Omit<
  ComponentPropsWithoutRef<"section">,
  "children" | "title"
> & {
  title?: string
  /** Почему тут пусто и что изменится после действия. Одно предложение. */
  description?: string
  /** Ровно одно действие: выбор из двух кнопок в пустоте некуда девать. */
  actionLabel?: string
  /** Тихая подсказка под кнопкой: справка, импорт, пример. Не вторая кнопка. */
  hint?: string
  accent?: string
}

// Идея компонента: пустое состояние с ровно одним выходом. Рисунок собран
// из трёх «листов» на псевдоэлементах: пустой экран без картинки читается
// как ошибка загрузки. Подсказка внизу — текст, а не вторая кнопка:
// две равные кнопки в пустоте превращают выбор в вопрос.
const STYLES = `
:where([data-vibeui-block="card-019"]){
--vibeui-card-019-bg:oklch(1 0 0);
--vibeui-card-019-fg:oklch(0.22 0.015 265);
--vibeui-card-019-muted:oklch(0.55 0.013 265);
--vibeui-card-019-border:oklch(0.91 0.006 265);
--vibeui-card-019-accent:oklch(0.55 0.18 262);
--vibeui-card-019-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="card-019"]{
display:flex;flex-direction:column;align-items:center;gap:0.5rem;
width:100%;max-width:26rem;box-sizing:border-box;padding:2rem 1.5rem 1.75rem;
background:var(--vibeui-card-019-bg);color:var(--vibeui-card-019-fg);
border:1px solid var(--vibeui-card-019-border);border-radius:1rem;
font-family:var(--vibeui-card-019-font);text-align:center;
}
/* Три листа на псевдоэлементах: рисунок без файла и без иконочного шрифта. */
[data-vibeui-block="card-019"] [data-part="art"]{
position:relative;flex:none;width:4.5rem;height:3.5rem;margin-bottom:0.5rem;
}
[data-vibeui-block="card-019"] [data-part="art"] span{
position:absolute;inset-inline-start:50%;inset-block-end:0;
width:2.5rem;height:3rem;border-radius:0.375rem;
border:1px solid var(--vibeui-card-019-border);
background:oklch(0.985 0.003 265);
}
[data-vibeui-block="card-019"] [data-part="art"] span:nth-child(1){
transform:translateX(-50%) translateX(-1.15rem) rotate(-11deg);
}
[data-vibeui-block="card-019"] [data-part="art"] span:nth-child(2){
transform:translateX(-50%) translateX(1.15rem) rotate(11deg);
}
[data-vibeui-block="card-019"] [data-part="art"] span:nth-child(3){
transform:translateX(-50%);
border-color:color-mix(in oklab,var(--vibeui-card-019-accent) 35%,var(--vibeui-card-019-border));
background:color-mix(in oklab,var(--vibeui-card-019-accent) 8%,oklch(1 0 0));
}
[data-vibeui-block="card-019"] [data-part="title"]{
margin:0;font-size:1.0625rem;font-weight:660;line-height:1.3;letter-spacing:-0.01em;
}
[data-vibeui-block="card-019"] [data-part="description"]{
margin:0;max-width:24rem;font-size:0.875rem;line-height:1.5;
color:var(--vibeui-card-019-muted);text-wrap:pretty;
}
[data-vibeui-block="card-019"] [data-part="action"]{
appearance:none;cursor:pointer;margin-top:0.625rem;
display:inline-flex;align-items:center;gap:0.4375rem;
height:2.375rem;padding:0 1.125rem;border-radius:0.625rem;border:0;
background:var(--vibeui-card-019-accent);color:oklch(0.99 0 0);
font:inherit;font-size:0.875rem;font-weight:650;
transition:background-color .16s ease;
}
[data-vibeui-block="card-019"] [data-part="action"]:hover{
background:color-mix(in oklab,var(--vibeui-card-019-accent) 86%,oklch(0.18 0.02 265));
}
[data-vibeui-block="card-019"] [data-part="action"]:focus-visible{
outline:2px solid var(--vibeui-card-019-accent);outline-offset:2px;
}
[data-vibeui-block="card-019"] [data-part="action"] svg{flex:none;width:0.875rem;height:0.875rem}
/* Подсказка — текст, а не вторая кнопка: у пустого состояния один выход. */
[data-vibeui-block="card-019"] [data-part="hint"]{
margin:0.375rem 0 0;font-size:0.75rem;line-height:1.45;color:var(--vibeui-card-019-muted);
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="card-019"] *{animation:none!important;transition:none!important}}
`

/**
 * Пустое состояние с одним действием: рисунок, объяснение, кнопка
 * и тихая подсказка. Один файл, ноль зависимостей.
 */
export function Card019({
  title = "Здесь пока нет проектов",
  description = "Проект хранит страницы, блоки и общую тему. Создайте первый — остальное появится внутри него.",
  actionLabel = "Создать проект",
  hint = "Или перенесите существующий: импорт из папки занимает минуту.",
  accent,
  className,
  style,
  ...props
}: Card019Props) {
  const palette = {
    ...(accent ? { "--vibeui-card-019-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-card-019" precedence="medium">
        {STYLES}
      </style>
      <section
        {...props}
        data-vibeui-block="card-019"
        className={className}
        style={palette}
      >
        <div data-part="art" aria-hidden="true">
          <span />
          <span />
          <span />
        </div>
        <h3 data-part="title">{title}</h3>
        {description ? <p data-part="description">{description}</p> : null}
        <button data-part="action" type="button">
          <svg viewBox="0 0 14 14" fill="none" aria-hidden="true">
            <path
              d="M7 2.5v9M2.5 7h9"
              stroke="currentColor"
              strokeWidth="1.8"
              strokeLinecap="round"
            />
          </svg>
          {actionLabel}
        </button>
        {hint ? <p data-part="hint">{hint}</p> : null}
      </section>
    </>
  )
}