Inputs

Floating Label Input

A text field whose label slides into the border as you type. The label never disappears, and no separate line above the field is needed. Hint and error states are built in.

  • input
  • text field
  • form
  • floating 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/input-001?lang=en

We never publish it or pass it on.
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 "input-001" (Floating Label Input) 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/input-001.json

Registry item: https://vibeui.ru/r/input-001.json
Installs to: components/vibeui/input-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 text field whose label slides into the border as you type. The label never disappears, and no separate line above the field is needed. Hint and error states are built in.

A text field with a floating label. While the field is empty the label sits where the text goes; on focus or with a value it shrinks and moves into the top of the border — pure CSS, no state and no client JS. Ships with a hint, an error state, two sizes and disabled handling. Zero dependencies, one file, its own palette.

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

<Input001
  id="email"
  name="email"
  type="email"
  label="Work email"
  hint="We never publish it or pass it on."
/>

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-input-001-* palette — do not swap it for your theme tokens (bg-background, border-input, text-muted-foreground and the like)
- the floating-label mechanism: the input's placeholder=" ", the :placeholder-shown:not(:focus) selector and the adjacent [data-part="label"] — without the space placeholder the label stops floating
- the field's top padding: it reserves the room the label moves into, and without it the text and the label overlap
- the box-shadow focus ring around the border and the label turning accent-coloured: together they mark the active field
- the error wiring: data-invalid on the root, aria-invalid on the input and role="alert" on the note
- the label transition through transform rather than by changing font size and position, and the prefers-reduced-motion rule
- the <style> block inside the component — it holds the palette, the sizes and the label behaviour

## 6. You may change
- the label and hint copy, and the error message
- the accent through the accent prop — it colours the focus ring and the active label
- size: md for fields in a form, lg for a single field owning the screen
- any native input props: type, name, value, onChange, required, autoComplete, disabled, pattern
- outer spacing and width through className — by default the field fills its parent

## 7. Rules
- Do not replace the label with a placeholder. The placeholder is taken: it is the space the whole mechanism rests on.
- Do not wrap the component in your own <label> — there is one inside, and nested labels break clicking the caption.
- id is what links the hint to the field through aria-describedby. Without it the field still works, but a screen reader will not announce the hint.
- 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/input-001.json
https://vibeui.ru/r/input-001.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-input-001-*. Плавающая подпись держится на CSS-селекторе :placeholder-shown, поэтому JS не нужен и компонент остаётся серверным. Внутри — нативный <input>, принимающий любые его пропсы. Ошибка задаётся строкой в error: она включает data-invalid, aria-invalid и role="alert" у примечания.

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 Input001Size = "md" | "lg"

export type Input001Props = Omit<
  ComponentPropsWithoutRef<"input">,
  "size" | "placeholder"
> & {
  label?: string
  /** Подсказка под полем. В состоянии ошибки её место занимает текст ошибки. */
  hint?: string
  /** Текст ошибки. Непустая строка переводит поле в невалидное состояние. */
  error?: string
  size?: Input001Size
  accent?: string
}

// Идея компонента: подпись живёт внутри рамки и уезжает в неё же при вводе.
// Поле не занимает вертикаль на отдельную подпись, но подпись никогда не
// исчезает — в отличие от placeholder, который пропадает вместе с контекстом.
// Всё держится на :placeholder-shown, поэтому JS не нужен.
const STYLES = `
:where([data-vibeui-block="input-001"]){
--vibeui-input-001-surface:oklch(1 0 0);
--vibeui-input-001-surface-border:oklch(0.91 0.006 265);
--vibeui-input-001-fg:oklch(0.24 0.016 265);
--vibeui-input-001-muted:oklch(0.55 0.014 265);
--vibeui-input-001-bg:oklch(1 0 0);
--vibeui-input-001-border:oklch(0.87 0.008 265);
--vibeui-input-001-accent:oklch(0.55 0.2 262);
--vibeui-input-001-danger:oklch(0.55 0.19 25);
--vibeui-input-001-radius:0.625rem;
--vibeui-input-001-height:3.5rem;
--vibeui-input-001-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
/* Собственная подложка: подпись поля — это текст, и на тёмной странице
   он обязан читаться без правки палитры проекта. */
[data-vibeui-block="input-001"]{
box-sizing:border-box;padding:0.875rem;
background:var(--vibeui-input-001-surface);
border:1px solid var(--vibeui-input-001-surface-border);border-radius:0.875rem;
display:flex;flex-direction:column;gap:0.375rem;
font-family:var(--vibeui-input-001-font);color:var(--vibeui-input-001-fg);
}
[data-vibeui-block="input-001"][data-size="lg"]{--vibeui-input-001-height:4rem}
[data-vibeui-block="input-001"] [data-part="field"]{
position:relative;display:block;
}
[data-vibeui-block="input-001"] input{
width:100%;box-sizing:border-box;margin:0;
height:var(--vibeui-input-001-height);
padding:1.375rem 0.875rem 0.5rem;
border:1px solid var(--vibeui-input-001-border);
border-radius:var(--vibeui-input-001-radius);
background:var(--vibeui-input-001-bg);color:inherit;
font:inherit;font-size:0.9375rem;line-height:1.2;
transition:border-color .16s ease,box-shadow .16s ease;
}
[data-vibeui-block="input-001"][data-size="lg"] input{font-size:1rem;padding-top:1.625rem}
[data-vibeui-block="input-001"] input:hover:not(:disabled):not(:focus){border-color:var(--vibeui-input-001-muted)}
[data-vibeui-block="input-001"] input:focus{
outline:none;border-color:var(--vibeui-input-001-accent);
box-shadow:0 0 0 3px color-mix(in oklab,var(--vibeui-input-001-accent) 22%,transparent);
}
[data-vibeui-block="input-001"] input:disabled{
cursor:not-allowed;opacity:.55;
background:color-mix(in oklab,var(--vibeui-input-001-border) 25%,var(--vibeui-input-001-bg));
}
[data-vibeui-block="input-001"] [data-part="label"]{
position:absolute;left:0.9375rem;top:0.5rem;
font-size:0.6875rem;font-weight:500;letter-spacing:0.01em;
color:var(--vibeui-input-001-muted);
transform-origin:left top;pointer-events:none;
transition:transform .16s ease,color .16s ease;
}
/* Поле пустое и не в фокусе — подпись стоит на месте текста. */
[data-vibeui-block="input-001"] input:placeholder-shown:not(:focus) + [data-part="label"]{
transform:translateY(0.8125rem) scale(1.32);
}
[data-vibeui-block="input-001"] input:focus + [data-part="label"]{color:var(--vibeui-input-001-accent)}
[data-vibeui-block="input-001"] [data-part="note"]{
font-size:0.75rem;line-height:1.35;color:var(--vibeui-input-001-muted);
}
[data-vibeui-block="input-001"][data-invalid="true"] input{border-color:var(--vibeui-input-001-danger)}
[data-vibeui-block="input-001"][data-invalid="true"] input:focus{
box-shadow:0 0 0 3px color-mix(in oklab,var(--vibeui-input-001-danger) 22%,transparent);
}
[data-vibeui-block="input-001"][data-invalid="true"] [data-part="label"],
[data-vibeui-block="input-001"][data-invalid="true"] [data-part="note"]{color:var(--vibeui-input-001-danger)}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="input-001"] *{animation:none!important;transition:none!important}}
`

/**
 * Текстовое поле с подписью, которая уезжает внутрь рамки при вводе.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Input001({
  label = "Рабочая почта",
  hint,
  error,
  size = "md",
  accent,
  id,
  className,
  style,
  ...props
}: Input001Props) {
  const palette = {
    ...(accent ? { "--vibeui-input-001-accent": accent } : null),
    ...style,
  } as CSSProperties

  const invalid = Boolean(error)
  const note = error || hint
  const noteId = id && note ? `${id}-note` : undefined

  return (
    <>
      <style href="vibeui-input-001" precedence="medium">
        {STYLES}
      </style>
      <div
        data-vibeui-block="input-001"
        data-size={size}
        data-invalid={invalid || undefined}
        className={className}
        style={palette}
      >
        <label data-part="field">
          <input
            {...props}
            id={id}
            placeholder=" "
            aria-invalid={invalid || undefined}
            aria-describedby={noteId}
          />
          <span data-part="label">{label}</span>
        </label>
        {note ? (
          <span
            data-part="note"
            id={noteId}
            role={invalid ? "alert" : undefined}
          >
            {note}
          </span>
        ) : null}
      </div>
    </>
  )
}