COMPOSANTS

Bascule de vue

Bascule tableau / grille

Deux boutons icône dans un join. La vue affichée porte btn-active (fond navy plein) et pointer-events-none ; l'autre reste un bouton default, donc visiblement cliquable.

<div class="join">
  <button class="join-item btn btn-sm btn-active pointer-events-none" title="Vue tableau" aria-label="Vue tableau">
    <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M3 6h18M3 14h18M3 18h18"/>
    </svg>
  </button>
  <button class="join-item btn btn-sm" title="Vue grille" aria-label="Vue grille">
    <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
            d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6z
               M14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6z
               M4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2z
               M14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/>
    </svg>
  </button>
</div>

Contexte dans une toolbar

Le toggle s'intègre dans la barre d'outils entre le compteur de résultats et le CTA principal. Il ne doit pas monopoliser l'attention.

284 produits
<div class="flex items-center justify-between mb-4">
  <div class="flex items-center gap-3">
    <span class="text-sm text-base-content/60">284 produits</span>
    <div class="join">
      <button class="join-item btn btn-sm btn-active pointer-events-none" title="Vue tableau">
        <!-- icône lignes -->
      </button>
      <button class="join-item btn btn-sm" title="Vue grille">
        <!-- icône grille -->
      </button>
    </div>
  </div>
  <button class="btn btn-primary btn-sm">Ajouter un produit</button>
</div>

Persistance localStorage

La préférence de vue est mémorisée entre les sessions. Appliquer au chargement de la page, sauvegarder à chaque changement.

// Stimulus — les deux vues sont rendues, le contrôleur en masque une
applyView(mode) {
  const isGrid = mode === "grid"
  this.tableViewTarget.classList.toggle("hidden", isGrid)
  this.gridViewTarget.classList.toggle("hidden", !isGrid)
  this._setActive(this.tableBtnTarget, !isGrid)
  this._setActive(this.gridBtnTarget, isGrid)
}

_setActive(btn, active) {
  btn.classList.toggle("btn-active", active)
  btn.classList.toggle("pointer-events-none", active)
}
Vue sélectionnée : btn-active + pointer-events-none, les deux ensemble.
Stocker la préférence en localStorage (persistante entre les sessions), avec une clé unique par liste.
Défaut selon le type : grille pour les listes visuelles (images/vignettes), table pour les listes de données.
Toujours ajouter title et aria-label (icônes uniquement).
Ne pas marquer l'inactif en btn-ghost : sans fond ni bordure, rien ne dit qu'il est cliquable.
Ne pas utiliser un dropdown pour basculer entre 2 vues — utiliser join + btn.