vue-a11y-phone
v1.1.1
Published
Accessible international phone input for Vue 3 with country selector, keyboard navigation, and WCAG 2.2 AA compliance
Downloads
84
Maintainers
Readme
vue-a11y-phone
Accessible international phone input for Vue 3. WCAG 2.2 AA, keyboard-first, screen-reader friendly.
- 9 KB JS + 1 KB CSS gzipped
- Zero runtime dependencies
- Vue 3.4+, peer-only
Install
npm install vue-a11y-phoneUsage
<template>
<Vue3PhoneInput v-model="phone" default-country="se" :preferred-countries="['se', 'no', 'dk']" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Vue3PhoneInput } from 'vue-a11y-phone';
import 'vue-a11y-phone/vue-a11y-phone.css';
const phone = ref('');
</script>Global registration
import { createApp } from 'vue';
import VueA11yPhone from 'vue-a11y-phone';
import 'vue-a11y-phone/vue-a11y-phone.css';
const app = createApp(App);
app.use(VueA11yPhone);Then use <vue3-phone-input> in any template.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| modelValue | string | '' | Phone number (full international format with dial code) |
| defaultCountry | string | 'us' | Default country ISO2 code (lowercase) |
| preferredCountries | string[] | [] | Countries shown at the top of the dropdown |
| onlyCountries | string[] | [] | Only show these countries |
| ignoredCountries | string[] | [] | Hide these countries from the dropdown |
| disabled | boolean | false | Disable the input and dropdown |
| inputId | string | — | Native input id attribute |
| name | string | 'phone' | Native input name attribute |
| required | boolean | false | Native input required attribute |
| placeholder | string | — | Native input placeholder |
| autocomplete | string | 'tel' | Native input autocomplete attribute |
| invalid | boolean | false | Marks the input as invalid (styling + aria-invalid) |
| ariaDescribedby | string | — | Additional aria-describedby ID(s) for the input |
| inputLabel | string | 'Phone number' | aria-label for the phone input |
| countryLabel | string | 'Select country code' | aria-label for the country selector button |
| searchPlaceholder | string | 'Search country...' | Placeholder and aria-label for the country search |
| searchResultsLabel | (count: number) => string | — | Custom function for screen reader search result announcements |
| formatDisplay | (digits, iso2) => string | — | Custom display formatter. Overrides built-in national formatting. Model value stays unformatted |
| useNationalFormat | boolean | true | Apply built-in country-aware grouping (e.g. 070 123 45 67 for SE) when no formatDisplay is provided |
| stripLeadingZero | boolean | true | Strip a single leading 0 from typed input (common in countries with a trunk prefix) |
| showNativeNames | boolean | false | Show each country's native name next to the English name in the dropdown |
| maxLength | number | per-country | Max digits in the national number. Defaults to the per-country max (or 15) |
| styleClasses | string \| string[] \| Record<string, boolean> | — | Custom CSS classes for the wrapper element |
Events
| Event | Payload | Description |
|-------|---------|-------------|
| update:modelValue | string | Phone number changed (full international format) |
| update:valid | boolean | Validity changed (length within country's min/max). Use with v-model:valid |
| country-changed | Country | Selected country changed |
| open | — | Dropdown opened |
| close | — | Dropdown closed |
| focus | FocusEvent | Phone input received focus |
| blur | FocusEvent | Phone input lost focus |
Exposed methods
const phoneRef = ref<InstanceType<typeof Vue3PhoneInput>>();
phoneRef.value?.focus();
phoneRef.value?.blur();
phoneRef.value?.validate(); // boolean — current validityHelpers
The package also exports utilities for working with phone numbers outside the component:
import {
allCountries,
formatNational,
formatSwedishPhone,
getCountryMeta,
getNativeName,
isValidNationalNumber
} from 'vue-a11y-phone';
formatNational('se', '701234567'); // → "70 123 45 67"
formatNational('us', '5551234567'); // → "555 123 4567"
isValidNationalNumber('se', '7012345'); // → true (7+ digits)
getCountryMeta('gb'); // → { minLength, maxLength, groups }
getNativeName('de'); // → "Deutschland"Theming
All styles use CSS custom properties. Override any --v3pi-* variable to customize:
.v3pi {
--v3pi-border-color: #ced4da;
--v3pi-border-radius: 0.375rem;
--v3pi-bg: #ffffff;
--v3pi-font-family: inherit;
--v3pi-font-size: 1rem;
--v3pi-padding-y: 0.375rem;
--v3pi-padding-x: 0.75rem;
--v3pi-focus-color: #0d6efd;
--v3pi-focus-width: 0.25rem;
--v3pi-error-color: #dc3545;
--v3pi-hover-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
--v3pi-dropdown-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.15);
--v3pi-item-hover-bg: #f8f9fa;
--v3pi-item-selected-bg: #e9ecef;
--v3pi-divider-color: #dee2e6;
--v3pi-disabled-bg: #e9ecef;
--v3pi-disabled-opacity: 0.65;
}Accessibility
This component is built for WCAG 2.2 AA compliance:
- Native
<input type="tel">— no fake inputs or masking - Real
<button>for the country selector witharia-expanded,aria-haspopup, andaria-controls - WAI-ARIA combobox pattern for country search (
role="combobox",aria-autocomplete,aria-activedescendant) role="listbox"androle="option"witharia-selectedfor country listaria-live="polite"region announcing search result count to screen readersaria-labelon the phone input, country button, and search inputaria-describedbylinking the phone input to a country hint for screen readersaria-invalidsupport for validation states- Full keyboard navigation: Arrow keys, Enter, Escape, Tab
:focus-visibleoutlines on all interactive elements- Unique IDs per instance — safe to use multiple components on one page
Keyboard navigation
DOM focus stays on the search input while the dropdown is open; only aria-activedescendant moves through the options. This follows the WAI-ARIA APG combobox pattern.
| Key | Context | Action |
|-----|---------|--------|
| Arrow Down / Up | Country button | Opens dropdown |
| Enter / Space | Country button | Opens dropdown |
| Arrow Down / Up | Search input | Highlights next/previous country |
| Home / End | Search input | Highlights first/last country |
| Enter | Search input | Selects highlighted country |
| Escape | Dropdown open | Closes dropdown, returns focus to button |
| Tab | Search input | Closes dropdown and moves focus to next focusable element |
TypeScript
Full type definitions are included. Import types directly:
import type { Country, CountryMeta, Vue3PhoneInputProps, Vue3PhoneInputEmits } from 'vue-a11y-phone';Browser support
The component targets evergreen browsers (Chrome, Firefox, Safari, Edge). It uses Intl.DisplayNames for native country names — older browsers fall back to the English name.
Contributing
Issues and pull requests welcome. See CONTRIBUTING.md.
License
MIT © Rickard Lindbom
