dsfr-tel
Dsfr-tel
Qu'est-ce ?
Dsfr-tel est un composant VueJS permettant de formater et de valider la saisie d’un numéro de téléphone (portable, fixe…) grâce à la bibliothèque Libphonenumber de Google, tout en s'inscrivant dans le système de design de l'État.
Il offre notamment :
- un champ de saisie du numéro de téléphone,
- une liste déroulante permettant de sélectionner l'indicatif du pays correspondant,
- un formatage automatique du numéro (passage de l'international au format national, par exemple) y compris lors de la saisie,
- une validation du numéro (présence, longueur, validité, type…),
- la détection automatique du pays à partir du fuseau horaire (optionnelle via la prop
autoDetectCountry).
Ce composant n'est pas proposé par le Service d'information du Gouvernement.
Les évolutions de chaque version sont détaillées dans CHANGELOG.md.
Installation
npm i dsfr-tel
N'oubliez pas d'importer également la feuille de style nécessaire :
import 'dsfr-tel/style.css';
libphonenumber-js est une dépendance du
paquet, et non une copie embarquée : votre bundler la résout comme n'importe quelle autre. Les
mises à jour de ses plans de numérotation, publiées plusieurs fois par mois, vous parviennent
donc sans attendre une nouvelle version de dsfr-tel, et la bibliothèque n'est jamais présente
en double si votre application l'utilise déjà.
Variante « portables uniquement »
Si votre formulaire n'accepte que des numéros de portable, importez le composant depuis
dsfr-tel/mobile : il s'appuie sur les métadonnées libphonenumber-js/mobile, plus légères
(49 ko gzip contre 64 ko dans l'application finale, Vue exclu).
import { DsfrTel } from 'dsfr-tel/mobile';
Ces métadonnées ne décrivent que les numéros de portable : un numéro fixe, surtaxé ou gratuit y
est jugé invalide. C'est donc le message errorMessages.invalid qui s'affiche, et non
incorrectType ; adaptez-le en conséquence (par exemple « Veuillez saisir un numéro de
téléphone portable valide. »). N'importez pas les deux variantes dans une même application :
les deux jeux de métadonnées seraient embarqués.
Sans outil de build
Avec un outil de build (Vite, webpack, Nuxt…), npm i dsfr-tel suffit : libphonenumber-js est
installé et résolu automatiquement. Seule une page qui charge le composant directement par
balises <script> doit charger Vue puis libphonenumber-js avant lui :
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/dsfr-tel@1/dist/dsfr-tel.css" />
<div id="app">
<dsfr-tel></dsfr-tel>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/libphonenumber-js@1/bundle/libphonenumber-max.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dsfr-tel@1/dist/dsfr-tel.umd.js"></script>
<script>
Vue.createApp({}).use(DsfrTel.default).mount('#app');
</script>
Pour la variante mobile, utilisez libphonenumber-mobile.js et dist/mobile/dsfr-tel.umd.js.
Utilisation
Vous pouvez utiliser le composant de deux manières :
Utilisation globale (plugin)
Installez le plugin dans le point d'entrée de votre application (par exemple dans main.ts ou main.js) :
// filepath: /src/main.ts
import { createApp } from 'vue';
import App from './App.vue';
import DsfrTelPlugin from 'dsfr-tel';
const app = createApp(App);
app.use(DsfrTelPlugin);
app.mount('#app');
Une fois installé globalement, vous pouvez directement utiliser le composant :
<template>
<DsfrTel ref="dsfrTel" />
</template>
Utilisation locale
Si vous préférez enregistrer le composant uniquement dans certains composants, importez le composant nommé et enregistrez-le localement :
<template>
<DsfrTel ref="dsfrTel" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { DsfrTel } from 'dsfr-tel';
const dsfrTel = ref();
function onSubmit() {
if(dsfrTel.value.validatePhoneNumber()){
const formattedNumber = dsfrTel.value.getPhoneNumberFormatted('NATIONAL');
// …
}
}
</script>
Paramètres
Le composant accepte les paramètres suivants via des props :
-
expectedTypes (Array<string>)
Définit les types de numéros autorisés.
Default :['MOBILE', 'FIXED_LINE_OR_MOBILE']Remarque : Les valeurs possibles pour les types sont celles retournées par la bibliothèque libphonenumber-js, par exemple :
MOBILE(Portable)FIXED_LINE(Fixe)FIXED_LINE_OR_MOBILE(Fixe ou portable)TOLL_FREE(Numéro gratuit)PREMIUM_RATE(Numéro surtaxé)VOIP(Numéro de VoIP)PERSONAL_NUMBER(Numéro personnel)PAGER(Numéro de bipeur)UAN(Numéro universel)UNKNOWN(Type inconnu)FAX(Fax)SHARED_COST(Numéro à coût partagé)SATELLITE(Numéro satellite)EMERGENCY(Numéro d'urgence)VOICEMAIL(Messagerie vocale)SPARE(Numéro de rechange)
Ces valeurs sont traduites automatiquement dans l'interface grâce à la table de concordance
numberTypeLabels. -
fieldsetLegend (string)
Texte affiché dans la légende du regroupement de champs.
Default :"Votre numéro de téléphone portable" -
errorMessages (Object)
Messages d'erreur généraux utilisés lors de la validation. Default :{ "required": "La saisie du numéro de téléphone est requise.", "invalid": "Le numéro renseigné est incorrect. Veuillez le vérifier.", "incorrectType": "Le numéro doit être de type {types}.", "unknown": "Erreur lors de la validation du numéro.", "parse": "Erreur lors de l'analyse du numéro." } -
placeholderPrefix (string)
Préfixe utilisé pour l'emplacement de texte du champ de saisie.
Default :"Exemple : " -
reasonMessages (Object)
Messages d'erreur spécifiques au format ou à la longueur du numéro de téléphone.
Default :{ "TOO_SHORT": "Le numéro de téléphone saisi est trop court.", "TOO_LONG": "Le numéro de téléphone saisi est trop long.", "INVALID_COUNTRY": "Le code du pays est invalide.", "INVALID_LENGTH": "La longueur du numéro de téléphone saisi n'est pas valide.", "NOT_A_NUMBER": "La valeur saisie n'est pas un numéro de téléphone." } -
hint (string)
Indication sur le format attendu.
Default :"Au format national (ex : X"où X est un exemple de format (au format national, fonction du pays sélectionné). -
locale (string)
Langue des noms de pays, fournis parIntl.DisplayNames. À défaut, l’attributlangde la page, puis le français.
Default :undefined -
autoDetectCountry (boolean)
Active la détection automatique du pays de l’usager à partir de son fuseau horaire.
Default :true -
modelValue (string)
Numéro lié parv-model, au formatmodelFormat. En entrée, un numéro international (+32470123456) sélectionne aussi le pays correspondant ; le champ affiche toujours le format national. Tant que la saisie ne constitue pas un numéro analysable, la valeur émise est vide. UtilisezvalidatePhoneNumber()ou l’événementvalidationpour la validité.
Default :undefined -
modelFormat (
'E164'|'INTERNATIONAL'|'NATIONAL'|'RFC3966')
Format émis parv-model. E.164 est le seul format qui conserve l’indicatif sans ambiguïté, et celui qu’attendent la plupart des services d’envoi de SMS.
Default :'E164' -
country (string)
Force le pays sélectionné (code ISO 3166-1 alpha-2). Prioritaire surautoDetectCountry.
Default :undefined -
inputLabel (string)
Étiquette du champ de saisie, associée via<label for>et masquée visuellement.
Default :"Numéro de téléphone" -
required (boolean)
Rend la saisie obligatoire : mention dans la légende (voirrequiredMarker), message d’erreur dédié etaria-required="true". L’attribut HTMLrequiredn’est volontairement pas posé, la bulle native du navigateur masquant sinon le message d’erreur du DSFR.
Default :false -
requiredMarker (string)
Mention ajoutée à la légende quandrequiredest actif :"*","(requis)"ou tout autre texte, par exemple"(obligatoire)". Une mention uniquement symbolique, comme*, est masquée aux technologies d’assistance, qui reçoivent déjà l’information pararia-required. Avec l’astérisque, sa signification doit être indiquée dans le formulaire, avant le champ (critère RGAA 11.10), par exemple : « Les champs marqués d’un astérisque (*) sont obligatoires. » Une chaîne vide supprime la mention, lorsque le formulaire précise déjà que tous les champs sont obligatoires.
Default :"*" -
labels (Object)
Chaînes de l’interface, pour traduire le composant. Toutes les clés sont facultatives ; les types non fournis gardent leur libellé français.
Default :{ "country": "Indicatif du pays", "countryList": "Liste de sélection de l'indicatif", "changeCountry": "Modifier l'indicatif sélectionné ({country})", "hint": "Au format national (ex : {example})", "types": { "MOBILE": "Portable", "FIXED_LINE": "Fixe", "…": "…" } } -
validateOnBlur (boolean)
Déclenche la validation à la sortie du champ.
Default :false
Événements
- update:modelValue (string) : lorsque le numéro change, au format
modelFormat(permetv-model). Rien n’est émis si la valeur est identique à la précédente. - update:country (CountryCode) : lorsque le pays change (sélection ou détection à partir d’un numéro international).
- validation (boolean) : résultat de chaque appel à
validatePhoneNumber().
Méthodes
Le composant expose plusieurs méthodes :
-
validatePhoneNumber() : boolean
Valide le numéro de téléphone saisi en vérifiant :- Sa présence,
- Son format (en utilisant Libphonenumber),
- Son type (parmi ceux définis dans
expectedTypes).
-
getPhoneNumberFormatted(format: 'E164' | 'NATIONAL' | 'INTERNATIONAL' | 'RFC3966') : string
Retourne le numéro de téléphone au format demandé (national, international…). -
getPhoneNumberType() : string
Renvoie le type du numéro tel que retourné par Libphonenumber ("MOBILE", par exemple, pour un numéro de téléphone portable). -
reset() : void
Vide le champ et efface le message d’erreur. -
focus() : void
Donne le focus au champ de saisie (utile après une soumission invalide).
Note : La détection du pays via le fuseau horaire s'effectue automatiquement lors du montage du composant si la prop
autoDetectCountryest activée. La méthodegetDefaultCountryFromTimezonerécupère le fuseau horaire de l’usager pour définir le pays par défaut.
Exports du paquet
En plus du composant et du plugin, le paquet expose numberTypeLabels (traduction des types
de numéros renvoyés par Libphonenumber, pour afficher « Portable » plutôt que MOBILE),
getCountryList(locale?) (liste des pays et indicatifs utilisée par le composant, triée dans
la langue demandée), defaultLabels (chaînes par défaut, à étendre pour une traduction) et les
types Country et Labels.
Propriétés exposées
Les propriétés accessibles sur le composant sont :
- phoneNumber : le numéro de téléphone actuellement saisi.
- selectedCountry : le code du pays sélectionné via la liste déroulante.
Personnalisation
Vous pouvez personnaliser l'apparence et le comportement du composant en lui passant des props. Par exemple :
<DsfrTel
:expectedTypes="['MOBILE']"
fieldsetLegend="Votre numéro de téléphone portable"
:errorMessages="{
required: 'La saisie de votre numéro de téléphone portable est obligatoire',
invalid: 'Numéro de téléphone invalide',
incorrectType: 'La saisie d\'un numéro de téléphone portable est attendue',
unknown: 'Erreur inconnue',
parse: 'Erreur d\'analyse'
}"
placeholderPrefix="Exemple : "
:reasonMessages="{
TOO_SHORT: 'Numéro trop court',
TOO_LONG: 'Numéro trop long',
INVALID_COUNTRY: 'Pays invalide',
INVALID_LENGTH: 'Longueur invalide',
NOT_A_NUMBER: 'Ce n’est pas un numéro'
}"
:autoDetectCountry="true"
/>
Noms de pays
Les noms proviennent d’Intl.DisplayNames : aucune table de traduction n’est embarquée et la
liste suit la langue de la page. Une seule correction est appliquée en français — « FR » est
libellé « France métropolitaine », les départements et collectivités d’outre-mer ayant leurs
propres codes et indicatifs (GP, MQ, GF, RE, YT…), qu’il serait trompeur de confondre dans un
champ téléphonique.
Accessibilité
La sélection de l’indicatif suit le motif ARIA
Select-Only Combobox,
qui reproduit le fonctionnement d’un <select> natif : une combobox non éditable, nommée
« Indicatif du pays » (aria-labelledby), dont le contenu restitue le pays retenu. À l’écran,
seul le drapeau est affiché ; le libellé complet apparaît au survol (title).
Le focus ne quitte jamais la combobox : l’option ayant le focus visuel est désignée par
aria-activedescendant, porte aria-selected="true" et est ramenée dans la zone visible, ce
qui importe aux personnes qui agrandissent l’affichage. Parcourir la liste ne change pas la
valeur : elle n’est retenue qu’avec Entrée, Espace, Tab, Alt+Flèche haut, un clic
sur une option, ou lorsque le focus quitte la combobox.
Clavier, liste fermée :
Flèche bas,Alt+Flèche bas,Entrée,Espace: ouvrent la liste sur le pays retenu ;Flèche haut,Début: ouvrent la liste sur la première option ;Fin, sur la dernière ;- lettres : ouvrent la liste sur le premier pays correspondant.
Clavier, liste ouverte :
Entrée,Espace,Alt+Flèche haut: retiennent l’option et ferment la liste ;Tab: retient l’option, ferme la liste et passe à l’élément suivant ;Échap: ferme la liste sans changer la valeur ;Flèche bas/Flèche haut: option suivante / précédente, sans dépasser les extrémités ;Début/Fin: première / dernière option ;Page suiv./Page préc.: saut de dix options, borné aux extrémités ;- lettres : recherche incrémentale insensible aux accents (« eg » atteint « Égypte ») ; répéter une même lettre parcourt les pays qui commencent par elle.
Chaque champ dispose d’une étiquette <label for>, les identifiants sont
uniques par instance et les messages d’erreur sont restitués via une région role="alert".
Les identifiants proviennent de useId() : ils sont identiques entre rendu serveur et
hydratation (Nuxt). Si une même page héberge plusieurs applications Vue, donnez-leur des
préfixes distincts pour éviter tout doublon :
app.config.idPrefix = 'formulaire-contact';
Développement
Node.js 22.22.2 ou plus récent est nécessaire pour développer le composant (Vite 8, Vitest 5, jsdom 30).
npm ci
npm run dev # démonstration locale (dossier demo/)
npm test # tests unitaires et audits axe-core (Vitest)
npm run test:coverage # idem, avec seuils de couverture
npm run typecheck # vue-tsc
npm run build # librairie → dist/
npm run build:demo # site de démonstration → dist-demo/
npm run check # toutes les vérifications, comme avant une publication
Publication
La publication est automatisée (.github/workflows/publish.yml) par Trusted Publishing : aucun jeton npm n'est stocké dans le dépôt, et chaque version est accompagnée d'une attestation de provenance.
Au fil des modifications, décrivez-les dans la section ## [Non publiée] en tête de CHANGELOG.md. Pour publier, une seule commande, dépôt à jour et sans modification en attente :
npm version minor # patch pour une correction, major pour un changement cassant
Elle vérifie que la section « Non publiée » est remplie et rejoue toutes les vérifications (npm run check), puis met à jour le numéro de version, renomme la section avec ce numéro et la date du jour, crée le commit « Version x.y.z » et son tag, et pousse le tout. Le tag déclenche la publication sur npm, puis la création de la release GitHub avec la section correspondante du journal.
La publication échoue si le tag ne correspond pas à la version du package.json ou si le journal n'a pas de section datée pour cette version, et ne fait rien si la version existe déjà sur npm.
Démonstration et DSFR
La démonstration (demo/) charge le DSFR depuis jsDelivr, en version épinglée, avec une
empreinte d'intégrité (SRI) qui fait refuser par le navigateur tout fichier altéré. Pour
changer de version, mettez à jour l'URL et l'empreinte dans demo/index.html :
curl -s https://cdn.jsdelivr.net/npm/@gouvfr/dsfr@X.Y.Z/dist/dsfr.min.css \
| openssl dgst -sha384 -binary | openssl base64 -A
(idem pour dsfr.module.min.js), en préfixant le résultat par sha384-.
Intégration continue
Le workflow .github/workflows/ci.yml vérifie les types, exécute les tests — dont des audits
axe-core (WCAG 2.1 A et AA) sur le composant liste fermée, ouverte et en erreur — avec des
seuils de couverture, et construit la librairie à chaque push et pull request. Les actions
sont épinglées par SHA de commit ; Dependabot (.github/dependabot.yml) propose chaque
semaine les mises à jour des actions et des dépendances npm, avec un délai de sept jours après
publication. Sur main, il construit la démonstration,
effectue un contrôle d’accessibilité automatisé (pa11y, WCAG 2.1 AA, configuré dans
pa11y.json, lancé par npm run test:a11y) et la publie sur GitHub Pages. Ce contrôle n’est bloquant que si la variable
de dépôt PA11Y_BLOQUANT vaut true (Settings → Secrets and variables → Actions →
Variables) : à activer après un premier passage propre. Activation : Settings → Pages → Source : GitHub Actions.
Related Packages
Composant Vue adoptant le système de design de l'État et proposant une autocompl...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 1.1.2 | ^3.5.0 | MIT | |
| 1.1.1 | ^3.5.0 | MIT | |
| 1.1.0 | ^3.5.0 | MIT | |
| 1.0.27 | ^3.5.13 | MIT | |
| 1.0.26 | ^3.5.13 | MIT | |
| 1.0.25 | ^3.5.13 | MIT | |
| 1.0.24 | ^3.5.13 | MIT | |
| 1.0.23 | ^3.5.13 | MIT | |
| 1.0.22 | ^3.5.13 | MIT | |
| 1.0.21 | ^3.5.13 | MIT | |
| 1.0.20 | ^3.5.13 | MIT | |
| 1.0.19 | ^3.5.13 | MIT | |
| 1.0.18 | ^3.5.13 | MIT | |
| 1.0.17 | ^3.5.13 | MIT | |
| 1.0.16 | ^3.5.13 | MIT | |
| 1.0.15 | ^3.5.13 | MIT | |
| 1.0.14 | ^3.5.13 | MIT | |
| 1.0.13 | ^3.5.13 | MIT | |
| 1.0.12 | ^3.5.13 | MIT | |
| 1.0.11 | ^3.5.13 | MIT | |
| 1.0.10 | ^3.5.13 | MIT | |
| 1.0.9 | ^3.5.13 | MIT | |
| 1.0.8 | ^3.5.13 | MIT | |
| 1.0.7 | ^3.5.13 | MIT | |
| 1.0.6 | ^3.5.13 | MIT | |
| 1.0.5 | ^3.5.13 | MIT | |
| 1.0.4 | ^3.5.13 | MIT | |
| 1.0.3 | ^3.5.13 | MIT | |
| 1.0.2 | ^3.5.13 | MIT | |
| 1.0.1 | ^3.5.13 | MIT |
Showing the latest 30 of 31. See every release on npm