Buttons

Dual Icon Button

Кнопка со значками с двух сторон: левый называет действие, правый обещает продолжение, и на наведении они ведут себя по-разному.

  • button
  • icon
  • leading
  • trailing

Превью

1440pxHost theme

Использовать с AI

  1. 1. Скопируйте ссылку.
  2. 2. Напишите агенту своими словами и вставьте её в предложение.
  3. 3. Агент откроет ссылку и поставит компонент из registry.

размести это в шапке: https://vibeui.ru/c/button-036

Показать полную инструкцию

То, что лежит по ссылке в развёрнутом виде. Нужна, если агент не может открыть ссылку — тогда вставьте этот текст целиком.

# Установка и размещение «button-036» (Dual Icon Button) из VibeUI

## 1. Сначала установка — не пропускай, не пересоздавай
Выполни эту команду до того, как писать код:
npx shadcn@latest add https://vibeui.ru/r/button-036.json

Registry item: https://vibeui.ru/r/button-036.json
Ставится в: components/vibeui/button-036.tsx (точный путь следует алиасам components.json этого проекта).
npm-зависимости: нет.
Registry-зависимости: нет.

Ставь из реестра. Не пересоздавай по описанию, не подменяй похожим
компонентом из другой библиотеки и не переписывай под стиль проекта.

## 2. Что это
Кнопка со значками с двух сторон: левый называет действие, правый обещает продолжение, и на наведении они ведут себя по-разному.

Залитая кнопка с двумя значками разных ролей. Левый (плюс или лупа) называет действие и оживает на месте: плюс поворачивается, лупа подрастает. Правый (шеврон или стрелка) обещает переход и уезжает вперёд. Оба нарисованы на CSS. Ноль зависимостей, один файл.

## 3. Как использовать
import { Button036 } from "@/components/vibeui/button-036"

<Button036 leading="plus" trailing="chevron" onClick={createProject}>
  Создать проект
</Button036>

Полный список пропсов — в установленном файле.

## 4. Куда поставить
Это мелкий inline-компонент. Поставь его ровно туда, куда просил
пользователь, внутрь существующей разметки. Не создавай ради него
новую страницу, секцию или обёртку. Если на этом месте уже стоит похожий
контрол — замени его, а не добавляй второй.

Куда поставить: ___
(Эту строку заполняет пользователь. Если она осталась пустой — спроси,
а не угадывай.)

## 5. Сохранить как установлено
- разные роли значков: слева — что делаем, справа — что будет дальше; менять их местами бессмысленно
- разное поведение на наведении: левый оживает на месте, правый уезжает — так читается направление
- асимметричные паддинги: слева меньше, потому что значок оптически тяжелее пустоты
- значки на псевдоэлементах: они масштабируются вместе с currentColor и не тянут иконочную библиотеку
- aria-hidden на обоих значках — они украшают подпись, а не заменяют её
- вариант none для каждой стороны: кнопка должна уметь работать и с одним значком

## 6. Можно менять
- подпись кнопки
- левый значок — проп leading (plus, search или none)
- правый значок — проп trailing (chevron, arrow или none)
- акцентный цвет — проп accent
- обработчик onClick и остальные нативные пропсы button

## 7. Правила
- Не ставь оба значка «направляющими»: две стрелки в одной кнопке спорят друг с другом.
- Не подменяй CSS-значки картинками: компонент должен оставаться одним файлом без сетевых запросов.
- Не убирай aria-hidden — иначе скринридер зачитает декоративные узлы как пустые элементы.
- Не выноси CSS-переменные в globals.css: компонент должен оставаться одним файлом.

## 8. Проверка
- рендерится без ошибок в консоли;
- выглядит так же, как превью на странице VibeUI, откуда взята ссылка;
- если нет — ты изменил что-то из раздела 5, верни как было.

Для разработчика

npx shadcn@latest add https://vibeui.ru/r/button-036.json
https://vibeui.ru/r/button-036.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-button-036-*. Серверный компонент. Оба значка — пустые span с data-part="lead"/"trail" и data-icon, форма рисуется гранями и фоном псевдоэлементов, поэтому ни SVG, ни иконочного шрифта не нужно. Левый паддинг меньше правого: значок оптически тяжелее пустоты. Значения "none" убирают узел из разметки.

Исходник компонента

Тот же файл, который поставит агент. Нужен, если вы предпочитаете скопировать код руками.

import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Button036Props = ComponentPropsWithoutRef<"button"> & {
  /** Значок слева: он говорит, что за действие. */
  leading?: "plus" | "search" | "none"
  /** Значок справа: он говорит, что будет после клика. */
  trailing?: "chevron" | "arrow" | "none"
  accent?: string
}

// Идея компонента: два значка с разными ролями. Левый называет действие,
// правый обещает продолжение, поэтому у них разные отступы и разное
// поведение на наведении: левый оживает на месте, правый уезжает вперёд.
// Оба нарисованы гранями псевдоэлементов — ни SVG, ни шрифта иконок.
const STYLES = `
:where([data-vibeui-block="button-036"]){
--vibeui-button-036-accent:oklch(0.55 0.19 295);
--vibeui-button-036-fg:oklch(0.99 0.01 295);
--vibeui-button-036-radius:0.625rem;
--vibeui-button-036-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="button-036"]{
appearance:none;border:0;cursor:pointer;box-sizing:border-box;
display:inline-flex;align-items:center;gap:0.5rem;
height:2.5rem;padding:0 0.75rem 0 0.6875rem;border-radius:var(--vibeui-button-036-radius);
background:var(--vibeui-button-036-accent);color:var(--vibeui-button-036-fg);
font-family:var(--vibeui-button-036-font);font-size:0.875rem;font-weight:600;line-height:1;
transition:filter .16s ease;
}
[data-vibeui-block="button-036"]:hover:not(:disabled){filter:brightness(1.08)}
[data-vibeui-block="button-036"]:focus-visible{outline:2px solid var(--vibeui-button-036-accent);outline-offset:2px}
[data-vibeui-block="button-036"]:disabled{cursor:not-allowed;opacity:.5}
[data-vibeui-block="button-036"] [data-part="lead"],
[data-vibeui-block="button-036"] [data-part="trail"]{
position:relative;flex:none;width:0.875rem;height:0.875rem;
}
[data-vibeui-block="button-036"] [data-part="lead"][data-icon="plus"]::before,
[data-vibeui-block="button-036"] [data-part="lead"][data-icon="plus"]::after{
content:"";position:absolute;left:50%;top:50%;background:currentColor;border-radius:1px;
}
[data-vibeui-block="button-036"] [data-part="lead"][data-icon="plus"]::before{width:0.75rem;height:1.75px;margin:-0.875px 0 0 -0.375rem}
[data-vibeui-block="button-036"] [data-part="lead"][data-icon="plus"]::after{width:1.75px;height:0.75rem;margin:-0.375rem 0 0 -0.875px}
[data-vibeui-block="button-036"] [data-part="lead"][data-icon="search"]::before{
content:"";position:absolute;left:0;top:0;width:0.625rem;height:0.625rem;
box-sizing:border-box;border:1.75px solid currentColor;border-radius:50%;
}
[data-vibeui-block="button-036"] [data-part="lead"][data-icon="search"]::after{
content:"";position:absolute;right:0.0625rem;bottom:0.0625rem;width:0.3125rem;height:1.75px;
background:currentColor;border-radius:1px;transform:rotate(45deg);transform-origin:right center;
}
[data-vibeui-block="button-036"] [data-part="lead"]{transition:transform .16s ease}
[data-vibeui-block="button-036"]:hover:not(:disabled) [data-part="lead"]{transform:rotate(90deg)}
[data-vibeui-block="button-036"] [data-part="lead"][data-icon="search"]{transition:transform .16s ease}
[data-vibeui-block="button-036"]:hover:not(:disabled) [data-part="lead"][data-icon="search"]{transform:scale(1.12) rotate(0deg)}
[data-vibeui-block="button-036"] [data-part="trail"]::before{
content:"";position:absolute;right:0.1875rem;top:50%;
width:0.4375rem;height:0.4375rem;box-sizing:border-box;
border:1.75px solid currentColor;border-left:0;border-bottom:0;
transform:translateY(-50%) rotate(45deg);
}
[data-vibeui-block="button-036"] [data-part="trail"][data-icon="arrow"]::after{
content:"";position:absolute;left:0;top:50%;width:0.75rem;height:1.75px;
margin-top:-0.875px;background:currentColor;border-radius:1px;
}
[data-vibeui-block="button-036"] [data-part="trail"]{transition:transform .18s cubic-bezier(0.16,1,0.3,1)}
[data-vibeui-block="button-036"]:hover:not(:disabled) [data-part="trail"]{transform:translateX(3px)}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="button-036"] *{animation:none!important;transition:none!important}}
`

/**
 * Кнопка с двумя значками: левый называет действие, правый обещает переход.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Button036({
  leading = "plus",
  trailing = "chevron",
  accent,
  type = "button",
  className,
  style,
  children = "Создать проект",
  ...props
}: Button036Props) {
  const palette = {
    ...(accent ? { "--vibeui-button-036-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-button-036" precedence="medium">
        {STYLES}
      </style>
      <button
        {...props}
        type={type}
        data-vibeui-block="button-036"
        className={className}
        style={palette}
      >
        {leading === "none" ? null : (
          <span data-part="lead" data-icon={leading} aria-hidden="true" />
        )}
        {children}
        {trailing === "none" ? null : (
          <span data-part="trail" data-icon={trailing} aria-hidden="true" />
        )}
      </button>
    </>
  )
}