Alert

Form Errors Alert

A form error summary above the submit button: the count in the heading and rows linking to the fields. In a long form, hunting for the field costs more than a click.

  • alert
  • form
  • errors
  • validation

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-010?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 "alert-010" (Form Errors 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-010.json

Registry item: https://vibeui.ru/r/alert-010.json
Installs to: components/vibeui/alert-010.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 form error summary above the submit button: the count in the heading and rows linking to the fields. In a long form, hunting for the field costs more than a click.

A form error summary: the count in the heading, then a list of fields linking to them. The field label is bold and the error text follows — first where, then what. Zero dependencies, one file, its own palette, no client JS.

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

<Alert010
  issues={[{ field: "Project address", message: "is taken", href: "#slug" }]}
/>

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-010-* palette — do not swap it for your theme tokens (bg-destructive, text-destructive and the like)
- the count in the heading: "fix the errors" without a number says nothing about how much work is left
- the rows as links to the fields: in a long form scrolling by eye costs more than a click
- the order "field — error" rather than the reverse: the place first, the reason second
- role="alert": the summary appears in response to an action and must be announced at once
- the <style> block inside the component — it holds the palette and the layout

## 6. You may change
- the issues array: field label, error text and anchor
- title — when you need your own wording instead of the generated one
- width and outer spacing through className: the block usually sits above the submit button

## 7. Rules
- The summary does not replace per-field messages: it leads to them, while alert-003 explains the error in place.
- The anchors have to exist. A link to #email with no field of that id goes nowhere.
- Do not list more than five or six items: a long error list means the form should be split into steps.
- 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-010.json
https://vibeui.ru/r/alert-010.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-alert-010-*. Заголовок собирается сам из числа ошибок, если не передан явно. Каждая строка — подпись поля жирным и текст ошибки обычным: сначала «где», потом «что». При переданном href строка становится ссылкой на якорь поля. Роль alert выставлена всегда: сводка появляется в ответ на отправку и должна быть объявлена сразу.

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 Alert010Issue = {
  /** Подпись поля, к которому относится ошибка. */
  field: string
  message: string
  /** Якорь поля: строка становится ссылкой и уводит к нему. */
  href?: string
}

export type Alert010Props = Omit<
  ComponentPropsWithoutRef<"div">,
  "title" | "children"
> & {
  title?: string
  issues?: Alert010Issue[]
}

// Идея компонента: сводка ошибок формы над кнопкой отправки. Каждая строка —
// ссылка на поле: в длинной форме искать глазами то самое поле дороже, чем
// щёлкнуть. Заголовок называет число, потому что «исправьте ошибки» без
// количества не говорит, сколько работы впереди.
const STYLES = `
:where([data-vibeui-block="alert-010"]){
--vibeui-alert-010-fg:oklch(0.24 0.016 265);
--vibeui-alert-010-muted:oklch(0.5 0.014 265);
--vibeui-alert-010-bg:oklch(1 0 0);
--vibeui-alert-010-danger:oklch(0.56 0.19 25);
--vibeui-alert-010-radius:0.75rem;
--vibeui-alert-010-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="alert-010"]{
display:flex;align-items:flex-start;gap:0.75rem;
width:100%;box-sizing:border-box;
padding:0.9375rem 1.0625rem;
border:1px solid color-mix(in oklab,var(--vibeui-alert-010-danger) 35%,transparent);
border-radius:var(--vibeui-alert-010-radius);
background:color-mix(in oklab,var(--vibeui-alert-010-danger) 5%,var(--vibeui-alert-010-bg));
color:var(--vibeui-alert-010-fg);font-family:var(--vibeui-alert-010-font);
}
[data-vibeui-block="alert-010"] [data-part="icon"]{
display:flex;align-items:center;justify-content:center;flex:none;
width:1.375rem;height:1.375rem;margin-top:0.0625rem;border-radius:9999px;
background:var(--vibeui-alert-010-danger);color:oklch(0.99 0.01 25);
font-size:0.75rem;font-weight:800;line-height:1;
}
[data-vibeui-block="alert-010"] [data-part="text"]{display:flex;flex-direction:column;gap:0.4375rem;flex:1 1 auto;min-width:0}
[data-vibeui-block="alert-010"] [data-part="title"]{font-size:0.875rem;font-weight:650;line-height:1.4}
[data-vibeui-block="alert-010"] ul{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:0.25rem}
[data-vibeui-block="alert-010"] li{
display:flex;gap:0.375rem;
font-size:0.8125rem;line-height:1.5;color:var(--vibeui-alert-010-muted);
}
/* Подпись поля жирнее текста ошибки: сначала «где», потом «что». */
[data-vibeui-block="alert-010"] [data-part="field"]{
flex:none;color:var(--vibeui-alert-010-fg);font-weight:600;
}
[data-vibeui-block="alert-010"] a{color:inherit;text-decoration:none}
[data-vibeui-block="alert-010"] a:hover [data-part="field"]{text-decoration:underline}
[data-vibeui-block="alert-010"] a:focus-visible{outline:2px solid var(--vibeui-alert-010-danger);outline-offset:2px;border-radius:0.25rem}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="alert-010"] *{animation:none!important;transition:none!important}}
`

const DEFAULT_ISSUES: Alert010Issue[] = [
  {
    field: "Адрес проекта",
    message: "уже занят — попробуйте другой",
    href: "#slug",
  },
  {
    field: "Почта администратора",
    message: "не похожа на адрес: пропущен домен",
    href: "#email",
  },
  {
    field: "Тариф",
    message: "не выбран",
    href: "#plan",
  },
]

/**
 * Сводка ошибок формы: число в заголовке, строки-ссылки на поля.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Alert010({
  title,
  issues = DEFAULT_ISSUES,
  className,
  style,
  ...props
}: Alert010Props) {
  const heading = title ?? `Не удалось сохранить: ошибок — ${issues.length}`

  return (
    <>
      <style href="vibeui-alert-010" precedence="medium">
        {STYLES}
      </style>
      <div
        {...props}
        data-vibeui-block="alert-010"
        role="alert"
        className={className}
        style={style as CSSProperties}
      >
        <span data-part="icon" aria-hidden="true">
          !
        </span>
        <div data-part="text">
          <span data-part="title">{heading}</span>
          <ul>
            {issues.map((issue) => (
              <li key={issue.field}>
                {issue.href ? (
                  <a href={issue.href}>
                    <span data-part="field">{issue.field}</span> {issue.message}
                  </a>
                ) : (
                  <>
                    <span data-part="field">{issue.field}</span>
                    {issue.message}
                  </>
                )}
              </li>
            ))}
          </ul>
        </div>
      </div>
    </>
  )
}