Inputs

Unit Suffix Field

A number and its unit inside one frame: the unit lives in its own zone behind a divider, so it stays visible on an empty field.

  • number
  • unit
  • measure
  • form

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/number-003?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 "number-003" (Unit Suffix Field) 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/number-003.json

Registry item: https://vibeui.ru/r/number-003.json
Installs to: components/vibeui/number-003.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 number and its unit inside one frame: the unit lives in its own zone behind a divider, so it stays visible on an empty field.

A number field with the unit in a separate zone on the right: it never disappears while typing and never drifts away from the number. Server component, zero dependencies, one file.

## 3. How to use it
import { Number003 } from "@/components/vibeui/number-003"

<Number003 label="Apartment area" unit="m²" defaultValue={54} min={10} max={400} />

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-number-003-* palette — do not swap it for your theme tokens
- its own light surface: the field gets shown over any background
- the unit as a separate zone rather than a placeholder: a placeholder vanishes exactly when the number appears
- the right-aligned number: no gap is allowed between the value and its unit
- aria-hidden on the unit: a screen reader takes it from the label, not as a second fragment of text
- the server nature of the component — nothing here needs the client, the form collects the value
- the label-to-input binding by id: clicking the caption has to focus the field

## 6. You may change
- label — the field caption
- unit — the unit of measure itself
- min, max, step and defaultValue
- hint — the explanation under the field
- name — the form field name
- the accent through the accent prop

## 7. Rules
- The browser spinners are removed: they overlap the unit zone and break the alignment.
- A long unit ("kWh") squeezes the field — widen the block max-width for those.
- The unit is not part of the value: the form submits the number alone.
- 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/number-003.json
https://vibeui.ru/r/number-003.json

Компонент самодостаточен: один файл, ноль зависимостей, палитра в локальных переменных --vibeui-number-003-*. Серверный: состояние не нужно, значение забирает форма по name. Единица сделана отдельным блоком с собственным фоном, а не плейсхолдером и не текстом рядом — плейсхолдер исчезает при вводе, а текст рядом отрывается от числа. Число выровнено вправо, вплотную к единице, поэтому «54 м²» читается как одна величина.

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 Number003Props = Omit<
  ComponentPropsWithoutRef<"div">,
  "children" | "defaultValue"
> & {
  label?: string
  unit?: string
  hint?: string
  defaultValue?: number
  min?: number
  max?: number
  step?: number
  name?: string
  accent?: string
}

// Идея компонента: число и его единица в одной рамке. Единица не плейсхолдер
// и не текст рядом — это отдельная зона поля за разделителем, поэтому она
// видна и при заполненном поле, и при пустом. Число прижато вправо, к самой
// единице: так «12 м²» читается как одна величина, а не как два элемента.
// Компонент серверный: состояние тут не нужно, значение забирает форма.
const STYLES = `
:where([data-vibeui-block="number-003"]){
--vibeui-number-003-surface:oklch(1 0 0);
--vibeui-number-003-field:oklch(1 0 0);
--vibeui-number-003-suffix:oklch(0.965 0.004 265);
--vibeui-number-003-shell:oklch(0.9 0.006 265);
--vibeui-number-003-fg:oklch(0.23 0.014 265);
--vibeui-number-003-muted:oklch(0.55 0.014 265);
--vibeui-number-003-border:oklch(0.88 0.008 265);
--vibeui-number-003-accent:oklch(0.52 0.15 250);
--vibeui-number-003-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
/* Своя светлая подложка: поле показывают поверх любого фона. */
[data-vibeui-block="number-003"]{
display:flex;flex-direction:column;gap:0.375rem;
width:100%;max-width:16rem;box-sizing:border-box;padding:0.875rem;
background:var(--vibeui-number-003-surface);
border:1px solid var(--vibeui-number-003-shell);border-radius:0.875rem;
font-family:var(--vibeui-number-003-font);color:var(--vibeui-number-003-fg);
}
[data-vibeui-block="number-003"] label{font-size:0.8125rem;font-weight:650}
[data-vibeui-block="number-003"] [data-part="field"]{
display:flex;align-items:stretch;overflow:hidden;
border:1px solid var(--vibeui-number-003-border);border-radius:0.625rem;
background:var(--vibeui-number-003-field);
}
[data-vibeui-block="number-003"] [data-part="field"]:focus-within{
border-color:var(--vibeui-number-003-accent);
box-shadow:0 0 0 2px oklch(0.52 0.15 250 / 20%);
}
/* Число прижато к единице: «12 м²» читается как одна величина. */
[data-vibeui-block="number-003"] input{
flex:1 1 auto;min-width:0;width:100%;
appearance:none;border:0;background:none;outline:none;
height:2.625rem;padding:0 0.625rem;color:inherit;
font:inherit;font-size:1rem;font-weight:680;text-align:right;
font-variant-numeric:tabular-nums;
}
[data-vibeui-block="number-003"] input::-webkit-outer-spin-button,
[data-vibeui-block="number-003"] input::-webkit-inner-spin-button{appearance:none;margin:0}
/* Единица — отдельная зона за разделителем: видна и на пустом поле. */
[data-vibeui-block="number-003"] [data-part="unit"]{
display:flex;align-items:center;flex:none;
padding:0 0.75rem;
border-left:1px solid var(--vibeui-number-003-border);
background:var(--vibeui-number-003-suffix);
color:var(--vibeui-number-003-muted);
font-size:0.875rem;font-weight:650;
}
[data-vibeui-block="number-003"] [data-part="hint"]{
margin:0;font-size:0.75rem;line-height:1.4;color:var(--vibeui-number-003-muted);
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="number-003"] *{animation:none!important;transition:none!important}}
`

/**
 * Число с единицей измерения в отдельной зоне поля.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Number003({
  label = "Площадь квартиры",
  unit = "м²",
  hint = "Считается по внутренним стенам, без балкона.",
  defaultValue = 54,
  min = 10,
  max = 400,
  step = 1,
  name = "area",
  accent,
  className,
  style,
  ...props
}: Number003Props) {
  const palette = {
    ...(accent ? { "--vibeui-number-003-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-number-003" precedence="medium">
        {STYLES}
      </style>
      <div
        {...props}
        data-vibeui-block="number-003"
        className={className}
        style={palette}
      >
        <label htmlFor={`${name}-input`}>{label}</label>
        <div data-part="field">
          <input
            id={`${name}-input`}
            name={name}
            type="number"
            inputMode="decimal"
            min={min}
            max={max}
            step={step}
            defaultValue={defaultValue}
            aria-describedby={hint ? `${name}-hint` : undefined}
          />
          <span data-part="unit" aria-hidden="true">
            {unit}
          </span>
        </div>
        {hint ? (
          <p id={`${name}-hint`} data-part="hint">
            {hint}
          </p>
        ) : null}
      </div>
    </>
  )
}