Inputs

Attached Button

A field attached to a button inside one border: subscribing in a single move, with Enter submitting the form and no key handlers.

  • inputgroup
  • form
  • subscribe
  • button

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/inputgroup-001?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 "inputgroup-001" (Attached 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/inputgroup-001.json

Registry item: https://vibeui.ru/r/inputgroup-001.json
Installs to: components/vibeui/inputgroup-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
A field attached to a button inside one border: subscribing in a single move, with Enter submitting the form and no key handlers.

A server-rendered field-and-button group in one border: collapsed borders, rounding only at the ends, focus above the neighbour, submit on Enter. Zero dependencies, one file.

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

<Inputgroup001
  name="email"
  label="News about new components"
  action="Subscribe"
/>

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-inputgroup-001-* palette — do not swap it for your theme tokens (bg-primary, border-input and the like)
- the collapsed borders: neighbours carry a negative margin so the halves share one line instead of two
- raising the focused element with z-index — without it the neighbour's border clips half the ring
- rounding only the first and last child: the middle of the group must stay square
- the form wrapper: Enter submits the subscription without a single key handler
- the label-to-field link and aria-describedby on the hint: attaching elements must not break screen readers

## 6. You may change
- the label copy and the action button caption
- the name prop — the field and hint ids are built from it
- the hint text under the group
- the accent through the accent prop — it colours the button and the ring

## 7. Rules
- The component submits nothing: attach onSubmit or action to the form.
- Do not let the button outgrow the field: on a phone, stack the group instead.
- A long button caption eats the room the address needs — keep it to one word.
- 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/inputgroup-001.json
https://vibeui.ru/r/inputgroup-001.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-inputgroup-001-*. Серверный: состояния нет, обёртка — настоящий <form>, поэтому Enter в поле нажимает кнопку. Общая рамка собрана схлопыванием границ (margin-left:-1px у соседей), скругления оставлены только на концах сцепки, фокусный элемент поднимается z-index'ом. Идентификаторы поля и подсказки собираются из пропа name, чтобы обойтись без клиентского хука.

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 Inputgroup001Props = Omit<
  ComponentPropsWithoutRef<"form">,
  "children"
> & {
  name?: string
  label?: string
  action?: string
  placeholder?: string
  hint?: string
  accent?: string
}

// Идея компонента: поле и кнопка — одна сцепка, а не два элемента рядом.
// Общая рамка собирается из схлопнутых границ: у соседей отрицательный
// margin, поэтому между ними одна линия, а не две. Фокусный элемент
// поднимается z-index'ом — иначе сосед срезает половину обводки. Обёртка —
// настоящий <form>, поэтому Enter в поле отправляет без обработчиков.
const STYLES = `
:where([data-vibeui-block="inputgroup-001"]){
--vibeui-inputgroup-001-surface:oklch(1 0 0);
--vibeui-inputgroup-001-shell:oklch(0.91 0.006 265);
--vibeui-inputgroup-001-fg:oklch(0.23 0.014 265);
--vibeui-inputgroup-001-muted:oklch(0.55 0.014 265);
--vibeui-inputgroup-001-field:oklch(0.99 0.002 265);
--vibeui-inputgroup-001-border:oklch(0.86 0.008 265);
--vibeui-inputgroup-001-accent:oklch(0.52 0.19 265);
--vibeui-inputgroup-001-radius:0.75rem;
--vibeui-inputgroup-001-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
/* Своя светлая подложка: подпись и подсказка — тёмный текст. */
[data-vibeui-block="inputgroup-001"]{
display:flex;flex-direction:column;gap:0.4375rem;
width:100%;max-width:24rem;box-sizing:border-box;padding:0.875rem;
background:var(--vibeui-inputgroup-001-surface);
border:1px solid var(--vibeui-inputgroup-001-shell);border-radius:0.875rem;
font-family:var(--vibeui-inputgroup-001-font);color:var(--vibeui-inputgroup-001-fg);
margin:0;
}
[data-vibeui-block="inputgroup-001"] *{box-sizing:border-box}
[data-vibeui-block="inputgroup-001"] label{font-size:0.8125rem;font-weight:600}
[data-vibeui-block="inputgroup-001"] [data-part="group"]{display:flex;align-items:stretch}
/* Схлопнутые границы: соседи делят одну линию. */
[data-vibeui-block="inputgroup-001"] [data-part="group"] > *{
position:relative;height:2.75rem;
border:1px solid var(--vibeui-inputgroup-001-border);
border-radius:0;margin-left:-1px;
font:inherit;font-size:0.875rem;
}
[data-vibeui-block="inputgroup-001"] [data-part="group"] > *:first-child{
margin-left:0;
border-radius:var(--vibeui-inputgroup-001-radius) 0 0 var(--vibeui-inputgroup-001-radius);
}
[data-vibeui-block="inputgroup-001"] [data-part="group"] > *:last-child{
border-radius:0 var(--vibeui-inputgroup-001-radius) var(--vibeui-inputgroup-001-radius) 0;
}
/* Фокус поднимает элемент над соседом, иначе обводку срезает. */
[data-vibeui-block="inputgroup-001"] [data-part="group"] > *:focus,
[data-vibeui-block="inputgroup-001"] [data-part="group"] > *:focus-visible{
z-index:1;outline:2px solid var(--vibeui-inputgroup-001-accent);outline-offset:-1px;
border-color:var(--vibeui-inputgroup-001-accent);
}
[data-vibeui-block="inputgroup-001"] input{
flex:1;min-width:0;padding:0 0.875rem;
background:var(--vibeui-inputgroup-001-field);color:inherit;
}
[data-vibeui-block="inputgroup-001"] button{
flex:none;padding:0 1rem;cursor:pointer;font-weight:650;
background:var(--vibeui-inputgroup-001-accent);
border-color:var(--vibeui-inputgroup-001-accent);
color:oklch(1 0 0);
transition:filter .16s ease;
}
[data-vibeui-block="inputgroup-001"] button:hover{filter:brightness(1.08)}
[data-vibeui-block="inputgroup-001"] [data-part="hint"]{
margin:0;font-size:0.75rem;line-height:1.4;color:var(--vibeui-inputgroup-001-muted);
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="inputgroup-001"] *{animation:none!important;transition:none!important}}
`

/**
 * Сцепка «поле + кнопка» с общей рамкой: подписка одним движением.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Inputgroup001({
  name = "email",
  label = "Письма о новых компонентах",
  action = "Подписаться",
  placeholder = "name@company.com",
  hint = "Раз в две недели. Отписаться можно из любого письма.",
  accent,
  className,
  style,
  ...props
}: Inputgroup001Props) {
  const palette = {
    ...(accent ? { "--vibeui-inputgroup-001-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-inputgroup-001" precedence="medium">
        {STYLES}
      </style>
      <form
        {...props}
        data-vibeui-block="inputgroup-001"
        className={className}
        style={palette}
      >
        <label htmlFor={`${name}-field`}>{label}</label>
        <div data-part="group">
          <input
            id={`${name}-field`}
            name={name}
            type="email"
            inputMode="email"
            autoComplete="email"
            required
            placeholder={placeholder}
            aria-describedby={`${name}-hint`}
          />
          <button type="submit">{action}</button>
        </div>
        <p data-part="hint" id={`${name}-hint`}>
          {hint}
        </p>
      </form>
    </>
  )
}