⭐ Examples
All examples assume Vue 3 + <script setup>
and that you’ve installed:
import "vue-tel-num-input/css";
import "vue-tel-num-input/flags.css";
v-model returns a typed object (TelInputModel
), not a plain string. Initialize with {} (component will populate it).
- Minimal usage
<template>
<VueTelNumInput v-model="model" />
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
Why: You get both the phone value and UI metadata (iso, code, name, etc.). Anti-pattern: Binding
string
directly — you lose metadata and internal state.
- Set default country + international format
<template>
<VueTelNumInput
v-model="model"
default-country-code="US"
:international="true"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
- National format (no leading +XX)
<template>
<VueTelNumInput
v-model="model"
:international="false"
default-country-code="GB"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
- Restrict visible countries
<template>
<VueTelNumInput
v-model="model"
:country-codes="['US', 'CA', 'GB', 'SK', 'PL']"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
Prefer allowlist (country-codes
) for clarity. Alternative: exclude-country-codes
to hide a few.
- Native country names + CDN flags
<template>
<VueTelNumInput
v-model="model"
display-name="native"
:flag="{ strategy: 'cdn', baseUrl: 'https://your-cdn.example/flags' }"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
Notes:
- CDN must be CORS-safe; otherwise use
emoji
or bundledsprite
.
- Placeholders by locale
<template>
<VueTelNumInput
v-model="model"
:placeholder="{ en: 'Phone number', sk: 'Telefónne číslo' }"
locale="sk"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
- Search UI localization
<template>
<VueTelNumInput
v-model="model"
:search="{ placeholder: { en: 'Search…', sk: 'Hľadať…' }, locale: 'sk' }"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
- Input behavior: clear/focus/lock/maxlength
<template>
<VueTelNumInput
v-model="model"
:input="{
clearOnCountrySelect: true,
focusAfterCountrySelect: true,
lockCountryCode: true,
formatterEnabled: true,
maxLength: 18,
}"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
Caveat:
formatterEnabled
uses libphonenumber’sAsYouType
.
- Programmatic control via ref
<template>
<VueTelNumInput ref="telRef" v-model="model" />
<div class="mt-2">
<button @click="open">Open dropdown</button>
<button @click="format">Format now</button>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, {
type TelInputInitModel,
type VueTelNumInputExpose,
} from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
const telRef = ref<VueTelNumInputExpose | null>(null);
const open = () => telRef.value?.switchDropdown(true);
const format = () => telRef.value?.formatNow();
</script>
- Auto-detect user country (best effort)
<template>
<VueTelNumInput
v-model="model"
:auto-detect-country="true"
default-country-code="US"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
Tip: Always keep default-country-code as a fallback.
- Validate field and show state
<template>
<div>
<VueTelNumInput v-model="model" :input="{ required: true }" />
<p :style="{ color: model.valid ? 'green' : 'crimson' }">
{{ model.valid ? "Valid number" : "Invalid or empty" }}
</p>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
// Optional: react to every change
watch(
model,
(m) => {
// m.value is the actual string; m.iso/m.code/m.name available
},
{ deep: true }
);
</script>
- Customize prefix button via slots
<template>
<VueTelNumInput v-model="model">
<template #prefix:flag>
<img
:src="`/my-flags/${model.iso}.svg`"
alt=""
style="width: 16px; height: 12px"
/>
</template>
<template #prefix:code>
<span class="text-muted">Dial {{ model.code }}</span>
</template>
<template #prefix:chevron>
<svg width="12" height="12" viewBox="0 0 16 16">
<path d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647..." />
</svg>
</template>
</VueTelNumInput>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
- Replace the input entirely (bring your own mask)
<template>
<VueTelNumInput v-model="model">
<template #input>
<input
:value="model.value"
@input="onInput"
:placeholder="`Number for ${model.name} (${model.code})`"
/>
</template>
</VueTelNumInput>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
// Keep model in sync when replacing the input
function onInput(e: Event) {
const v = (e.target as HTMLInputElement).value;
model.value = { ...model.value, value: v };
}
</script>
- Custom search bar (icon/input slots)
<template>
<VueTelNumInput v-model="model" :search="{ autoFocus: true }">
<template #search:icon>
<span style="margin-left:12px">🔎</span>
</template>
<template #search:input>
<input
v-if="!searchHidden"
v-model="model.search"
placeholder="Type a country or code…"
/>
</template>
</VueTelNumInput>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
const searchHidden = computed(() => false);
</script>
- Customize list rows (flag/code/name slots)
<template>
<VueTelNumInput v-model="model">
<template #item:before>
<span>•</span>
</template>
<template #item:code="{ model }">
<span class="muted">{{ model.code }}</span>
</template>
<template #item:countryName="{ model }">
<strong>{{ model.name }}</strong>
</template>
</VueTelNumInput>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
If your slot expects extra context (e.g. the row’s
data
), ensure your component forwards it; otherwise usemodel
fromv-model
as shown.
- Control dropdown size
<template>
<VueTelNumInput
v-model="model"
:list="{ itemsPerView: 8 }"
:item-height="48"
size="xl"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
- Disable the list / prefix
<template>
<VueTelNumInput
v-model="model"
:list="{ hidden: true }"
:prefix="{ hidden: true }"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
Useful for embedding the input alone in custom forms/dialogs.
- Form example (required + submit guard)
<template>
<form @submit.prevent="submit">
<VueTelNumInput v-model="model" :input="{ required: true }" />
<button type="submit">Submit</button>
<p v-if="error" style="color:crimson">{{ error }}</p>
</form>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
const error = ref("");
function submit() {
if (!model.value.valid) {
error.value = "Please enter a valid phone number.";
return;
}
error.value = "";
// send: model.value.value (string), plus metadata: iso/code/name
}
</script>
- Styling via CSS variables
<template>
<div
style="
--tel-input-height: 44px;
--tel-input-border-radius: 10px;
--tel-input-font-size: 15px;
--tel-input-prefix-gap: 10px;
--tel-input-input-width: 260px;
"
>
<VueTelNumInput v-model="model" />
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
- Detect country on demand (exposed API)
<template>
<VueTelNumInput ref="telRef" v-model="model" />
<button @click="detect">Detect country</button>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, {
type TelInputInitModel,
type VueTelNumInputExpose,
} from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
const telRef = ref<VueTelNumInputExpose | null>(null);
const detect = async () => {
const iso = await telRef.value?.requestUserCountry();
console.log("Detected:", iso);
};
</script>
- Disable built-in sizing and style from scratch
<template>
<div class="my-tel-wrap">
<VueTelNumInput v-model="model" :disable-sizing="true" />
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
<style scoped>
.my-tel-wrap :deep(.tel-num-input__head) {
border: 1px solid #000;
border-radius: 0;
background: #fffbe6;
}
</style>
- Toggle animation
<template>
<VueTelNumInput v-model="model" animation-name="fade" />
</template>
<script setup lang="ts">
import { ref } from "vue";
import VueTelNumInput, { type TelInputInitModel } from "vue-tel-num-input";
const model = ref<TelInputInitModel>({});
</script>
You can also register your own
<Transition name="...">
CSS.
- Accessibility hints
- The search input auto-focuses when opening the dropdown (
search.autoFocus
). - Ensure your custom slots maintain focus order and click targets.
- Provide visible focus ring overrides with CSS variables if your design removes outlines.