Buttons

Ghost Button

A ghost button on its own surface: no border and no fill at rest, weight arrives only on hover, and the active state keeps a tinted wash.

  • button
  • ghost
  • secondary
  • toolbar

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/button-033?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 "button-033" (Ghost Button) 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/button-033.json

Registry item: https://vibeui.ru/r/button-033.json
Installs to: components/vibeui/button-033.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 ghost button on its own surface: no border and no fill at rest, weight arrives only on hover, and the active state keeps a tinted wash.

A minimal-weight button placed on its own paper surface. At rest only the label and a square marker show; hover brings in a wash mixed from the accent, and the active state keeps that wash while the label turns accent-coloured. The state is announced through aria-current. Zero dependencies, one file.

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

<Button033 active onClick={openSettings}>
  Settings
</Button033>

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 surface at the root of the block: a ghost exists only by contrast with it, on a bare background the button disappears
- the absence of a border and a fill on the button itself at rest — that is the definition of a ghost
- aria-current as the carrier of the active state: colour alone does not convey it
- the wash built with color-mix from the accent rather than a separate grey: it keeps the tie to the theme accent
- the square marker and its 45° turn on hover — a second, colourless signal
- focus-visible with an offset: without it the button is lost during keyboard use

## 6. You may change
- the label
- the on state through the active prop
- the accent colour through the accent prop
- the onClick handler and disabled
- outer spacing through className — it lands on the surface

## 7. Rules
- Do not drop the surface at the root: on a dark background the ghost is unreadable without it.
- Do not line up several ghosts as primary actions — this weight is meant for secondary ones.
- Do not replace aria-current with an 'active' class: the state has to reach the accessibility tree.
- 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/button-033.json
https://vibeui.ru/r/button-033.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-button-033-*. Серверный компонент. Корень блока — не сама кнопка, а бумажная поверхность, на которой призрак только и читается; кнопка лежит внутри и помечена data-part="button". Активное состояние объявлено через aria-current, поэтому его видно и скринридеру, а не только по цвету. Подложки собраны через color-mix от акцента, отдельных переменных под оттенки нет.

Component source

The same file your agent installs. Here in case you would rather copy it by hand.

import type {
  ComponentPropsWithoutRef,
  CSSProperties,
  MouseEventHandler,
  ReactNode,
} from "react"

export type Button033Props = Omit<
  ComponentPropsWithoutRef<"div">,
  "children" | "onClick"
> & {
  children?: ReactNode
  /** Постоянно подсвеченное состояние: кнопка-призрак как активный пункт. */
  active?: boolean
  disabled?: boolean
  onClick?: MouseEventHandler<HTMLButtonElement>
  accent?: string
}

// Идея компонента: кнопка без собственного пятна. В покое у неё нет ни фона,
// ни рамки — она живёт на чужой поверхности и берёт вес только на наведении.
// Поэтому корень блока — сама поверхность: без неё призрак не с чем сравнить,
// а на тёмной подложке каталога его было бы не видно.
const STYLES = `
:where([data-vibeui-block="button-033"]){
--vibeui-button-033-surface:oklch(0.99 0.003 265);
--vibeui-button-033-border:oklch(0.9 0.006 265);
--vibeui-button-033-fg:oklch(0.46 0.014 265);
--vibeui-button-033-fg-strong:oklch(0.24 0.02 265);
--vibeui-button-033-accent:oklch(0.55 0.17 265);
--vibeui-button-033-radius:0.5rem;
--vibeui-button-033-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="button-033"]{
display:inline-flex;box-sizing:border-box;padding:0.375rem;
background:var(--vibeui-button-033-surface);
border:1px solid var(--vibeui-button-033-border);
border-radius:calc(var(--vibeui-button-033-radius) + 0.375rem);
font-family:var(--vibeui-button-033-font);
}
[data-vibeui-block="button-033"] [data-part="button"]{
appearance:none;border:0;background:transparent;cursor:pointer;
display:inline-flex;align-items:center;gap:0.5rem;
height:2.25rem;padding:0 0.75rem;border-radius:var(--vibeui-button-033-radius);
font:inherit;font-size:0.875rem;font-weight:500;line-height:1;
color:var(--vibeui-button-033-fg);
transition:background-color .16s ease,color .16s ease;
}
[data-vibeui-block="button-033"] [data-part="dot"]{
flex:none;width:0.4375rem;height:0.4375rem;border-radius:2px;
background:currentColor;opacity:.45;
transition:opacity .16s ease,transform .16s ease;
}
[data-vibeui-block="button-033"] [data-part="button"]:hover:not(:disabled){
background:color-mix(in oklab,var(--vibeui-button-033-accent) 10%,transparent);
color:var(--vibeui-button-033-fg-strong);
}
[data-vibeui-block="button-033"] [data-part="button"]:hover:not(:disabled) [data-part="dot"]{opacity:.9;transform:rotate(45deg)}
[data-vibeui-block="button-033"] [data-part="button"][aria-current="true"]{
background:color-mix(in oklab,var(--vibeui-button-033-accent) 14%,transparent);
color:var(--vibeui-button-033-accent);font-weight:650;
}
[data-vibeui-block="button-033"] [data-part="button"][aria-current="true"] [data-part="dot"]{opacity:1;transform:rotate(45deg)}
[data-vibeui-block="button-033"] [data-part="button"]:focus-visible{
outline:2px solid var(--vibeui-button-033-accent);outline-offset:2px;
}
[data-vibeui-block="button-033"] [data-part="button"]:disabled{cursor:not-allowed;opacity:.45}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="button-033"] *{animation:none!important;transition:none!important}}
`

/**
 * Кнопка-призрак на собственной поверхности: вес появляется только
 * на наведении. Один файл, ноль зависимостей, собственная палитра.
 */
export function Button033({
  children = "Настройки",
  active = false,
  disabled,
  onClick,
  accent,
  className,
  style,
  ...props
}: Button033Props) {
  const palette = {
    ...(accent ? { "--vibeui-button-033-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-button-033" precedence="medium">
        {STYLES}
      </style>
      <div
        {...props}
        data-vibeui-block="button-033"
        className={className}
        style={palette}
      >
        <button
          type="button"
          data-part="button"
          aria-current={active ? "true" : undefined}
          disabled={disabled}
          onClick={onClick}
        >
          <span data-part="dot" aria-hidden="true" />
          {children}
        </button>
      </div>
    </>
  )
}