Alert

Confirm Word Alert

Confirmation for an irreversible action: a list of what disappears and a field for typing the name back. The pause that separates a stray click from a decision.

  • alert
  • confirm
  • destructive
  • danger

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/alert-020?lang=en

Delete the project “Studio site”?

Действие необратимо. Восстановить проект из резервной копии мы не сможем.

  • 18 страниц и вся история публикаций
  • домен studio-polet.ru и его сертификат
  • доступы четырёх участников
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 "alert-020" (Confirm Word Alert) 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/alert-020.json

Registry item: https://vibeui.ru/r/alert-020.json
Installs to: components/vibeui/alert-020.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
Confirmation for an irreversible action: a list of what disappears and a field for typing the name back. The pause that separates a stray click from a decision.

An irreversible-action confirmation: a heading in the danger tone, a list of what will be lost, a field for typing the object's name and two buttons. While the field is empty the confirm button is disabled by a CSS rule — no JS involved. Zero dependencies, one file, its own palette.

## 3. How to use it
import { Alert020 } from "@/components/vibeui/alert-020"

<Alert020
  title="Delete the project “Studio site”?"
  confirmWord="Studio site"
  losses={["18 pages", "the domain and its certificate"]}
  onCancel={close}
/>

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-alert-020-* palette — do not swap it for your theme tokens (bg-destructive and the like)
- typing the name as the pause before something irreversible: that is what separates a stray click from a decision
- the list of losses above the field: it has to be read before the typing starts
- itemised losses rather than "all data": specifics are the only thing that makes people stop
- the :has(input:placeholder-shown) rule that disables confirm without JS, and the space placeholder it rests on
- role="alertdialog" and the aria-label carrying the heading
- the <style> block inside the component — it holds the palette and the disabling logic

## 6. You may change
- the title, description, inputLabel and button copy
- confirmWord — what has to be typed; usually the object's name
- losses — what will disappear
- onCancel — dismissal; handle confirmation on the form's submit
- width and outer spacing through className

## 7. Rules
- Verify the exact match on the server: pattern and CSS are convenience, not protection.
- The component does not wrap itself in a <form>: the wrapper and the submit handler come from the caller.
- Do not ask people to type "DELETE": the object's name forces a look at what is being deleted, while a fixed word is typed without reading.
- 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/alert-020.json
https://vibeui.ru/r/alert-020.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-alert-020-*. Пока поле пустое, подтверждение недоступно — это делает правило `:has(input:placeholder-shown)`, то есть без единой строки JS. Точное совпадение проверяет атрибут pattern (значение экранируется) и, окончательно, вызывающий код на отправке формы: CSS сравнивать строки не умеет. Список потерь стоит выше поля намеренно — его должны прочитать до того, как начнут печатать.

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 Alert020Props = Omit<
  ComponentPropsWithoutRef<"div">,
  "title" | "children"
> & {
  title?: string
  description?: string
  /** Что будет удалено: перечень, а не одна строка «все данные». */
  losses?: string[]
  /** Слово, которое нужно ввести для подтверждения. */
  confirmWord?: string
  inputLabel?: string
  confirmLabel?: string
  cancelLabel?: string
  onCancel?: () => void
}

// Идея компонента: подтверждение необратимого действия. Кнопка «Удалить»
// выключена, пока не введено точное имя объекта — та самая пауза, которая
// отделяет случайный клик от решения. Перечисление того, что исчезнет,
// стоит выше поля: человек должен прочитать список до того, как начнёт печатать.
const STYLES = `
:where([data-vibeui-block="alert-020"]){
--vibeui-alert-020-fg:oklch(0.22 0.014 265);
--vibeui-alert-020-muted:oklch(0.5 0.014 265);
--vibeui-alert-020-bg:oklch(1 0 0);
--vibeui-alert-020-border:oklch(0.9 0.006 265);
--vibeui-alert-020-danger:oklch(0.56 0.19 25);
--vibeui-alert-020-radius:0.875rem;
--vibeui-alert-020-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;
--vibeui-alert-020-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
container-type:inline-size;
}
[data-vibeui-block="alert-020"]{
display:flex;flex-direction:column;gap:0.75rem;
width:100%;max-width:32rem;box-sizing:border-box;
padding:1.0625rem 1.125rem;
border:1px solid color-mix(in oklab,var(--vibeui-alert-020-danger) 35%,var(--vibeui-alert-020-border));
border-radius:var(--vibeui-alert-020-radius);
background:color-mix(in oklab,var(--vibeui-alert-020-danger) 4%,var(--vibeui-alert-020-bg));
color:var(--vibeui-alert-020-fg);font-family:var(--vibeui-alert-020-font);
}
[data-vibeui-block="alert-020"] [data-part="title"]{font-size:0.9375rem;font-weight:650;line-height:1.35;color:var(--vibeui-alert-020-danger)}
[data-vibeui-block="alert-020"] [data-part="description"]{margin:0;font-size:0.8125rem;line-height:1.55;color:var(--vibeui-alert-020-muted)}
[data-vibeui-block="alert-020"] ul{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:0.1875rem}
[data-vibeui-block="alert-020"] li{
position:relative;padding-left:0.9375rem;
font-size:0.8125rem;line-height:1.5;color:var(--vibeui-alert-020-muted);
}
[data-vibeui-block="alert-020"] li::before{
content:"";position:absolute;left:0;top:0.5rem;
width:0.3125rem;height:0.3125rem;border-radius:9999px;
background:color-mix(in oklab,var(--vibeui-alert-020-danger) 55%,transparent);
}
[data-vibeui-block="alert-020"] label{display:flex;flex-direction:column;gap:0.3125rem;font-size:0.8125rem}
[data-vibeui-block="alert-020"] [data-part="word"]{
font-family:var(--vibeui-alert-020-mono);font-weight:650;color:var(--vibeui-alert-020-fg);
}
[data-vibeui-block="alert-020"] input{
width:100%;box-sizing:border-box;margin:0;height:2.25rem;padding:0 0.75rem;
border:1px solid var(--vibeui-alert-020-border);border-radius:0.5rem;
background:oklch(1 0 0);color:inherit;
font-family:var(--vibeui-alert-020-mono);font-size:0.875rem;
transition:border-color .16s ease,box-shadow .16s ease;
}
[data-vibeui-block="alert-020"] input:focus{
outline:none;border-color:var(--vibeui-alert-020-danger);
box-shadow:0 0 0 3px color-mix(in oklab,var(--vibeui-alert-020-danger) 20%,transparent);
}
[data-vibeui-block="alert-020"] [data-part="actions"]{display:flex;gap:0.5rem;flex-wrap:wrap}
[data-vibeui-block="alert-020"] button{
appearance:none;cursor:pointer;font:inherit;
display:inline-flex;align-items:center;height:2.125rem;padding:0 1rem;
border-radius:0.5rem;border:1px solid transparent;
font-size:0.8125rem;font-weight:600;
transition:background-color .16s ease,border-color .16s ease;
}
[data-vibeui-block="alert-020"] [data-part="confirm"]{
background:var(--vibeui-alert-020-danger);color:oklch(0.99 0.01 25);
}
/* Кнопка выключена, пока слово не совпало: проверку делает :placeholder-shown
   у поля — сравнение значения без JS невозможно, поэтому форма требует
   заполнения, а точное совпадение проверяет вызывающий код на отправке. */
[data-vibeui-block="alert-020"] [data-part="confirm"]:disabled{
opacity:.5;cursor:not-allowed;
}
[data-vibeui-block="alert-020"] [data-part="cancel"]{
background:transparent;color:var(--vibeui-alert-020-fg);
border-color:var(--vibeui-alert-020-border);
}
[data-vibeui-block="alert-020"] [data-part="cancel"]:hover{background:color-mix(in oklab,var(--vibeui-alert-020-border) 40%,transparent)}
[data-vibeui-block="alert-020"] :focus-visible{outline:2px solid var(--vibeui-alert-020-danger);outline-offset:2px}
/* Пока поле пустое, подтверждение недоступно — без единой строки JS. */
[data-vibeui-block="alert-020"]:has(input:placeholder-shown) [data-part="confirm"]{
opacity:.5;pointer-events:none;
}
@container (max-width: 22rem){
[data-vibeui-block="alert-020"] [data-part="actions"] button{flex:1 1 100%;justify-content:center}
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="alert-020"] *{animation:none!important;transition:none!important}}
`

const DEFAULT_LOSSES = [
  "18 страниц и вся история публикаций",
  "домен studio-polet.ru и его сертификат",
  "доступы четырёх участников",
]

/**
 * Подтверждение необратимого действия: список потерь и ввод имени.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Alert020({
  title = "Удалить проект «Сайт студии»?",
  description = "Действие необратимо. Восстановить проект из резервной копии мы не сможем.",
  losses = DEFAULT_LOSSES,
  confirmWord = "Сайт студии",
  inputLabel = "Введите название проекта, чтобы подтвердить:",
  confirmLabel = "Удалить навсегда",
  cancelLabel = "Отмена",
  onCancel,
  className,
  style,
  ...props
}: Alert020Props) {
  return (
    <>
      <style href="vibeui-alert-020" precedence="medium">
        {STYLES}
      </style>
      <div
        {...props}
        data-vibeui-block="alert-020"
        role="alertdialog"
        aria-label={title}
        className={className}
        style={style as CSSProperties}
      >
        <span data-part="title">{title}</span>
        {description ? <p data-part="description">{description}</p> : null}
        {losses.length ? (
          <ul>
            {losses.map((loss) => (
              <li key={loss}>{loss}</li>
            ))}
          </ul>
        ) : null}
        <label>
          <span>
            {inputLabel} <span data-part="word">{confirmWord}</span>
          </span>
          <input
            type="text"
            name="confirm"
            placeholder=" "
            autoComplete="off"
            required
            pattern={confirmWord.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}
          />
        </label>
        <span data-part="actions">
          <button data-part="confirm" type="submit">
            {confirmLabel}
          </button>
          <button data-part="cancel" type="button" onClick={onCancel}>
            {cancelLabel}
          </button>
        </span>
      </div>
    </>
  )
}