Babelize Elements
Campos de entrada

Entrada de teléfono

Campo internacional para números de teléfono con selector de país, emojis de bandera y búsqueda de prefijos telefónicos. Admite 50 países con formato y validación integrados.

Instalación

npx shadcn@latest add @babelize/phone-input

Uso

"use client";

import { useState } from "react";
import { PhoneInput } from "@/components/ui/phone-input";

export function PhoneField() {
  const [phone, setPhone] = useState("");
  const [countryCode, setCountryCode] = useState("US");

  return (
    <PhoneInput
      value={phone} // omit for uncontrolled
      country={countryCode} // omit for uncontrolled (or use defaultCountry)
      onValueChange={(value, country) => {
        setPhone(value);
        console.log(country.dialCode);
      }}
      onCountryChange={(country) => setCountryCode(country.code)}
      placeholder="Enter phone number"
    />
  );
}

Control del país

El país sigue la misma convención controlada/no controlada que el número: pasa country + onCountryChange para controlarlo, o defaultCountry para definir el valor predeterminado no controlado. Cuando country es controlado, el padre tiene la última palabra; usa onCountryChange si quieres que la detección del prefijo (abajo) cambie de país.

Detección del prefijo telefónico

Escribe o pega un número que empiece por + y el componente cambiará el país para que coincida, eliminando del campo el prefijo y el separador que lo sigue. El prefijo que declara + tiene prioridad sobre un país seleccionado manualmente:

  • Al pegar +44 20 7946 0958 se selecciona GB y 20 7946 0958 se mantiene en el campo.
  • Un + escrito delante de dígitos que ya están en el campo nunca los elimina: el prefijo solo se consume cuando sus dígitos se introdujeron después del +. Escribe +1 delante de un número existente y el país cambiará a US cuando aparezca el 1, y el número conservará todos los dígitos que tenía.
  • Gana el prefijo más largo que coincida, así que +880… selecciona Bangladés aunque un prefijo más corto también coincida.
  • Los prefijos compartidos mantienen el país seleccionado actualmente cuando es uno de los países que comparten el prefijo coincidente; de lo contrario, eligen el primero de la lista ofrecida, así que +1 nunca salta lejos de US o CA mientras escribes. La misma regla se aplica a cualquier prefijo compartido en una lista personalizada de countries, como +7 si ofreces tanto RU como KZ.
  • Un prefijo que no está en tu lista de countries se ignora: el país solo cambia a un país que realmente ofreces.
  • Tras un cambio, el menú de países sigue funcionando con normalidad: elegir un país sustituye al detectado, y no se vuelve a detectar nada hasta la próxima vez que el valor empiece por +.

Props

PropTipoObligatorioDescripción
valuestringNoNúmero de teléfono: pásalo para controlar el componente
defaultValuestringNoNúmero de teléfono inicial cuando no está controlado
onValueChange(phone: string, country: Country) => voidNoCallback que se ejecuta cuando cambia el número de teléfono o el país
countrystringNoCódigo de país seleccionado (ISO 3166-1 alpha-2): pásalo para controlar el país
onCountryChange(country: Country) => voidNoCallback que se ejecuta cuando cambia el país seleccionado, ya sea desde el menú o por la detección del prefijo
defaultCountrystringNoCódigo de país inicial cuando no está controlado (ISO 3166-1 alpha-2, p. ej. "US", "GB", "IN"; predeterminado: "US")
countriesCountry[]NoPaíses ofrecidos, en orden de visualización (predeterminado: COUNTRIES)
placeholderstringNoTexto de marcador de posición
disabledbooleanNoDesactiva el campo
showFlagsbooleanNoMuestra la bandera del país junto al prefijo telefónico (predeterminado: true)
size"sm" | "default" | "lg"NoAltura del campo (predeterminado: "default")
align"start" | "end"NoBorde del campo con el que se alinea el menú (predeterminado: "start")
side"top" | "bottom"NoLado preferido del menú; cambia de lado si no hay espacio (predeterminado: "bottom")
searchPlaceholderstringNoMarcador de posición para la búsqueda de países (predeterminado: "Search countries...")
emptyMessageReactNodeNoSe muestra cuando la búsqueda no coincide con nada (predeterminado: "No countries found.")
labelsPartial<PhoneInputLabels>NoSustituye el texto de interfaz en inglés integrado (consulta más abajo)
displayLocalestringNoConfiguración regional para los nombres de países, p. ej. "fr" (predeterminado: nombres en inglés)
maxHeightnumber | stringNoAltura máxima de la lista de países con desplazamiento (predeterminado: "15rem")
labelstringNoEtiqueta accesible para el campo
classNamestringNoClases para el contenedor raíz
classNamesPhoneInputClassNamesNoClases por parte: root, field, trigger, input, content, search, list, item, empty

Cualquier otra prop — name, required, autoComplete, aria-invalid, data-* — se pasa al <input> subyacente, y la ref se reenvía a él, así que el componente funciona dentro de un formulario HTML normal. Si estableces aria-invalid, todo el campo se vuelve rojo, igual que Input de shadcn.

Objeto de país

PropiedadTipoDescripción
codestringCódigo ISO 3166-1 alpha-2 (p. ej. "US", "GB", "IN")
dialCodestringPrefijo telefónico (p. ej. "+1", "+44", "+91")
namestringNombre del país
flagstringEmoji de bandera

Ejemplos

Con el SDK de Babelize

Combina la entrada de teléfono con el SDK de Babelize para validar según la configuración regional:

"use client";

import { useEffect, useState } from "react";
import { PhoneInput } from "@/components/ui/phone-input";
import { useBabelize } from "@babelize/sdk/react";

export function ContactPhone() {
  const { locale } = useBabelize();
  const [phone, setPhone] = useState("");
  const [countryCode, setCountryCode] = useState("US");

  // The locale picks the country — until the user overrides it through the
  // menu or by pasting a `+` number, which `onCountryChange` must honour.
  useEffect(() => {
    setCountryCode(locale === "en" ? "US" : locale === "de" ? "DE" : "GB");
  }, [locale]);

  return (
    <PhoneInput
      value={phone}
      country={countryCode}
      onCountryChange={(country) => setCountryCode(country.code)}
      onValueChange={(value, country) => {
        setPhone(value);
        // Validate against locale-specific rules
      }}
      placeholder="Enter phone number"
    />
  );
}

Con validación de formularios

"use client";

import { useState } from "react";
import { PhoneInput } from "@/components/ui/phone-input";

export default function CheckoutForm() {
  const [phone, setPhone] = useState("");
  const [error, setError] = useState("");

  const validate = (value: string) => {
    if (value.length < 10) {
      setError("Phone number is too short");
    } else {
      setError("");
    }
  };

  return (
    <div>
      <PhoneInput
        value={phone}
        onValueChange={(value) => {
          setPhone(value);
          validate(value);
        }}
        defaultCountry="US"
        placeholder="Enter phone number"
        aria-invalid={error ? true : undefined}
      />
      {error && <p className="mt-1 text-sm text-destructive">{error}</p>}
    </div>
  );
}

Con filtro de países

"use client";

import { COUNTRIES, PhoneInput } from "@/components/ui/phone-input";

// Only the markets you ship to, in the order you want them listed.
const SUPPORTED = ["US", "GB", "CA", "AU"].map((code) =>
  COUNTRIES.find((country) => country.code === code)!,
);

export default function App() {
  return (
    <PhoneInput
      countries={SUPPORTED}
      defaultCountry="US"
      onValueChange={(phone, country) => console.log(phone, country)}
    />
  );
}

Personalizar partes

Cada parte admite sus propias clases mediante classNames, que se combinan después de los valores predeterminados:

<PhoneInput
  size="lg"
  align="end"
  classNames={{
    field: "rounded-full",
    trigger: "ps-4",
    content: "w-80",
    item: "py-2",
  }}
/>

Texto de interfaz traducido

Pasa labels para traducir el texto del menú de países, y displayLocale para mostrar los nombres de países en otro idioma. Los nombres proceden de Intl.DisplayNames del navegador, por lo que su redacción exacta puede variar ligeramente entre navegadores. La búsqueda coincide con el nombre traducido, mientras que onValueChange sigue devolviendo el name en inglés del país. Las props searchPlaceholder y emptyMessage siguen funcionando, y tienen prioridad sobre labels cuando se definen ambas.

"use client";

import { PhoneInput } from "@/components/ui/phone-input";

export default function App() {
  return (
    <PhoneInput
      defaultCountry="FR"
      displayLocale="fr"
      label="Numéro de téléphone"
      placeholder="Numéro de téléphone"
      labels={{
        trigger: "Choisir un pays",
        searchPlaceholder: "Rechercher un pays…",
        searchLabel: "Rechercher un pays",
        clearSearch: "Effacer la recherche",
        emptyMessage: "Aucun pays trouvé.",
        listbox: "Choisir un pays",
      }}
    />
  );
}
ClavePredeterminadoSe usa para
trigger"Select country"Etiqueta del disparador de país
searchPlaceholder"Search countries..."Marcador de posición del campo de búsqueda
searchLabel"Search countries"Etiqueta accesible del campo de búsqueda
clearSearch"Clear search"Etiqueta accesible del botón de borrar
emptyMessage"No countries found."Se muestra cuando nada coincide
listbox"Select country"Etiqueta accesible de la lista de países

Soporte de teclado

El menú de países sigue el patrón combobox de WAI-ARIA: el foco permanece en el campo de búsqueda, que se expone como role="combobox", y el país resaltado se anuncia mediante aria-activedescendant.

ClaveAcción
Enter, Space o clicAbre el menú de países desde el disparador de bandera
ArrowDown / ArrowUp (cerrado)Abre con el primer / último país resaltado
ArrowDown / ArrowUpMueve el resaltado, dando la vuelta en ambos extremos
Home / EndResalta el primer / último país
EnterSelecciona el país resaltado; el foco pasa al campo de número
EscapeCierra el menú y devuelve el foco al disparador de bandera
EscrituraFiltra por nombre, código o prefijo telefónico; el resaltado vuelve al principio

El resaltado se mantiene en la fila en la que apareció al abrir el menú: la lista se desplaza por debajo, como un menú nativo, y la página nunca se desplaza. Pasa maxHeight para cambiar la altura de la lista de países antes de que empiece a desplazarse.

Características

  • 50 países — precargados con prefijos, banderas y nombres
  • Búsqueda en el desplegable — filtra países por nombre o prefijo telefónico
  • Emojis de bandera — detectados automáticamente a partir del código de país, con un interruptor showFlags
  • Accesible con teclado — navegación con flechas, Inicio / Fin, Intro y Escape
  • Etiquetas ARIA — campo de búsqueda role="combobox" con aria-activedescendant, además de aria-expanded, aria-haspopup y aria-selected
  • Personalizable con temas — con estilos basados en los tokens de shadcn/ui (border-input, bg-popover, ring-ring), así que sigue tu tema y el modo oscuro
  • Menú animado — aparece y desaparece con motion, respeta la configuración de movimiento reducido y cambia hacia arriba cuando no hay espacio debajo
  • Controlado/no controlado — funciona con o sin value y country
  • Detección del prefijo telefónico — al escribir o pegar +44… el país cambia a GB y se elimina el prefijo; los prefijos compartidos (+1, +7) mantienen la selección actual

Países compatibles

El campo incluye 50 países precargados. Usa la búsqueda del desplegable para encontrar cualquier país por nombre o prefijo telefónico. La lista completa incluye:

PaísCódigoPrefijo telefónicoBandera
Estados UnidosUS+1🇺🇸
Reino UnidoGB+44🇬🇧
CanadáCA+1🇨🇦
AustraliaAU+61🇦🇺
IndiaIN+91🇮🇳
AlemaniaDE+49🇩🇪
FranciaFR+33🇫🇷
JapónJP+81🇯🇵
BrasilBR+55🇧🇷
Corea del SurKR+82🇰🇷
...y 40 más mediante la búsqueda del desplegable

Dependencias

Este componente usa:

  • clsx y tailwind-merge (incluidos como dependencias)
  • lucide-react para los iconos y motion para la animación del menú

Dependencias de pares:

  • react >= 18
  • react-dom >= 18
  • tailwindcss >= 3

Créditos

Creado por sapatmohit.

En esta página