Phone Field

PreviousNext

A form input for collecting phone numbers with automatic formatting, pattern masking, and optional country code selection.

'use client';

import { PhoneField } from '@/ui/forms/PhoneField';

export function PhoneFieldDemo() {
  return (
    <div className="flex flex-col gap-16">
      <PhoneField
        label="Phone Number"
        description="Enter your phone number"
        placeholder="+33 1 23 45 67 89"
        mode="full"
        className="max-w-sm"
        pattern="+33 # ## ## ## ##"
      />
      <PhoneField
        label="Phone Number"
        description="Select your code and number"
        placeholder="1 23 45 67 89"
        mode="split"
        className="max-w-sm"
        pattern="# ## ## ## ##"
      />
    </div>
  );
}

Installation

npx shadcn@latest add https://develop.trident-ui.pro.clubmed/r/phone-field.json

Usage

Full Mode

Single input field with pattern-based formatting for consistent phone number display.

'use client';
 
import { useState } from 'react';
import { PhoneField } from '@/docs/lib/ui/forms/phone-field';
 
export function Component() {
  const [phone, setPhone] = useState<PhoneValue | null>(null);
 
  return (
    <PhoneField
      label="Phone Number"
      description="We'll send a verification code to this number"
      mode="full"
      pattern="## ## ## ## ##"
      placeholder="Enter phone number"
      onChange={(name, value) => setPhone(value)}
      required
    />
  );
}

Split Mode

Separate prefix selector and number input for international phone numbers.

'use client';
 
import { useState } from 'react';
import { PhoneField } from '@/docs/lib/ui/forms/phone-field';
 
export function Component() {
  const [phone, setPhone] = useState<PhoneValue | null>(null);
 
  return (
    <PhoneField
      label="International Phone"
      mode="split"
      defaultPrefix="+1"
      pattern="## ## ## ## ##"
      placeholder="Enter phone number"
      onChange={(name, value) => {
        // value.full: "+1 12 34 56 78 90"
        // value.prefix: "+1"
        // value.number: "12 34 56 78 90"
        // value.raw: "11234567890" (for backend)
        setPhone(value);
      }}
    />
  );
}

API Reference

PhoneField

PropTypeDefaultDescription
mode"full" | "split""full"Display mode. "full" shows single input with pattern. "split" shows prefix dropdown + number input.
patternstring"## ## ## ## ##"Format pattern where # represents digit placeholder. Example: "+1 (###) ###-####" or "## ## ## ## ##".
prefixesPhonePrefix[]DEFAULT_PREFIXESArray of country code prefixes for split mode. Only used when mode="split".
defaultPrefixstringFirst prefix in arrayDefault selected country code in split mode. Must match one of the prefixes values.
labelstringundefinedThe label text displayed above the input field.
descriptionstringundefinedHelper text displayed below the label to provide additional context.
placeholderstring""Placeholder text shown when the input is empty.
idstringAuto-generatedThe unique identifier for the input field. Auto-generated if not provided.
namestringSame as idThe name attribute for the input field, used in form submissions. Defaults to the id value.
valuePhoneValueundefinedThe current phone value. See PhoneValue type below.
onChange(name: string, value: PhoneValue) => voidundefinedCallback function called when the phone number changes. Receives the field name and a PhoneValue object.
iconIconicNamesundefinedOptional icon name from @clubmed/trident-icons to display on the left side of the input (full mode only).
iconTypeIconicTypesundefinedOptional icon type variant for the icon.
disabledbooleanfalseWhether the field is disabled. Disabled fields have reduced opacity and cannot be interacted with.
requiredbooleanfalseWhether the field is required. Displays a red asterisk next to the label.
hideRequiredStarbooleanfalseWhen true, hides the required asterisk even if the field is required.
validationStatus"default" | "error" | "success""default"The validation state of the field. Changes border color and shows status icons.
errorMessagestringundefinedError message text displayed when validationStatus is "error".
dataTestIdstring"PhoneField"Custom test ID for the component wrapper, useful for testing.
classNamestringundefinedAdditional CSS classes for the component wrapper.

The component also accepts all standard HTML input attributes via spreading (...rest).

PhoneValue Type

The value and onChange props work with a structured PhoneValue object:

interface PhoneValue {
  full: string;      // Complete formatted number: "+33 12 34 56 78"
  prefix?: string;   // Country code (split mode only): "+33"
  number?: string;   // Formatted number (split mode only): "12 34 56 78"
  raw: string;       // Digits only, for backend: "33123456789"
}

PhonePrefix Type

When using split mode with custom prefixes:

interface PhonePrefix {
  code: string;      // e.g., "+33"
  label?: string;    // e.g., "France (+33)"
  country?: string;  // e.g., "FR" (ISO country code)
}

Notes

Pattern Formatting

The component formats phone numbers in real-time as users type. The pattern prop uses # as a placeholder for digits, with other characters treated as literals:

  • "## ## ## ## ##"12 34 56 78 90
  • "+1 (###) ###-####"+1 (555) 123-4567
  • "(###) ###-####"(555) 123-4567

The component automatically:

  • Extracts only digits from user input
  • Applies the pattern format
  • Maintains cursor position while typing
  • Prevents input exceeding pattern length

Mode Comparison

Full Mode (mode="full"):

  • Single input field with pattern-based formatting
  • Best for national phone numbers with consistent format
  • Simpler UI for users in single country
  • Example: 12 34 56 78 90

Split Mode (mode="split"):

  • Separate prefix dropdown and number input
  • Best for international phone numbers
  • Users can select country code from dropdown
  • Example: +33 (dropdown) + 12 34 56 78 (input)

Default Prefixes

The component includes 24 common country code prefixes by default:

  • US/Canada (+1), UK (+44), France (+33), Germany (+49), Italy (+39)
  • Spain (+34), China (+86), India (+91), Japan (+81), South Korea (+82)
  • Russia (+7), Australia (+61), Brazil (+55), Mexico (+52), Netherlands (+31)
  • Sweden (+46), Switzerland (+41), Belgium (+32), Poland (+48), Portugal (+351)
  • Greece (+30), Norway (+47), Denmark (+45), Finland (+358)

You can override these by providing a custom prefixes array.

Value Structure

The component returns a rich PhoneValue object with multiple formats:

  • full: Complete formatted number for display
  • raw: Digits only (no formatting) - use this for API submissions
  • prefix (split mode): Selected country code
  • number (split mode): Formatted number without prefix

This structure provides flexibility for different use cases while maintaining a clean API.

Validation States

The component supports three validation states via the validationStatus prop:

  • "default": Normal state with light gray border
  • "error": Red border with cross icon, displays errorMessage if provided
  • "success": Green border with check icon

The status icons appear on the right side of the input (or on the number input in split mode).

Form Control Integration

PhoneField wraps the input in a FormControl component that provides consistent styling for labels, descriptions, error messages, and validation states across all form fields.

Accessibility

  • The input uses type="tel" which triggers numeric keyboards on mobile devices
  • The component automatically associates the label with the input using the id attribute
  • In split mode, both the prefix dropdown and number input have appropriate aria-label attributes
  • Disabled fields have appropriate visual feedback with reduced opacity
  • Required fields are indicated with a red asterisk (unless hideRequiredStar is true)
  • Status icons (check/cross) provide visual validation feedback

Cursor Position Management

The component intelligently manages cursor position during formatting. When you type digits and the component adds spaces or special characters, the cursor stays at the correct position relative to the digits you entered, not jumping to the end of the input.

Client Component

This component uses React hooks (useState, useEffect, useRef, useId) and must be used in a client component. Add "use client" at the top of files that use this component.

Examples

US Phone Number

<PhoneField
  label="Phone Number"
  mode="full"
  pattern="+1 (###) ###-####"
  placeholder="Enter US phone number"
/>

French Phone Number

<PhoneField
  label="Numéro de téléphone"
  mode="full"
  pattern="+33 ## ## ## ## ##"
  placeholder="Entrez votre numéro"
/>

International with Custom Prefixes

<PhoneField
  label="Contact Number"
  mode="split"
  prefixes={[
    { code: '+1', label: 'US/Canada (+1)', country: 'US' },
    { code: '+33', label: 'France (+33)', country: 'FR' },
    { code: '+44', label: 'UK (+44)', country: 'GB' },
  ]}
  defaultPrefix="+33"
  pattern="## ## ## ## ##"
/>

With Validation

<PhoneField
  label="Phone Number"
  mode="full"
  pattern="## ## ## ## ##"
  validationStatus="error"
  errorMessage="Please enter a valid phone number"
/>

With Icon

<PhoneField
  label="Phone Number"
  mode="full"
  pattern="## ## ## ## ##"
  icon="CalendarDefault"
/>