All components
Phone Input
inputsUi-Layouts component.
responsive · 380px
Install
Same command in any shadcn project — React (Vite/CRA), Next.js, Remix, Astro, and more:
$
npx shadcn@latest add https://your-domain/r/phone-input.jsonUsage
'use client';
import { useState } from "react";
import { PhoneInput } from "@/registry/ui-layouts/phone-input";
export default function Demo() {
const [value, setValue] = useState("");
return (
<div className="flex items-center justify-center p-8">
<div className="w-80 border-2 rounded-lg overflow-hidden dark:bg-neutral-950 bg-neutral-50">
<PhoneInput
placeholder="Enter phone number"
value={value}
onChange={(val) => setValue(val ?? "")}
defaultCountry="US"
/>
</div>
</div>
);
}Component source
'use client';
import { Button } from '@/registry/ui-layouts/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/registry/ui-layouts/command';
import { Popover, PopoverContent, PopoverTrigger } from '@/registry/ui-layouts/popover';
import { ScrollArea } from '@/registry/ui-layouts/scroll-area';
import { cn } from '@/lib/utils';
import { CheckIcon, ChevronsUpDown } from 'lucide-react';
import * as React from 'react';
import * as RPNInput from 'react-phone-number-input';
import flags from 'react-phone-number-input/flags';
type PhoneInputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value'> &
Omit<RPNInput.Props<typeof RPNInput.default>, 'onChange'> & {
onChange?: (value: RPNInput.Value) => void;
};
const PhoneInput: React.ForwardRefExoticComponent<PhoneInputProps> = React.forwardRef<
React.ElementRef<typeof RPNInput.default>,
PhoneInputProps
>(({ className, onChange, ...props }, ref) => {
return (
<RPNInput.default
ref={ref}
className={cn('flex', className)}
flagComponent={FlagComponent}
countrySelectComponent={CountrySelect}
inputComponent={InputComponent}
/**
* Handles the onChange event.
*
* react-phone-number-input might trigger the onChange event as undefined
* when a valid phone number is not entered. To prevent this,
* the value is coerced to an empty string.
*
* @param {E164Number | undefined} value - The entered value
*/
// @ts-expect-error
onChange={(value) => onChange?.(value || '')}
{...props}
/>
);
});
PhoneInput.displayName = 'PhoneInput';
const InputComponent = React.forwardRef<HTMLInputElement, any>(({ className, ...props }, ref) => (
<input
className={cn(
'rounded-e-lg rounded-s-none px-2 bg-primary-base outline-hidden w-full ',
className
)}
{...props}
ref={ref}
/>
));
InputComponent.displayName = 'InputComponent';
type CountrySelectOption = { label: string; value: RPNInput.Country };
type CountrySelectProps = {
disabled?: boolean;
value: RPNInput.Country;
onChange: (value: RPNInput.Country) => void;
options: CountrySelectOption[];
};
const CountrySelect = ({ disabled, value, onChange, options }: CountrySelectProps) => {
const handleSelect = React.useCallback(
(country: RPNInput.Country) => {
onChange(country);
},
[onChange]
);
return (
<Popover>
<PopoverTrigger asChild>
<Button
type='button'
variant={'outline'}
className={cn('flex gap-1 rounded-e-none bg-primary-base rounded-s-lg px-3')}
disabled={disabled}
>
<FlagComponent country={value} countryName={value} />
<ChevronsUpDown
className={cn('-mr-2 h-4 w-4 opacity-50', disabled ? 'hidden' : 'opacity-100')}
/>
</Button>
</PopoverTrigger>
<PopoverContent className='w-[300px] p-0'>
<Command className='dark:bg-neutral-950 border dark:border-neutral-800 border-neutral-200'>
<CommandList>
<ScrollArea className='h-72'>
<CommandInput placeholder='Search country...' />
<CommandEmpty>No country found.</CommandEmpty>
<CommandGroup>
{options
.filter((x) => x.value)
.map((option) => (
<CommandItem
className='gap-2'
key={option.value}
onSelect={() => handleSelect(option.value)}
>
<FlagComponent country={option.value} countryName={option.label} />
<span className='flex-1 text-sm'>{option.label}</span>
{option.value && (
<span className='text-foreground/50 text-sm'>
{`+${RPNInput.getCountryCallingCode(option.value)}`}
</span>
)}
<CheckIcon
className={cn(
'ml-auto h-4 w-4',
option.value === value ? 'opacity-100' : 'opacity-0'
)}
/>
</CommandItem>
))}
</CommandGroup>
</ScrollArea>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
};
const FlagComponent = ({ country, countryName }: RPNInput.FlagProps) => {
const Flag = flags[country];
return (
<span className='bg-foreground/20 flex h-4 w-6 overflow-hidden rounded-xs'>
{Flag && <Flag title={countryName} />}
</span>
);
};
FlagComponent.displayName = 'FlagComponent';
export { PhoneInput };Dependencies
lucide-reactreact-phone-number-inputzod
Source: Ui-Layouts