Input
Le système de champs TDS couvre tous les contrôles de formulaire : text, textarea, select,
checkbox, radio, switch et file upload. La structure standard est toujours label + champ + (message).
Do / Don’t
✓ Faire
- Label court et explicite au-dessus du champ
- Message d’erreur en dessous du champ, jamais dans le placeholder
- Placeholder = exemple de valeur, jamais le label
- Utiliser
autocompletepour faciliter la saisie
✗ Éviter
- Supprimer le label et le remplacer par un placeholder
- Valider le champ uniquement à la soumission (préférer
onBlur) - Mettre les messages d’erreur dans une tooltip
- Désactiver le copier-coller dans les champs mot de passe
Rendu avec le CSS réellement buildé de @tds-tchad/core — toutes les tailles, variantes et états.
Variantes
| Variante | Classe | Usage |
|---|---|---|
default | tds-input--default (ou aucune) | Saisie normale |
error | tds-input--error | Validation échouée, champ requis vide |
success | tds-input--success | Champ validé avec succès |
disabled | attribut disabled | Champ inactif, non modifiable |
Tailles
| Taille | Classe | Police | Padding | Min-height |
|---|---|---|---|---|
sm | tds-input--sm | 14px | 0.5rem 0.75rem | 44px |
md | tds-input--md | 16px | 0.75rem 1rem | 44px |
lg | tds-input--lg | 18px | 0.875rem 1.25rem | 52px |
Importation
@import "@tds-tchad/core/components/input/input.css";import type { InputVariant, InputSize, InputState } from '@tds-tchad/core/components/input';Structure de champ standard
<div class="tds-field"> <label class="tds-field__label" for="CHAMP_ID"> Libellé du champ <!-- Marqueur requis si nécessaire --> <span aria-hidden="true"> *</span> </label>
<!-- Aide contextuelle optionnelle --> <p id="CHAMP_ID-hint" class="tds-helper-text"> Texte d'aide pour préciser la saisie attendue. </p>
<!-- Le champ lui-même --> <input id="CHAMP_ID" name="nom_champ" type="text" class="tds-input tds-input--md" aria-describedby="CHAMP_ID-hint" required >
<!-- Message d'erreur (affiché en cas d'erreur) --> <p id="CHAMP_ID-error" class="tds-error-text" role="alert" hidden> Message d'erreur explicite. </p></div>Groupe de champs avec fieldset
Pour plusieurs champs liés, utilisez un <fieldset> nommé. Chaque contrôle est placé dans
un élément distinct et le groupe de messages est relié avec aria-describedby.
<fieldset class="tds-fieldset" aria-label="Demande de nom et prénom" aria-describedby="name-1-fieldset-messages"> <div class="tds-fieldset__element"> <div class="tds-input-group"> <label class="tds-label" for="input-family-name-1"> Nom </label> <input class="tds-input" spellcheck="false" autocomplete="family-name" name="family-name" id="input-family-name-1" type="text" > </div> </div>
<div class="tds-fieldset__element"> <div class="tds-input-group"> <label class="tds-label" for="input-firstname-1"> Prénom </label> <input class="tds-input" spellcheck="false" autocomplete="given-name" name="given-name" id="input-firstname-1" type="text" > </div> </div>
<div class="tds-messages-group" aria-live="assertive" id="name-1-fieldset-messages" ></div></fieldset>Lorsqu’un titre doit être visible, préférez un <legend class="tds-fieldset__legend">
à aria-label. Le groupe de messages peut recevoir des éléments
tds-helper-text, tds-error-text ou tds-success-text.
Types de contrôles
Text Input
<!-- Champ texte standard --><div class="tds-field"> <label class="tds-field__label" for="nom">Nom complet</label> <input id="nom" name="nom" type="text" class="tds-input tds-input--md" autocomplete="name" required placeholder="Ex : Moussa Ibrahim" ></div>
<!-- Champ en erreur --><div class="tds-field"> <label class="tds-field__label" for="email">Adresse e-mail</label> <input id="email" name="email" type="email" class="tds-input tds-input--md tds-input--error" autocomplete="email" aria-invalid="true" aria-describedby="email-error" value="invalide@" > <p id="email-error" class="tds-error-text" role="alert"> L'adresse e-mail saisie n'est pas valide. </p></div>
<!-- Champ désactivé --><div class="tds-field"> <label class="tds-field__label" for="ref">Numéro de référence</label> <input id="ref" name="ref" type="text" class="tds-input tds-input--md" disabled aria-disabled="true" value="REF-2024-001234" readonly > <p class="tds-helper-text">Ce champ est généré automatiquement.</p></div>import '@tds-tchad/core/components/input/input.css';import type { InputVariant, InputSize } from '@tds-tchad/core/components/input';
interface TextFieldProps { id: string; name: string; label: string; type?: 'text' | 'email' | 'password' | 'tel' | 'number'; size?: InputSize; variant?: InputVariant; hint?: string; error?: string; required?: boolean; disabled?: boolean; value?: string; onChange?: (value: string) => void;}
export function TextField({ id, name, label, type = 'text', size = 'md', variant = 'default', hint, error, required = false, disabled = false, value, onChange,}: TextFieldProps) { const hintId = hint ? `${id}-hint` : undefined; const errorId = error ? `${id}-error` : undefined; const describedBy = [hintId, errorId].filter(Boolean).join(' ') || undefined; const effectiveVariant = error ? 'error' : variant;
return ( <div className="tds-field"> <label className="tds-field__label" htmlFor={id}> {label} {required && <span aria-hidden="true"> *</span>} </label>
{hint && ( <p id={hintId} className="tds-helper-text">{hint}</p> )}
<input id={id} name={name} type={type} className={`tds-input tds-input--${size} tds-input--${effectiveVariant}`} disabled={disabled} required={required} aria-invalid={error ? 'true' : undefined} aria-describedby={describedBy} aria-required={required || undefined} value={value} onChange={(e) => onChange?.(e.target.value)} />
{error && ( <p id={errorId} className="tds-error-text" role="alert">{error}</p> )} </div> );}<script setup lang="ts">import '@tds-tchad/core/components/input/input.css';import { computed } from 'vue';import type { InputVariant, InputSize } from '@tds-tchad/core/components/input';
const props = withDefaults(defineProps<{ id: string; name: string; label: string; type?: string; size?: InputSize; variant?: InputVariant; hint?: string; error?: string; required?: boolean; disabled?: boolean; modelValue?: string;}>(), { type: 'text', size: 'md', variant: 'default', required: false, disabled: false,});
const emit = defineEmits<{ 'update:modelValue': [value: string] }>();
const effectiveVariant = computed(() => props.error ? 'error' : props.variant);const hintId = computed(() => props.hint ? `${props.id}-hint` : undefined);const errorId = computed(() => props.error ? `${props.id}-error` : undefined);const describedBy = computed(() => [hintId.value, errorId.value].filter(Boolean).join(' ') || undefined);</script>
<template> <div class="tds-field"> <label class="tds-field__label" :for="id"> {{ label }} <span v-if="required" aria-hidden="true"> *</span> </label>
<p v-if="hint" :id="hintId" class="tds-helper-text">{{ hint }}</p>
<input :id="id" :name="name" :type="type" :class="`tds-input tds-input--${size} tds-input--${effectiveVariant}`" :disabled="disabled" :required="required" :aria-invalid="error ? 'true' : undefined" :aria-describedby="describedBy" :value="modelValue" @input="emit('update:modelValue', ($event.target as HTMLInputElement).value)" />
<p v-if="error" :id="errorId" class="tds-error-text" role="alert"> {{ error }} </p> </div></template>Textarea
<div class="tds-field"> <label class="tds-field__label" for="description"> Description de la demande </label> <p id="description-hint" class="tds-helper-text"> Maximum 500 caractères. Soyez précis pour accélérer le traitement. </p> <textarea id="description" name="description" class="tds-textarea tds-textarea--md" rows="5" maxlength="500" aria-describedby="description-hint" required ></textarea></div>Select
<div class="tds-field"> <label class="tds-field__label" for="type-document"> Type de document demandé </label> <select id="type-document" name="type_document" class="tds-select tds-select--md" required aria-required="true" > <option value="" disabled selected>Sélectionnez un type</option> <option value="cni">Carte Nationale d'Identité</option> <option value="passeport">Passeport</option> <option value="naissance">Acte de naissance</option> <option value="mariage">Acte de mariage</option> <option value="permis">Permis de conduire</option> </select></div>Checkbox
<!-- Checkbox unique — consentement --><label class="tds-checkbox"> <input type="checkbox" name="consentement" value="1" required aria-required="true" > J'accepte que mes données soient traitées conformément à la politique de confidentialité.</label>
<!-- Groupe de checkboxes --><fieldset> <legend class="tds-field__label">Documents à fournir</legend> <label class="tds-checkbox"> <input type="checkbox" name="docs[]" value="cni"> Carte d'identité </label> <label class="tds-checkbox"> <input type="checkbox" name="docs[]" value="justificatif"> Justificatif de domicile </label> <label class="tds-checkbox"> <input type="checkbox" name="docs[]" value="photo"> Photo d'identité </label></fieldset>Radio
<fieldset> <legend class="tds-field__label">Vous êtes</legend> <label class="tds-radio"> <input type="radio" name="statut" value="citoyen" required> Citoyen tchadien </label> <label class="tds-radio"> <input type="radio" name="statut" value="resident"> Résident étranger </label> <label class="tds-radio"> <input type="radio" name="statut" value="diaspora"> Ressortissant en diaspora </label></fieldset>Switch
<!-- Switch simple --><label class="tds-switch"> <input type="checkbox" name="notifications" role="switch" aria-checked="false" > Recevoir les notifications par e-mail</label>Check-list obligatoire
- ✅ Chaque champ a un
<label>associé viafor/id - ✅ Les champs requis portent
required(HTML) etaria-required="true" - ✅ Les champs en erreur portent
aria-invalid="true" - ✅ Le message d’erreur est lié au champ via
aria-describedby - ✅ Le message d’erreur utilise
role="alert"pour être annoncé immédiatement - ✅ Les groupes checkbox/radio sont dans un
<fieldset>+<legend> - ✅ Le focus visible est maintenu (
:focus-visible) - ✅ Les champs disabled portent aussi
aria-disabled="true"