Buttons

Split Action Button

An action with a variants attachment: the main action on the left, alternatives on the right, a hairline between them. Self-contained — one file, no dependencies.

  • button
  • split
  • action
  • dropdown
  • 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.
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-002" (Split 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-002.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
An action with a variants attachment: the main action on the left, alternatives on the right, a hairline between them. Self-contained — one file, no dependencies.

Two zones inside one control: the main action on the left, a narrow chevron attachment on the right for alternatives. A hairline separates them and the attachment sits slightly darker, so it reads as part of the button rather than a second one. Zero dependencies, one file.

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

<Button002 onClick={publish} onMenuClick={openVariants} menuLabel="More options">
  Publish
</Button002>

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 two zones inside one shape, sharing a radius and overflow:hidden — do not split them into two separate buttons with a gap
- the hairline via inset box-shadow and the recessed background of the right zone: that is the trick separating a split button from a pair of buttons
- the width of the attachment: it is noticeably narrower than the main zone
- the aria-label on the attachment — it carries no text label
- focus-visible with z-index:1, otherwise the second zone's ring slides under the first
- the <style> block inside the component

## 6. You may change
- the main label and menuLabel
- the onClick and onMenuClick handlers
- the accent through the accent prop (and accentForeground)
- outer spacing through className — it lands on the group

## 7. Rules
- Do not turn the attachment into a <select> or a native dropdown: the component only fires a handler, the menu is your app's job.
- Do not drop the separator — without it the two zones melt into one button.
- 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

The install command is unavailable: the environment variableREGISTRY_BASE_URL

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-button-002-*. Корень — group из двух кнопок, поэтому className попадает на группу, а нативные пропсы button — на основную зону. Вторая зона вызывает onMenuClick и обязана иметь menuLabel: у неё нет видимой подписи.

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 Button002Props = ComponentPropsWithoutRef<"button"> & {
  /** Подпись и обработчик второй зоны. Первая зона — обычные пропсы button. */
  menuLabel?: string
  onMenuClick?: () => void
  accent?: string
  accentForeground?: string
  /** Класс попадает на группу, а не на основную кнопку: зон две. */
  className?: string
}

// Идея компонента: одно управляющее пятно, разделённое волосяной линией на
// две зоны — основное действие и его варианты. Вторая зона уже первой и чуть
// притоплена, поэтому читается как приставка, а не как вторая кнопка.
const STYLES = `
:where([data-vibeui-block="button-002"]){
--vibeui-button-002-accent:oklch(0.58 0.16 258);
--vibeui-button-002-accent-fg:oklch(0.99 0.004 258);
--vibeui-button-002-divider:oklch(1 0 0 / 28%);
--vibeui-button-002-ring:color-mix(in oklab, var(--vibeui-button-002-accent) 70%, transparent);
--vibeui-button-002-radius:0.625rem;
--vibeui-button-002-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="button-002"]{
display:inline-flex;align-items:stretch;isolation:isolate;
border-radius:var(--vibeui-button-002-radius);overflow:hidden;
font-family:var(--vibeui-button-002-font);
}
[data-vibeui-block="button-002"] [data-part]{
appearance:none;border:0;cursor:pointer;background:var(--vibeui-button-002-accent);
color:var(--vibeui-button-002-accent-fg);font:inherit;font-weight:500;font-size:0.875rem;
line-height:1;height:2.5rem;display:inline-flex;align-items:center;justify-content:center;
transition:background-color .18s ease,opacity .18s ease;
}
[data-vibeui-block="button-002"] [data-part="action"]{padding:0 1.125rem}
[data-vibeui-block="button-002"] [data-part="more"]{
padding:0 0.625rem;box-shadow:inset 1px 0 0 var(--vibeui-button-002-divider);
background:color-mix(in oklab, var(--vibeui-button-002-accent) 88%, black);
}
[data-vibeui-block="button-002"] [data-part]:hover:not(:disabled){background:color-mix(in oklab, var(--vibeui-button-002-accent) 80%, black)}
[data-vibeui-block="button-002"] [data-part]:focus-visible{outline:2px solid var(--vibeui-button-002-ring);outline-offset:2px;z-index:1}
[data-vibeui-block="button-002"] [data-part]:disabled{cursor:not-allowed;opacity:.55}
[data-vibeui-block="button-002"] svg{width:0.75rem;height:0.75rem;display:block;transition:transform .18s ease}
[data-vibeui-block="button-002"] [data-part="more"]:hover:not(:disabled) svg{transform:translateY(1px)}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="button-002"] *{animation:none!important;transition:none!important}}
`

/**
 * Кнопка с приставкой вариантов: основное действие слева, вызов списка
 * альтернатив справа. Один файл, ноль зависимостей, собственная палитра.
 */
export function Button002({
  menuLabel = "Другие варианты",
  onMenuClick,
  accent,
  accentForeground,
  type = "button",
  disabled,
  className,
  style,
  children = "Опубликовать",
  ...props
}: Button002Props) {
  const palette = {
    ...(accent ? { "--vibeui-button-002-accent": accent } : null),
    ...(accentForeground
      ? { "--vibeui-button-002-accent-fg": accentForeground }
      : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-button-002" precedence="medium">
        {STYLES}
      </style>
      <div
        data-vibeui-block="button-002"
        role="group"
        className={className}
        style={palette}
      >
        <button {...props} type={type} data-part="action" disabled={disabled}>
          {children}
        </button>
        <button
          type="button"
          data-part="more"
          aria-label={menuLabel}
          onClick={onMenuClick}
          disabled={disabled}
        >
          <svg viewBox="0 0 12 8" fill="none" aria-hidden="true">
            <path
              d="M1 1.5 6 6.5l5-5"
              stroke="currentColor"
              strokeWidth="1.75"
              strokeLinecap="round"
              strokeLinejoin="round"
            />
          </svg>
        </button>
      </div>
    </>
  )
}