Card

Checklist Card

A task card with the checklist in plain sight: progress is a row of segments, one per item, so the remainder reads without the caption.

  • card
  • checklist
  • progress
  • task

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-015?lang=en

Catalog card wave

3 из 5
  • Собрать список компонентов — выполнено
  • Написать metadata и перевод — выполнено
  • Проверить превью на мобильной ширине — выполнено
  • Прогнать meta:validate и lint — не выполнено
  • Выложить на витрину — не выполнено

Срок: до пятницы · Марк Ильин

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-015" (Checklist 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-015.json

Registry item: https://vibeui.ru/r/card-015.json
Installs to: components/vibeui/card-015.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 task card with the checklist in plain sight: progress is a row of segments, one per item, so the remainder reads without the caption.

A task card with a checklist: a title, a counter, segmented progress with one segment per item, and the items themselves with ticks. Zero dependencies, one file, no client JS.

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

<Card015
  title="Catalog card wave"
  items={[{ text: "Write the metadata", done: true }, { text: "Run lint" }]}
  meta="Due Friday"
/>

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-015-* palette — do not swap it for your theme tokens
- segments instead of a solid bar: one segment is one item, and the remainder shows without reading the counter
- role="progressbar" with aria-valuemax from the list length — a row of coloured strips does not exist for a screen reader otherwise
- the visually hidden "done" and "not done": strike-through and a tick are silent
- the "3 of 5" counter beside the title: nobody reads a number off a fill length
- the order of the items: a checklist is a plan, not a set, and reordering changes the meaning

## 6. You may change
- the heading through title and the meta caption
- the items array — the item text and its done flag
- the showDone mode: hide what is finished and keep only what is ahead
- the accent through the accent prop — the segment fill and the ticks

## 7. Rules
- The counter and the segments count the whole items array, not the visible part: with showDone={false} the progress stays honest.
- More than eight items turn the card into a page — link to the task screen instead.
- The items do not toggle: the checklist here shows state, and click handling belongs to the caller.
- 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-015.json
https://vibeui.ru/r/card-015.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-card-015-*. Серверный: хуков нет. Прогресс — не полоса, а ряд равных сегментов по числу пунктов, объявленный role="progressbar" с aria-valuemax по длине списка. Пункты — обычный ul; состояние живёт в data-done, поэтому галочка, цвет рамки и зачёркивание переключаются одним атрибутом. Слова «выполнено»/«не выполнено» уезжают в визуально скрытый текст: зачёркивание вслух не читается.

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 Card015Item = {
  text: string
  done?: boolean
}

export type Card015Props = Omit<
  ComponentPropsWithoutRef<"article">,
  "children" | "title"
> & {
  title?: string
  /** Пункты чек-листа. Порядок сохраняется: это план, а не множество. */
  items?: Card015Item[]
  /** false прячет выполненные пункты и оставляет только то, что впереди. */
  showDone?: boolean
  /** Подпись под чек-листом: срок, исполнитель, ветка. */
  meta?: string
  accent?: string
}

// Идея компонента: карточка задачи, где виден сам чек-лист, а не только
// полоса прогресса. Прогресс собран из сегментов — по одному на пункт,
// поэтому «7 из 12» читается и глазами. Галочка нарисована двумя линиями
// SVG: иконочный шрифт ради одного знака ставить незачем.
const STYLES = `
:where([data-vibeui-block="card-015"]){
--vibeui-card-015-bg:oklch(1 0 0);
--vibeui-card-015-fg:oklch(0.22 0.015 265);
--vibeui-card-015-muted:oklch(0.56 0.013 265);
--vibeui-card-015-border:oklch(0.91 0.006 265);
--vibeui-card-015-track:oklch(0.93 0.005 265);
--vibeui-card-015-accent:oklch(0.56 0.15 152);
--vibeui-card-015-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="card-015"]{
display:flex;flex-direction:column;gap:0.6875rem;
width:100%;max-width:20rem;box-sizing:border-box;padding:0.9375rem 1rem 1rem;
background:var(--vibeui-card-015-bg);color:var(--vibeui-card-015-fg);
border:1px solid var(--vibeui-card-015-border);border-radius:0.875rem;
font-family:var(--vibeui-card-015-font);
}
[data-vibeui-block="card-015"] [data-part="head"]{
display:flex;align-items:baseline;justify-content:space-between;gap:0.625rem;
}
[data-vibeui-block="card-015"] [data-part="title"]{
margin:0;font-size:0.9375rem;font-weight:650;line-height:1.3;
}
[data-vibeui-block="card-015"] [data-part="count"]{
flex:none;font-size:0.75rem;font-weight:650;font-variant-numeric:tabular-nums;
color:var(--vibeui-card-015-muted);
}
/* Прогресс сегментами, а не сплошной полосой: сегмент = пункт, поэтому
   «сколько осталось» видно без чтения подписи. */
[data-vibeui-block="card-015"] [data-part="meter"]{
display:flex;gap:0.1875rem;
}
[data-vibeui-block="card-015"] [data-part="seg"]{
flex:1;height:0.3125rem;border-radius:9999px;background:var(--vibeui-card-015-track);
}
[data-vibeui-block="card-015"] [data-part="seg"][data-filled="true"]{background:var(--vibeui-card-015-accent)}
[data-vibeui-block="card-015"] [data-part="list"]{
display:flex;flex-direction:column;gap:0.4375rem;margin:0;padding:0;list-style:none;
}
[data-vibeui-block="card-015"] [data-part="row"]{
position:relative;display:flex;align-items:flex-start;gap:0.5rem;
font-size:0.8125rem;line-height:1.4;
}
[data-vibeui-block="card-015"] [data-part="sr"]{
position:absolute;width:1px;height:1px;margin:-1px;padding:0;
overflow:hidden;clip-path:inset(50%);white-space:nowrap;
}
[data-vibeui-block="card-015"] [data-part="box"]{
display:flex;align-items:center;justify-content:center;flex:none;
width:1rem;height:1rem;margin-top:0.0625rem;border-radius:0.3125rem;
border:1.5px solid var(--vibeui-card-015-border);background:oklch(1 0 0);
color:oklch(1 0 0);
}
[data-vibeui-block="card-015"] [data-part="row"][data-done="true"] [data-part="box"]{
border-color:var(--vibeui-card-015-accent);background:var(--vibeui-card-015-accent);
}
[data-vibeui-block="card-015"] [data-part="tick"]{width:0.625rem;height:0.625rem}
[data-vibeui-block="card-015"] [data-part="row"][data-done="false"] [data-part="tick"]{visibility:hidden}
/* Выполненное приглушено, но не вычеркнуто в ноль: список остаётся историей. */
[data-vibeui-block="card-015"] [data-part="row"][data-done="true"] [data-part="text"]{
color:var(--vibeui-card-015-muted);text-decoration:line-through;
text-decoration-color:color-mix(in oklab,var(--vibeui-card-015-muted) 45%,transparent);
}
[data-vibeui-block="card-015"] [data-part="meta"]{
margin:0;padding-top:0.5rem;border-top:1px solid var(--vibeui-card-015-border);
font-size:0.75rem;color:var(--vibeui-card-015-muted);
}
`

const DEFAULT_ITEMS: Card015Item[] = [
  { text: "Собрать список компонентов", done: true },
  { text: "Написать metadata и перевод", done: true },
  { text: "Проверить превью на мобильной ширине", done: true },
  { text: "Прогнать meta:validate и lint" },
  { text: "Выложить на витрину" },
]

/**
 * Карточка задачи с видимым чек-листом и сегментным прогрессом.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Card015({
  title = "Волна карточек в каталог",
  items = DEFAULT_ITEMS,
  showDone = true,
  meta = "Срок: до пятницы · Марк Ильин",
  accent,
  className,
  style,
  ...props
}: Card015Props) {
  const done = items.filter((item) => item.done).length
  const visible = showDone ? items : items.filter((item) => !item.done)

  const palette = {
    ...(accent ? { "--vibeui-card-015-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-card-015" precedence="medium">
        {STYLES}
      </style>
      <article
        {...props}
        data-vibeui-block="card-015"
        className={className}
        style={palette}
      >
        <div data-part="head">
          <h3 data-part="title">{title}</h3>
          <span data-part="count">
            {done} из {items.length}
          </span>
        </div>
        <div
          data-part="meter"
          role="progressbar"
          aria-valuenow={done}
          aria-valuemin={0}
          aria-valuemax={items.length}
          aria-label={`Выполнено ${done} из ${items.length} пунктов`}
        >
          {items.map((item, index) => (
            <span
              key={item.text}
              data-part="seg"
              data-filled={index < done}
              aria-hidden="true"
            />
          ))}
        </div>
        <ul data-part="list">
          {visible.map((item) => (
            <li key={item.text} data-part="row" data-done={Boolean(item.done)}>
              <span data-part="box" aria-hidden="true">
                <svg data-part="tick" viewBox="0 0 12 12" fill="none">
                  <path
                    d="M2.5 6.3 4.8 8.6 9.5 3.6"
                    stroke="currentColor"
                    strokeWidth="1.8"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  />
                </svg>
              </span>
              <span data-part="text">
                {item.text}
                <span data-part="sr">
                  {item.done ? " — выполнено" : " — не выполнено"}
                </span>
              </span>
            </li>
          ))}
        </ul>
        {meta ? <p data-part="meta">{meta}</p> : null}
      </article>
    </>
  )
}