Buttons

Wrapping Label Button

A button with a long label that honestly wraps onto two lines: the icon stays pinned to the first line instead of drifting to the vertical centre.

  • button
  • wrap
  • multiline
  • long-label

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-038?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-038" (Wrapping Label 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-038.json

Registry item: https://vibeui.ru/r/button-038.json
Installs to: components/vibeui/button-038.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 button with a long label that honestly wraps onto two lines: the icon stays pinned to the first line instead of drifting to the vertical centre.

A button built for a label longer than a line. Its width is capped, the text is set flush left and wraps with balanced lines, and the icon stays on the first line thanks to top alignment and a height of exactly one line-height. The surface is light and softly tinted. Zero dependencies, one file.

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

<Button038 width={18} onClick={confirmMove}>
  Confirm moving the meeting to next week
</Button038>

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
- align-items:flex-start and the icon's one-line-height box: otherwise on two lines it drifts to the middle
- text-wrap:balance — without it the second line collapses into a single word
- overflow-wrap:anywhere: a long URL or SKU must not break the button apart
- the flush-left text: with wrapping, centring reads as a layout mistake
- the width cap as a variable rather than a fixed width: the button has to shrink on a narrow screen
- the light tinted surface — it is what makes the dark label visible

## 6. You may change
- the label, long ones included
- the maximum width through the width prop, in rem
- the accent colour through the accent prop
- the onClick handler and the rest of the native button props
- outer spacing through className

## 7. Rules
- Do not switch to text-align:center: with wrapped lines, centring hurts readability.
- Do not drop this button into a dense toolbar — a two-line button breaks the row height.
- Do not centre the icon vertically: it has to point at the first line of the label.
- 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-038.json
https://vibeui.ru/r/button-038.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-button-038-*. Серверный компонент. Ширина ограничена переменной --vibeui-button-038-width, которую задаёт проп width в rem; включены text-wrap:balance, overflow-wrap:anywhere и hyphens:auto, поэтому длинное слово не выламывает кнопку. align-items:flex-start плюс высота значка, равная line-height первой строки, держат его на верхней строке.

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 Button038Props = ComponentPropsWithoutRef<"button"> & {
  /** Ширина кнопки в rem: за неё подпись переносится на вторую строку. */
  width?: number
  accent?: string
}

// Идея компонента: кнопка с длинной подписью, которая честно переносится.
// Обычная кнопка выравнивает содержимое по центру и растёт в строку — здесь
// задана ширина, включён перенос и balance, а значок прибит к первой строке
// через align-items:flex-start и собственную line-height, поэтому он не
// уезжает в вертикальный центр двухстрочного текста.
const STYLES = `
:where([data-vibeui-block="button-038"]){
--vibeui-button-038-bg:oklch(0.95 0.04 150);
--vibeui-button-038-border:oklch(0.85 0.07 150);
--vibeui-button-038-fg:oklch(0.32 0.07 150);
--vibeui-button-038-accent:oklch(0.5 0.13 150);
--vibeui-button-038-width:18rem;
--vibeui-button-038-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="button-038"]{
appearance:none;cursor:pointer;box-sizing:border-box;text-align:left;
display:inline-flex;align-items:flex-start;gap:0.625rem;
width:100%;max-width:var(--vibeui-button-038-width);
padding:0.75rem 0.875rem;border-radius:0.75rem;
border:1px solid var(--vibeui-button-038-border);
background:var(--vibeui-button-038-bg);color:var(--vibeui-button-038-fg);
font-family:var(--vibeui-button-038-font);font-size:0.875rem;font-weight:600;
/* Перенос и балансировка строк: длинная подпись не рвётся по одному слову. */
line-height:1.35;text-wrap:balance;overflow-wrap:anywhere;hyphens:auto;
transition:background-color .16s ease,border-color .16s ease;
}
[data-vibeui-block="button-038"]:hover:not(:disabled){
background:color-mix(in oklab,var(--vibeui-button-038-accent) 14%,var(--vibeui-button-038-bg));
border-color:var(--vibeui-button-038-accent);
}
[data-vibeui-block="button-038"]:focus-visible{outline:2px solid var(--vibeui-button-038-accent);outline-offset:2px}
[data-vibeui-block="button-038"]:disabled{cursor:not-allowed;opacity:.5}
/* Значок держится первой строки: высота равна её line-height. */
[data-vibeui-block="button-038"] [data-part="mark"]{
position:relative;flex:none;width:1.125rem;height:calc(0.875rem * 1.35);
}
[data-vibeui-block="button-038"] [data-part="mark"]::before{
content:"";position:absolute;left:0;top:50%;width:1.125rem;height:1.125rem;
margin-top:-0.5625rem;box-sizing:border-box;
border:1.75px solid var(--vibeui-button-038-accent);border-radius:0.3125rem;
}
[data-vibeui-block="button-038"] [data-part="mark"]::after{
content:"";position:absolute;left:0.3125rem;top:50%;width:0.3125rem;height:0.5625rem;
margin-top:-0.375rem;box-sizing:border-box;
border:1.75px solid var(--vibeui-button-038-accent);border-top:0;border-left:0;
transform:rotate(42deg);
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="button-038"]{transition:none!important}}
`

/**
 * Кнопка с длинной подписью и переносом: значок прибит к первой строке.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Button038({
  width = 18,
  accent,
  type = "button",
  className,
  style,
  children = "Подтвердить перенос встречи на следующую неделю",
  ...props
}: Button038Props) {
  const palette = {
    "--vibeui-button-038-width": `${width}rem`,
    ...(accent ? { "--vibeui-button-038-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-button-038" precedence="medium">
        {STYLES}
      </style>
      <button
        {...props}
        type={type}
        data-vibeui-block="button-038"
        className={className}
        style={palette}
      >
        <span data-part="mark" aria-hidden="true" />
        <span data-part="label">{children}</span>
      </button>
    </>
  )
}