Inputs

Input Group

Поле с неизменяемой приставкой и окончанием внутри одной рамки: домен, протокол, единица измерения. Фокус подсвечивает группу целиком, а не только середину.

  • input
  • input group
  • prefix
  • form

Превью

1440pxHost theme

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

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

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

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

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

# Установка и размещение «input-002» (Input Group) из VibeUI

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

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

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

## 2. Что это
Поле с неизменяемой приставкой и окончанием внутри одной рамки: домен, протокол, единица измерения. Фокус подсвечивает группу целиком, а не только середину.

Поле ввода с неизменяемыми частями слева и справа в общей рамке: протокол и домен, символ валюты, единица измерения, окончание файла. Приставка визуально отделена волосяной линией, фокус подсвечивает всю группу через :focus-within. Ноль зависимостей, один файл, своя палитра, клиентский JS не нужен.

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

<Input002
  id="slug"
  name="slug"
  label="Адрес страницы"
  prefix="vibeui.ru/"
  placeholder="portfolio"
/>

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

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

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

## 5. Сохранить как установлено
- локальную палитру --vibeui-input-002-* и не подменять её токенами вашей темы (bg-muted, border-input, text-muted-foreground и подобными)
- общую рамку: рамка живёт на группе, а у самого input её нет — иначе внутри рамки появится вторая
- подсветку группы через :focus-within вместе с кольцом box-shadow: без неё при фокусе загорается только середина
- разделители между фиксированной частью и полем (border-right у начала, border-left у конца) и их светлую подложку — по ним видно, что эту часть не редактируют
- overflow:hidden у группы: он обрезает подложку приставки по радиусу рамки
- user-select:none у фиксированных частей — их не выделяют и не копируют вместе со значением
- блок <style> внутри компонента — в нём вся палитра, геометрия и поведение фокуса

## 6. Можно менять
- содержимое prefix и suffix — это ReactNode, туда же можно положить свою иконку
- текст label и placeholder
- акцент через проп accent — цвет рамки и кольца при фокусе
- любые нативные пропсы input: type, name, value, onChange, inputMode, required, disabled
- ширину и внешние отступы через className — по умолчанию группа занимает всю ширину родителя

## 7. Правила
- Не кладите в prefix и suffix кнопки. Это неинтерактивные части рамки; кнопке рядом с полем нужен собственный компонент.
- Не дублируйте приставку в placeholder: пользователь увидит vibeui.ru дважды.
- id связывает подпись с полем. Без него клик по подписи не наводит фокус.
- Не выносите CSS-переменные в globals.css: компонент обязан оставаться одним файлом.

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

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

npx shadcn@latest add https://vibeui.ru/r/input-002.json
https://vibeui.ru/r/input-002.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-input-002-*. Приставка и окончание — обычные span'ы внутри рамки, поле растягивается между ними на flex. Подсветка группы держится на :focus-within, выключенное состояние — на :has(input:disabled), поэтому JS не нужен и компонент остаётся серверным. Внутри нативный <input>, принимающий любые его пропсы.

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

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

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

export type Input002Props = Omit<
  ComponentPropsWithoutRef<"input">,
  "size" | "prefix"
> & {
  /** Неизменяемая часть слева: протокол, домен, символ валюты. */
  prefix?: ReactNode
  /** Неизменяемая часть справа: домен почты, единица измерения, расширение. */
  suffix?: ReactNode
  label?: string
  accent?: string
}

// Идея компонента: приставка и окончание живут внутри той же рамки, что и
// поле, а фокус подсвечивает группу целиком. Пользователь видит будущее
// значение полностью и не набирает то, что и так известно.
const STYLES = `
:where([data-vibeui-block="input-002"]){
--vibeui-input-002-surface:oklch(1 0 0);
--vibeui-input-002-surface-border:oklch(0.91 0.006 265);
--vibeui-input-002-fg:oklch(0.24 0.016 265);
--vibeui-input-002-muted:oklch(0.54 0.014 265);
--vibeui-input-002-bg:oklch(1 0 0);
--vibeui-input-002-fixed:oklch(0.97 0.003 265);
--vibeui-input-002-border:oklch(0.87 0.008 265);
--vibeui-input-002-accent:oklch(0.55 0.2 262);
--vibeui-input-002-radius:0.625rem;
--vibeui-input-002-height:2.75rem;
--vibeui-input-002-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
/* Собственная подложка: подпись поля — это текст, и на тёмной странице
   он обязан читаться без правки палитры проекта. */
[data-vibeui-block="input-002"]{
box-sizing:border-box;padding:0.875rem;
background:var(--vibeui-input-002-surface);
border:1px solid var(--vibeui-input-002-surface-border);border-radius:0.875rem;
display:flex;flex-direction:column;gap:0.375rem;
font-family:var(--vibeui-input-002-font);color:var(--vibeui-input-002-fg);
}
[data-vibeui-block="input-002"] [data-part="label"]{
font-size:0.8125rem;font-weight:500;
}
[data-vibeui-block="input-002"] [data-part="group"]{
display:flex;align-items:stretch;overflow:hidden;
height:var(--vibeui-input-002-height);
border:1px solid var(--vibeui-input-002-border);
border-radius:var(--vibeui-input-002-radius);
background:var(--vibeui-input-002-bg);
transition:border-color .16s ease,box-shadow .16s ease;
}
[data-vibeui-block="input-002"] [data-part="group"]:focus-within{
border-color:var(--vibeui-input-002-accent);
box-shadow:0 0 0 3px color-mix(in oklab,var(--vibeui-input-002-accent) 22%,transparent);
}
[data-vibeui-block="input-002"] [data-part="fixed"]{
display:flex;align-items:center;flex:none;
padding:0 0.75rem;
background:var(--vibeui-input-002-fixed);
color:var(--vibeui-input-002-muted);
font-size:0.875rem;white-space:nowrap;user-select:none;
}
[data-vibeui-block="input-002"] [data-part="fixed"][data-side="start"]{border-right:1px solid var(--vibeui-input-002-border)}
[data-vibeui-block="input-002"] [data-part="fixed"][data-side="end"]{border-left:1px solid var(--vibeui-input-002-border)}
[data-vibeui-block="input-002"] input{
flex:1 1 auto;min-width:0;margin:0;border:0;outline:none;
padding:0 0.75rem;background:transparent;color:inherit;
font:inherit;font-size:0.9375rem;
}
[data-vibeui-block="input-002"] input::placeholder{color:color-mix(in oklab,var(--vibeui-input-002-muted) 70%,transparent)}
[data-vibeui-block="input-002"] input:disabled{cursor:not-allowed}
[data-vibeui-block="input-002"]:has(input:disabled) [data-part="group"]{opacity:.55}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="input-002"] *{animation:none!important;transition:none!important}}
`

/**
 * Поле с неизменяемой приставкой и окончанием в одной рамке.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Input002({
  prefix = "vibeui.ru/",
  suffix,
  label = "Адрес страницы",
  accent,
  id,
  className,
  style,
  ...props
}: Input002Props) {
  const palette = {
    ...(accent ? { "--vibeui-input-002-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-input-002" precedence="medium">
        {STYLES}
      </style>
      <div data-vibeui-block="input-002" className={className} style={palette}>
        {label ? (
          <label data-part="label" htmlFor={id}>
            {label}
          </label>
        ) : null}
        <div data-part="group">
          {prefix ? (
            <span data-part="fixed" data-side="start">
              {prefix}
            </span>
          ) : null}
          <input {...props} id={id} />
          {suffix ? (
            <span data-part="fixed" data-side="end">
              {suffix}
            </span>
          ) : null}
        </div>
      </div>
    </>
  )
}