Phone Input

With Extension

A phone field with a separate extension input: two values mean two fields with different autofill hints, and the narrow one states its own length.

  • phone
  • extension
  • container-query
  • 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/phoneinput-007?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 "phoneinput-007" (With Extension) 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/phoneinput-007.json

Registry item: https://vibeui.ru/r/phoneinput-007.json
Installs to: components/vibeui/phoneinput-007.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 phone field with a separate extension input: two values mean two fields with different autofill hints, and the narrow one states its own length.

A work phone field with a separate extension: side by side at wider widths, stacked at narrow ones. Zero dependencies, one file, no client JS.

## 3. How to use it
import { Phoneinput007 } from "@/components/vibeui/phoneinput-007"

<Phoneinput007
  label="Work phone"
  extensionLabel="Extension"
  extensionPlaceholder="1234"
/>

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-phoneinput-007-* palette — do not swap it for your theme tokens
- autoComplete="tel-extension" on the extension: otherwise the browser pastes it into the main number
- the layout on [data-part="shell"] rather than the root: @container never applies to the container itself
- the narrow fixed width of the extension — it tells you three or four digits are expected
- align-items:flex-end in the row: with captions of different lengths the field borders otherwise sit at different heights
- the block's own light surface: without it the dark labels disappear on a dark background

## 6. You may change
- the main field name through label, the extension caption through extensionLabel and its stub through extensionPlaceholder
- the focus color through accent
- the extension width and the @container breakpoint
- the wording of the note under the fields

## 7. Rules
- An extension is not a phone number: type="text" with inputMode="numeric" is more honest than type="tel" and its star-and-hash keypad.
- Do not glue the number and the extension into one string on submit: parsing "+44 20 7123456 ext. 12" on the server is painful.
- An extension is nearly always optional — if yours is required, say so right in the caption.
- 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/phoneinput-007.json
https://vibeui.ru/r/phoneinput-007.json

Компонент самодостаточен: один файл, без зависимостей, палитра в локальных переменных --vibeui-phoneinput-007-*. Серверный: из хуков только useId. Добавочный вынесен в своё поле с autoComplete="tel-extension", поэтому браузер не подставляет его внутрь основного номера и не путает автозаполнение. Корень объявлен container-type:inline-size, а раскладка лежит на внутренней оболочке [data-part="shell"]: правило внутри @container не действует на сам контейнер. От 20rem собственной ширины поля встают в строку, добавочному отводится 6rem — ширина сама сообщает, что там три-четыре цифры; на узкой ширине поля идут стопкой. Выравнивание по нижнему краю (align-items:flex-end) держит рамки полей на одной линии при подписях разной длины.

Component source

The same file your agent installs. Here in case you would rather copy it by hand.

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

export type Phoneinput007Props = Omit<
  ComponentPropsWithoutRef<"div">,
  "children"
> & {
  label?: string
  extensionLabel?: string
  extensionPlaceholder?: string
  accent?: string
}

// Идея компонента: добавочный — отдельное значение, а не хвост номера.
// Он живёт в своём поле с autoComplete="tel-extension", поэтому браузер
// не подставляет его в основной номер и не путает автозаполнение.
// Ширина добавочного фиксирована и мала: она сама говорит, что там
// три-четыре цифры, а не второй телефон.
const STYLES = `
:where([data-vibeui-block="phoneinput-007"]){
--vibeui-phoneinput-007-surface:oklch(1 0 0);
--vibeui-phoneinput-007-surface-border:oklch(0.91 0.006 265);
--vibeui-phoneinput-007-fg:oklch(0.24 0.016 265);
--vibeui-phoneinput-007-muted:oklch(0.54 0.014 265);
--vibeui-phoneinput-007-field-border:oklch(0.85 0.01 265);
--vibeui-phoneinput-007-accent:oklch(0.55 0.2 262);
--vibeui-phoneinput-007-radius:0.625rem;
--vibeui-phoneinput-007-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
container-type:inline-size;
}
[data-vibeui-block="phoneinput-007"]{
box-sizing:border-box;width:100%;max-width:24rem;
padding:1rem;border-radius:0.875rem;
background:var(--vibeui-phoneinput-007-surface);
border:1px solid var(--vibeui-phoneinput-007-surface-border);
font-family:var(--vibeui-phoneinput-007-font);color:var(--vibeui-phoneinput-007-fg);
}
/* Раскладка лежит на оболочке, а не на корне: правило внутри @container
   действует только на потомков контейнера. */
[data-vibeui-block="phoneinput-007"] [data-part="shell"]{
display:flex;flex-direction:column;gap:0.5rem;
}
[data-vibeui-block="phoneinput-007"] [data-part="cell"]{
display:flex;flex-direction:column;gap:0.375rem;min-width:0;
}
[data-vibeui-block="phoneinput-007"] [data-part="cell"][data-role="extension"]{flex:none}
[data-vibeui-block="phoneinput-007"] label{
font-size:0.875rem;font-weight:600;line-height:1.3;cursor:pointer;
}
[data-vibeui-block="phoneinput-007"] [data-part="cell"][data-role="extension"] label{
font-weight:500;color:var(--vibeui-phoneinput-007-muted);
}
[data-vibeui-block="phoneinput-007"] input{
box-sizing:border-box;width:100%;height:2.5rem;padding:0 0.75rem;
font:inherit;font-size:0.9375rem;font-variant-numeric:tabular-nums;
color:var(--vibeui-phoneinput-007-fg);
background:var(--vibeui-phoneinput-007-surface);
border:1px solid var(--vibeui-phoneinput-007-field-border);
border-radius:var(--vibeui-phoneinput-007-radius);
transition:border-color .16s ease,box-shadow .16s ease;
}
[data-vibeui-block="phoneinput-007"] input::placeholder{color:var(--vibeui-phoneinput-007-muted)}
[data-vibeui-block="phoneinput-007"] input:focus-visible{
outline:none;border-color:var(--vibeui-phoneinput-007-accent);
box-shadow:0 0 0 3px color-mix(in oklab,var(--vibeui-phoneinput-007-accent) 22%,transparent);
}
[data-vibeui-block="phoneinput-007"] [data-part="hint"]{
margin:0.5rem 0 0;font-size:0.8125rem;line-height:1.45;
color:var(--vibeui-phoneinput-007-muted);
}
@container (min-width: 20rem){
[data-vibeui-block="phoneinput-007"] [data-part="shell"]{
flex-direction:row;align-items:flex-end;gap:0.5rem;
}
[data-vibeui-block="phoneinput-007"] [data-part="cell"][data-role="extension"]{width:6rem}
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="phoneinput-007"] *{animation:none!important;transition:none!important}}
`

/**
 * Телефон с отдельным полем добавочного: два значения, два поля, разные
 * подсказки автозаполнения. Один файл, ноль зависимостей.
 */
export function Phoneinput007({
  label = "Рабочий телефон",
  extensionLabel = "Добавочный",
  extensionPlaceholder = "1234",
  accent,
  className,
  style,
  ...props
}: Phoneinput007Props) {
  const id = useId()
  const extensionId = `${id}-extension`
  const hintId = `${id}-hint`
  const palette = {
    ...(accent ? { "--vibeui-phoneinput-007-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-phoneinput-007" precedence="medium">
        {STYLES}
      </style>
      <div
        {...props}
        data-vibeui-block="phoneinput-007"
        className={className}
        style={palette}
      >
        <div data-part="shell">
          <div data-part="cell" data-role="number">
            <label htmlFor={id}>{label}</label>
            <input
              id={id}
              name="phone"
              type="tel"
              inputMode="tel"
              autoComplete="tel"
              placeholder="+7 495 123-45-67"
              aria-describedby={hintId}
            />
          </div>
          <div data-part="cell" data-role="extension">
            <label htmlFor={extensionId}>{extensionLabel}</label>
            <input
              id={extensionId}
              name="extension"
              type="text"
              inputMode="numeric"
              autoComplete="tel-extension"
              placeholder={extensionPlaceholder}
            />
          </div>
        </div>
        <p data-part="hint" id={hintId}>
          Добавочный необязателен: без него позвоним на общий номер.
        </p>
      </div>
    </>
  )
}