Tu as sûrement déjà rempli un formulaire où la liste des villes change selon le pays choisi. Ce sont des listes déroulantes liées (ou dynamiques) : la seconde se remplit en JavaScript en fonction de la première. Un petit projet parfait pour apprendre à manipuler le DOM et les évènements. Durée : environ 30 minutes.

Ce que tu vas construire

  • une liste de pays remplie automatiquement à partir des données ;
  • une liste de villes désactivée tant qu'aucun pays n'est choisi, puis remplie avec les villes du pays ;
  • un message qui confirme le choix, annoncé par les lecteurs d'écran.

Crée un dossier avec index.html, style.css et script.js.

1. La structure HTML

Deux <select>, chacun avec son <label> : le label est lié au champ par l'attribut for, ce qui rend le formulaire accessible et agrandit la zone cliquable. La liste des villes commence désactivée (disabled).

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Listes déroulantes liées</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <form class="form">
    <label for="country">Pays</label>
    <select id="country" name="country">
      <option value="">Choisis un pays</option>
    </select>

    <label for="city">Ville</label>
    <select id="city" name="city" disabled>
      <option value="">Choisis d'abord un pays</option>
    </select>

    <p class="result" aria-live="polite"></p>
  </form>

  <script src="script.js"></script>
</body>
</html>

L'attribut aria-live="polite" sur le paragraphe de résultat demande aux lecteurs d'écran d'annoncer son contenu quand il change.

2. Un peu de style

.form {
  display: grid;
  gap: 0.5rem;
  max-width: 20rem;
  margin: 2rem auto;
  font-family: system-ui, sans-serif;
}

select {
  padding: 0.5rem;
  font: inherit;
  border: 1px solid #aaa;
  border-radius: 0.375rem;
}

select:disabled {
  opacity: 0.5;
}

.result {
  min-height: 1.5em;
  font-weight: 600;
}

3. Le JavaScript

// Les données : chaque pays et ses villes. Plus tard, elles pourront venir d'un fichier JSON.
const citiesByCountry = {
  France: ['Paris', 'Marseille', 'Lyon', 'Toulouse'],
  Belgique: ['Bruxelles', 'Liège', 'Namur'],
  Canada: ['Montréal', 'Québec', 'Ottawa'],
};

const countrySelect = document.getElementById('country');
const citySelect = document.getElementById('city');
const result = document.querySelector('.result');

// Crée une balise <option> ; new Option(texte, valeur) évite d'écrire du HTML à la main.
function fillSelect(select, values, placeholder) {
  select.replaceChildren(new Option(placeholder, ''));
  values.forEach((value) => select.add(new Option(value, value)));
}

fillSelect(countrySelect, Object.keys(citiesByCountry), 'Choisis un pays');

countrySelect.addEventListener('change', () => {
  const cities = citiesByCountry[countrySelect.value] ?? [];

  fillSelect(citySelect, cities, cities.length ? 'Choisis une ville' : "Choisis d'abord un pays");
  citySelect.disabled = cities.length === 0;
  result.textContent = '';
});

citySelect.addEventListener('change', () => {
  result.textContent = citySelect.value ? `Tu as choisi ${citySelect.value} (${countrySelect.value}).` : '';
});

Ce qu'il faut retenir :

  • Les données sont séparées du code : l'objet citiesByCountry associe chaque pays à ses villes. Pour ajouter un pays, il suffit d'ajouter une ligne ; aucune autre partie du code ne change.
  • new Option(texte, valeur) crée une option proprement, sans concaténer du HTML (source classique de bugs et de failles).
  • replaceChildren() vide la liste en une seule instruction avant de la remplir à nouveau.
  • ?? [] (l'opérateur de coalescence) renvoie une liste vide quand le pays choisi est l'option « Choisis un pays ».

4. Tester

  • Au chargement, la liste des villes doit être grisée.
  • Choisis « Belgique » : la liste propose Bruxelles, Liège et Namur.
  • Reviens sur « Choisis un pays » : la liste des villes se désactive et le message s'efface.

Pour aller plus loin : charger les données depuis un fichier JSON

Dans un vrai projet, les données viennent d'un fichier ou d'une API. Place-les dans villes.json :

{
  "France": ["Paris", "Marseille", "Lyon", "Toulouse"],
  "Belgique": ["Bruxelles", "Liège", "Namur"]
}

Puis, au début du script, remplace l'objet écrit en dur par un appel à fetch :

const response = await fetch('villes.json');
const citiesByCountry = await response.json();

Attention : fetch ne fonctionne pas en ouvrant le fichier directement (file://). Lance un petit serveur local, par exemple avec l'extension Live Server de VS Code, ou la commande php -S localhost:8000 dans le dossier du projet.

Autres idées : un troisième niveau (pays, région, ville), ou un champ de recherche qui filtre les villes au fil de la frappe.