Phone Input

Format Sample

A phone field with a shown format sample instead of a hard mask: the input accepts any notation while the expected shape stays visible below.

  • phone
  • format
  • hint
  • paste

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-005?lang=en

Например: +44 20 7123 4567

Brackets, dashes and spaces are fine — we normalise on submit.

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-005" (Format Sample) 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-005.json

Registry item: https://vibeui.ru/r/phoneinput-005.json
Installs to: components/vibeui/phoneinput-005.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 shown format sample instead of a hard mask: the input accepts any notation while the expected shape stays visible below.

A phone field with a format sample underneath: input is unrestricted and the expected shape is shown by example. Zero dependencies, one file, no client JS.

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

<Phoneinput005
  label="Contact phone"
  example="+44 20 7123 4567"
  note="Brackets and dashes are fine."
/>

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-005-* palette — do not swap it for your theme tokens
- the absence of pattern and of a mask: strict format checking rejects valid numbers written differently
- the <samp> markup for the sample — it is an example, not a typed value and not a second label
- listing both ids in aria-describedby: otherwise only one line is ever spoken
- the inputMode="tel" and autoComplete="tel" pair: a numeric keypad on phones and working autofill
- the block's own light surface: without it the dark label disappears on a dark background

## 6. You may change
- the field name through label, the sample through example and the format wording through note
- the focus color through accent
- the sample's monospace stack through the --vibeui-phoneinput-005-mono variable
- the placeholder: it is taken from the same sample

## 7. Rules
- The sample must be real for your country: a foreign format in the example confuses more than no example at all.
- Since input is free, normalisation has to happen on the server — otherwise the database collects five shapes of one number.
- The placeholder repeats the sample, so an empty field shows the example twice: set placeholder separately if that bothers you.
- 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-005.json
https://vibeui.ru/r/phoneinput-005.json

Компонент самодостаточен: один файл, без зависимостей, палитра в локальных переменных --vibeui-phoneinput-005-*. Серверный: из хуков только useId, состояния нет. Вместо маски показан образец: пример набран моноширинным начертанием и обёрнут в <samp>, чтобы его не спутали ни с введённым значением, ни с подписью. Поле не ограничивает ввод — скобки, дефисы и пробелы допустимы, поэтому номер из буфера вставляется без борьбы. Обе строки под полем связаны с ним через aria-describedby перечислением идентификаторов, поэтому и образец, и пояснение читаются вслух. Атрибут pattern сознательно не поставлен: он отсекает верные номера, записанные иначе.

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 Phoneinput005Props = Omit<
  ComponentPropsWithoutRef<"input">,
  "children" | "type" | "size"
> & {
  label?: string
  example?: string
  note?: string
  accent?: string
}

// Идея компонента: вместо жёсткой маски — показанный образец. Поле
// принимает что угодно, а под ним стоит пример в моноширинном начертании,
// помеченный <samp>: это образец, а не введённое значение. Так номер из
// буфера с любыми скобками и дефисами вставляется без борьбы с полем,
// а человек всё равно видит, какой вид мы ждём.
const STYLES = `
:where([data-vibeui-block="phoneinput-005"]){
--vibeui-phoneinput-005-surface:oklch(1 0 0);
--vibeui-phoneinput-005-surface-border:oklch(0.91 0.006 265);
--vibeui-phoneinput-005-fg:oklch(0.24 0.016 265);
--vibeui-phoneinput-005-muted:oklch(0.54 0.014 265);
--vibeui-phoneinput-005-sample-bg:oklch(0.96 0.004 265);
--vibeui-phoneinput-005-field-border:oklch(0.85 0.01 265);
--vibeui-phoneinput-005-accent:oklch(0.55 0.2 262);
--vibeui-phoneinput-005-radius:0.625rem;
--vibeui-phoneinput-005-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
--vibeui-phoneinput-005-mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
}
[data-vibeui-block="phoneinput-005"]{
box-sizing:border-box;width:100%;max-width:22rem;
padding:1rem;border-radius:0.875rem;
background:var(--vibeui-phoneinput-005-surface);
border:1px solid var(--vibeui-phoneinput-005-surface-border);
font-family:var(--vibeui-phoneinput-005-font);color:var(--vibeui-phoneinput-005-fg);
display:flex;flex-direction:column;gap:0.375rem;
}
[data-vibeui-block="phoneinput-005"] label{
font-size:0.875rem;font-weight:600;line-height:1.3;cursor:pointer;
}
[data-vibeui-block="phoneinput-005"] 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-005-fg);
background:var(--vibeui-phoneinput-005-surface);
border:1px solid var(--vibeui-phoneinput-005-field-border);
border-radius:var(--vibeui-phoneinput-005-radius);
transition:border-color .16s ease,box-shadow .16s ease;
}
[data-vibeui-block="phoneinput-005"] input::placeholder{color:var(--vibeui-phoneinput-005-muted)}
[data-vibeui-block="phoneinput-005"] input:focus-visible{
outline:none;border-color:var(--vibeui-phoneinput-005-accent);
box-shadow:0 0 0 3px color-mix(in oklab,var(--vibeui-phoneinput-005-accent) 22%,transparent);
}
[data-vibeui-block="phoneinput-005"] [data-part="format"]{
display:flex;flex-wrap:wrap;align-items:center;gap:0.375rem;margin:0;
font-size:0.8125rem;line-height:1.5;color:var(--vibeui-phoneinput-005-muted);
}
/* Образец набран моноширинным и лежит в подложке: так его не спутать с
   уже введённым значением и не прочитать как подпись. */
[data-vibeui-block="phoneinput-005"] samp{
padding:0.0625rem 0.375rem;border-radius:0.375rem;
background:var(--vibeui-phoneinput-005-sample-bg);
font-family:var(--vibeui-phoneinput-005-mono);font-size:0.8125rem;
color:var(--vibeui-phoneinput-005-fg);white-space:nowrap;
}
[data-vibeui-block="phoneinput-005"] [data-part="note"]{
margin:0;font-size:0.8125rem;line-height:1.45;
color:var(--vibeui-phoneinput-005-muted);
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="phoneinput-005"] *{animation:none!important;transition:none!important}}
`

/**
 * Телефон с показанным образцом формата вместо жёсткой маски: поле
 * принимает любой вид записи. Один файл, ноль зависимостей.
 */
export function Phoneinput005({
  label = "Телефон для связи",
  example = "+7 999 123-45-67",
  note = "Скобки, дефисы и пробелы можно оставить — приведём к одному виду при отправке.",
  accent,
  className,
  style,
  ...props
}: Phoneinput005Props) {
  const id = useId()
  const formatId = `${id}-format`
  const noteId = `${id}-note`
  const palette = {
    ...(accent ? { "--vibeui-phoneinput-005-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-phoneinput-005" precedence="medium">
        {STYLES}
      </style>
      <div
        data-vibeui-block="phoneinput-005"
        className={className}
        style={palette}
      >
        <label htmlFor={id}>{label}</label>
        <input
          {...props}
          id={id}
          name="phone"
          type="tel"
          inputMode="tel"
          autoComplete="tel"
          placeholder={example}
          aria-describedby={`${formatId} ${noteId}`}
        />
        <p data-part="format" id={formatId}>
          Например: <samp>{example}</samp>
        </p>
        <p data-part="note" id={noteId}>
          {note}
        </p>
      </div>
    </>
  )
}