Vous les voyez ces champs limités en longueur sur les réseaux sociaux ? La plupart du temps, ce n'est qu'à la validation du formulaire qu'on vous prévient que « eh oh ! C'est trop long là ! ».
Pas toujours sympa… Autant prévenir en direct live et l'annoncer clairement.
Petit script tout simple qui va gérer ça automatiquement, en ajoutant un paragraphe au-dessus ou en-dessous (vous choisissez, on ne juge pas) du champ de saisie concerné.
C'est parti !
Accessibilité
Un maxlength sur un <input> ou un <textarea>, c'est bien. Mais silencieux. L'utilisateur tape, tape, tape... et rien ne se passe plus. Pas d'erreur, pas d'explication : le champ a simplement arrêté d'écouter. Pour quelqu'un qui navigue au clavier ou avec un lecteur d'écran, c'est une expérience désagréable au mieux, bloquante au pire.
zeFinalCountDown.js injecte un compteur de caractères dans le DOM, annoté avec aria-live="polite" et role="status". La restitution vocale se déclenche à chaque frappe, sans interrompre la navigation. WCAG 2.1 — critère 4.1.3 (Messages d'état) — coché.
Le tronquage automatique au-delà du maxlength évite aussi les surprises à la soumission du formulaire : ce qui dépasse est silencieusement retiré, et le compteur reflète la réalité.
Techniquement
L'API est minimaliste : un sélecteur CSS ou un élément DOM, un objet d'options facultatif.
Le script s'appuie sur l'attribut maxlength par défaut, mais c'est configurable — pratique pour les cas où la limite est portée par un attribut data-* maison.
Deux seuils de changement d'état visuel sont disponibles :
warning(défaut :0.5) — ajoute la classethreshold-warningà partir de 50% de la jauge rempliealert(défaut :0.9) — prend le relais avecthreshold-alertà partir de 90%
Les classes sont mutuellement exclusives et se nettoient proprement. Le CSS, c'est votre affaire, mais il y a le minimum syndical plus bas…
Le script écoute également l'événement reset sur le formulaire parent pour remettre le compteur à zéro.
Le wording du compteur est personnalisable via l'attribut data-wording directement sur le champ. Utilisez %s comme placeholder : le premier %s correspond aux caractères saisis, le second au maximum. Par défaut : %s sur %s caractères.
Support i18n intégré via window.i18n : le libellé finalCountDown_wording est remplaçable sans toucher au script.
Exemple
Jetez un œil sur la page contact.
Sinon, le code peut ressembler à ça :
Pour un champ simple
<label for="pseudo">Prénom</label>
<input type="text" id="pseudo" name="pseudo" maxlength="40">
<!-- appel JS -->
document.addEventListener('DOMContentLoaded', () => {
finalCountDown('#pseudo');
});
Wording personnalisé :
<input type="text" id="pseudo" name="pseudo" maxlength="40" data-wording="%s caractères saisis sur %s">
Automatisation à partir d'un sélecteur custom :
// un champ custom
<input type="text" id="pseudo" data-countdown data-maxlength="40" aria-label="Exemple de décompte de caractères" placeholder="Exemple de décompte">
// Récupère tous les éléments avec attribut 'data-maxlength'
document.addEventListener('DOMContentLoaded', () => {
finalCountDown('[data-countdown]', {
attr: 'data-maxlength',
css: 'mv-0 pl-s green'
});
});
Options
| Option | Type | Défaut | Description |
|---|---|---|---|
attr |
string |
'maxlength' |
Attribut portant la limite de caractères |
mobile |
boolean |
true |
Désactive le script si false |
css |
string ou null |
null |
Classe CSS additionnelle sur le compteur |
style |
string ou null |
null |
Style inline sur le compteur |
position |
string |
'after' |
'before' ou 'after' le champ |
warning |
number |
0.5 |
Seuil d'avertissement (proportion) |
alert |
number |
0.9 |
Seuil d'alerte critique (proportion) |
Le script
Taille brute : 4,34 kilo-octets (~1,63 kilo-octets gzip)
;(() => {
'use strict';
// Polyfill pour setAttributes si absent
Element.prototype.setAttributes ??= function (object) {
Object.keys(object).forEach((property) => {
object[property] !== null && this.setAttribute(property, object[property]);
});
};
/**
* Affiche un compteur de caractères pour les champs de saisie
*
* @param {string|HTMLElement} selector - Sélecteur CSS ou élément DOM
* @param {Object} [o={}] - Options de configuration
* @param {string} [o.attr='maxlength'] - Attribut définissant la longueur maximale
* @param {boolean} [o.mobile=true] - Activer sur mobile
* @param {string|null} [o.css=null] - Classes CSS additionnelles séparées par espace pour le compteur
* @param {string|null} [o.style=null] - Style inline pour le compteur
* @param {string} [o.position='after'] - Position du compteur ('before' ou 'after')
* @param {number} [o.warning=0.5] - Seuil d'avertissement (0.5 = 50%)
* @param {number} [o.alert=0.9] - Seuil d'alerte critique (0.9 = 90%)
*
* Wording personnalisable via l'attribut data-wording sur le champ.
* Utiliser %s comme placeholder : le premier %s = caractères saisis, le second %s = maximum.
* Exemple : data-wording="%s caractères saisis sur %s"
* Par défaut : "%s sur %s caractères"
*
* @example
* finalCountDown('textarea', { position: 'before', warning: 0.8 });
*
* @example
* // Wording personnalisé directement sur le champ HTML
* <textarea data-wording="%s caractères saisis sur %s maximum"></textarea>
*/
window.finalCountDown = (selector, o = {}) => {
const i18n = window.i18n || {};
const settings = Object.assign({
attr: 'maxlength',
mobile: true,
css: null,
style: null,
position: 'after',
warning: 0.5,
alert: 0.9
}, o);
if (!settings.mobile) return;
(typeof selector === 'string' ? document.querySelectorAll(selector) : [selector]).forEach(el => {
const maxlength = parseInt(el.getAttribute(settings.attr)) || 255;
// Wording : data-wording sur le champ en priorité, sinon i18n, sinon défaut
const wording = el.dataset.wording
|| i18n['finalCountDown_wording']
|| '%s sur %s caractères';
const getValLength = () => el.value.trim().length;
// Tronque initial si valeur préexistante dépasse maxlength
getValLength > maxlength && (el.value = el.value.trim().slice(0, maxlength));
const container = document.createElement('p');
container.classList.add('finalCountDown');
settings.css && container.classList.add(...settings.css.split(' '));
settings.style && container.setAttribute('style', settings.style);
// Force la lecture avec la combinaison live + role
container.setAttributes({
'aria-live': 'polite',
'role': 'status'
});
/**
* Met à jour le texte et les classes seuils du compteur
* @param {number} length - Nombre actuel de caractères
*/
const updateCounter = (length) => {
container.textContent = wording
.replace('%s', length)
.replace('%s', maxlength);
// Seuil d'alerte critique (prioritaire)
if (length >= maxlength * settings.alert) {
container.classList.add('threshold-alert');
container.classList.remove('threshold-warning');
// Seuil d'avertissement
} else if (length >= maxlength * settings.warning) {
container.classList.add('threshold-warning');
container.classList.remove('threshold-alert');
} else {
container.classList.remove('threshold-warning', 'threshold-alert');
}
};
// Initialisation
updateCounter(Math.min(getValLength(), maxlength));
// Event input
el.addEventListener('input', () => {
getValLength() > maxlength && (el.value = el.value.trim().slice(0, maxlength));
updateCounter(getValLength());
});
// Event reset sur le formulaire parent
el.closest('form')?.addEventListener('reset', () => {
// setTimeout pour attendre que le form reset soit effectif
setTimeout(() => updateCounter(0), 0);
});
settings.position === 'after' ? el.after(container) : el.before(container);
});
};
})();
Télécharger kortic-zeFinalCountDown.js
et un peu de CSS.
Taille brute : 495,00 octets (~297,00 octets gzip)
.finalCountDown {
font-weight: normal;
font-size: 0.9375em;
color: var(--canvasText, black);
line-height: inherit;
padding-left: var(--small-radius, 0.625em);
}
.finalCountDown + textarea {
margin-top: 0.3125rem;
}
/* !important nécessaires au cas où une classe de couleur est ajoutée via JS */
.finalCountDown.threshold-warning {
color: rgb(193,71,5) !important;
font-weight: 500;
}
.finalCountDown.threshold-alert {
color: var(--error-color, red) !important;
font-weight: 600;
}
Télécharger kortic-zeFinalCountDown.css
That's all, Folks !