Buttons

Action Button

The primary action button: three sizes, three tones and a loading state. Self-contained — one file, no dependencies, indifferent to your project's theme.

  • button
  • action
  • inline
  • 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.
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 "button-001" (Action Button) from VibeUI

## 1. Install first — do not skip, do not recreate
Install command is unavailable: the VibeUI registry URL is not configured.

Installs to: components/vibeui/button-001.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
The primary action button: three sizes, three tones and a loading state. Self-contained — one file, no dependencies, indifferent to your project's theme.

A compact action button. Three sizes (sm/md/lg), three tones (solid fills with the accent, soft sits on a translucent wash, outline is border only) and a loading state whose spinner is pure CSS. Ships with focus-visible and disabled handling. Zero dependencies, one file, its own palette.

## 3. How to use it
import { Button001 } from "@/components/vibeui/button-001"

<Button001 tone="solid" size="md" onClick={handleSubmit}>
  Get started
</Button001>

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-button-001-* palette — do not swap it for your theme tokens (bg-primary, text-primary-foreground, bg-background and the like)
- the heights, horizontal padding and font sizes of sm/md/lg — those proportions are the button
- the --vibeui-button-001-radius corner radius: it is shared across all three sizes
- the three tones as distinct values of the tone prop — do not collapse them into one variant or invent a fourth
- the focus-visible outline and its offset: without them the button is unusable from the keyboard
- the loading behaviour: the button goes disabled and gains aria-busy, and the label is not replaced by the spinner
- the CSS-animated spinner and its prefers-reduced-motion rule
- the <style> block inside the component — it holds the whole palette, the sizes and the keyframes

## 6. You may change
- the label and any children, including an icon next to the text
- the accent colour through the accent prop (and accentForeground for label contrast)
- the size and tone values to suit the spot in the interface
- any native button props: onClick, type, form, name, disabled, aria-*
- outer spacing and width through className
- the loading state — the calling code owns it, the button waits for nothing on its own

## 7. Rules
- This is an inline component, not a section. Do not wrap it in an extra <div> for spacing — use className.
- Do not pull in an icon library for the spinner: it is already there in CSS. An icon beside the text can be passed as children.
- Do not lift the CSS variables into globals.css: the component has to stay a single file.
- Do not swap <button> for <a>. For navigation use a plain link or your router's component, not this button.

## 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

The install command is unavailable: the environment variableREGISTRY_BASE_URL

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-button-001-*. Акцент меняется пропом accent, размеры и тона — data-атрибутами, объявленными внутри компонента. Принимает любые пропсы нативного <button>. Спиннер на чистом 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 Button001Size = "sm" | "md" | "lg"
export type Button001Tone = "solid" | "soft" | "outline"

export type Button001Props = ComponentPropsWithoutRef<"button"> & {
  size?: Button001Size
  tone?: Button001Tone
  loading?: boolean
  accent?: string
  accentForeground?: string
}

// Компонент везде выглядит одинаково, поэтому палитра, размеры и keyframes
// живут здесь, а не в globals.css проекта. Объявление переменных обёрнуто
// в :where() — нулевая специфичность, так что inline style пропа accent
// и любой класс пользователя переопределяют значение.
//
// Правила размеров и тонов заданы селекторами по data-атрибутам, а не
// Tailwind-классами: компонент не должен зависеть от версии Tailwind
// в чужом проекте. Базовое правило имеет специфичность (0,1,0), поэтому
// утилиты вроде mt-4 или w-full из className продолжают работать.
const STYLES = `
:where([data-vibeui-block="button-001"]){
--vibeui-button-001-accent:oklch(0.62 0.187 264);
--vibeui-button-001-accent-fg:oklch(0.99 0.004 264);
--vibeui-button-001-soft-bg:color-mix(in oklab, var(--vibeui-button-001-accent) 14%, transparent);
--vibeui-button-001-soft-bg-hover:color-mix(in oklab, var(--vibeui-button-001-accent) 22%, transparent);
--vibeui-button-001-border:color-mix(in oklab, var(--vibeui-button-001-accent) 45%, transparent);
--vibeui-button-001-ring:color-mix(in oklab, var(--vibeui-button-001-accent) 70%, transparent);
--vibeui-button-001-radius:0.625rem;
--vibeui-button-001-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="button-001"]{
display:inline-flex;align-items:center;justify-content:center;gap:0.5em;
border:1px solid transparent;border-radius:var(--vibeui-button-001-radius);
font-family:var(--vibeui-button-001-font);font-weight:500;line-height:1;
text-decoration:none;white-space:nowrap;cursor:pointer;-webkit-appearance:none;appearance:none;
transition:background-color .18s ease,border-color .18s ease,color .18s ease,opacity .18s ease;
}
[data-vibeui-block="button-001"]:focus-visible{outline:2px solid var(--vibeui-button-001-ring);outline-offset:2px}
[data-vibeui-block="button-001"]:disabled{cursor:not-allowed;opacity:.55}

[data-vibeui-block="button-001"][data-size="sm"]{height:2rem;padding:0 0.75rem;font-size:0.8125rem}
[data-vibeui-block="button-001"][data-size="md"]{height:2.5rem;padding:0 1.125rem;font-size:0.875rem}
[data-vibeui-block="button-001"][data-size="lg"]{height:3rem;padding:0 1.5rem;font-size:1rem}

[data-vibeui-block="button-001"][data-tone="solid"]{background:var(--vibeui-button-001-accent);color:var(--vibeui-button-001-accent-fg)}
[data-vibeui-block="button-001"][data-tone="solid"]:hover:not(:disabled){background:color-mix(in oklab, var(--vibeui-button-001-accent) 88%, black)}
[data-vibeui-block="button-001"][data-tone="soft"]{background:var(--vibeui-button-001-soft-bg);color:var(--vibeui-button-001-accent)}
[data-vibeui-block="button-001"][data-tone="soft"]:hover:not(:disabled){background:var(--vibeui-button-001-soft-bg-hover)}
[data-vibeui-block="button-001"][data-tone="outline"]{background:transparent;color:var(--vibeui-button-001-accent);border-color:var(--vibeui-button-001-border)}
[data-vibeui-block="button-001"][data-tone="outline"]:hover:not(:disabled){background:var(--vibeui-button-001-soft-bg)}

[data-vibeui-block="button-001"] [data-part="spinner"]{
width:1em;height:1em;flex:none;border-radius:9999px;
border:2px solid currentColor;border-top-color:transparent;
animation:vibeui-button-001-spin .6s linear infinite;
}
@keyframes vibeui-button-001-spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="button-001"] *{animation:none!important;transition:none!important}}
`

/**
 * Основная кнопка действия. Один файл, ноль зависимостей, собственная
 * палитра — выглядит одинаково в любом проекте независимо от его темы.
 */
export function Button001({
  size = "md",
  tone = "solid",
  loading = false,
  accent,
  accentForeground,
  type = "button",
  disabled,
  className,
  style,
  children = "Get started",
  ...props
}: Button001Props) {
  const palette = {
    ...(accent ? { "--vibeui-button-001-accent": accent } : null),
    ...(accentForeground
      ? { "--vibeui-button-001-accent-fg": accentForeground }
      : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-button-001" precedence="medium">
        {STYLES}
      </style>
      <button
        {...props}
        type={type}
        data-vibeui-block="button-001"
        data-tone={tone}
        data-size={size}
        // Кнопка в состоянии загрузки не должна принимать повторный клик,
        // а aria-busy сообщает об этом скринридеру.
        disabled={disabled || loading}
        aria-busy={loading || undefined}
        className={className}
        style={palette}
      >
        {loading ? <span data-part="spinner" aria-hidden="true" /> : null}
        {children}
      </button>
    </>
  )
}