Menu
Sur cette page
Un menu est un <details> avec un panneau stylé. C’est un choix délibéré face à l’API popover : un popover vit dans la couche supérieure et ne peut pas être positionné par rapport à son déclencheur sans le positionnement par ancre, qui n’est pas encore partout. Un <details> se place correctement dès aujourd’hui et n’a besoin de rien de chargé.
Le déclencheur est ce <summary>, stylé en bouton — vous passez donc le libellé et les props de bouton à menu() plutôt qu’un button() déjà rendu. Un summary est déjà interactif, et un bouton à l’intérieur imbrique deux contrôles là où il n’y a qu’une action : balisage invalide, et deux arrêts de tabulation pour une seule chose.
La fermeture au clic extérieur et à Échap vient d’un gestionnaire ontoggle qui les importe à la première ouverture d’un menu — et seulement alors. Si ce module n’arrive jamais, un menu s’ouvre et se ferme quand même depuis son propre summary.
Menu de base
menu({ trigger: 'Actions' },
menuItem({ href: '#edit' }, 'Modifier'),
menuItem({ href: '#duplicate' }, 'Dupliquer'),
menuSeparator(),
menuItem({ href: '#delete' }, 'Supprimer'),
)Alignement
Un menu s’ouvre depuis le bord de départ de son déclencheur. align: 'end' le retourne, ce qu’il faut à un menu proche du bord droit d’une barre.
stack({ direction: 'row', gap: 'xl', justify: 'space-between', style: 'width: 100%' },
menu({ trigger: 'Aligné au début', variant: 'soft' },
menuItem({ href: '#a' }, 'Premier'),
menuItem({ href: '#b' }, 'Deuxième'),
),
menu({ trigger: 'Aligné à la fin', variant: 'soft', align: 'end' },
menuItem({ href: '#c' }, 'Premier'),
menuItem({ href: '#d' }, 'Deuxième'),
),
)Déclencheurs en icône
Une icône sans texte de trigger réclame un label — il devient le nom accessible que l’icône ne peut pas fournir.
stack({ direction: 'row', gap: 'md' },
menu({
align: 'end',
label: 'Plus d’actions',
variant: 'ghost',
icon: icon('more-horizontal'),
},
menuItem({ href: '#rename' }, 'Renommer'),
menuItem({ href: '#move' }, 'Déplacer'),
menuSeparator(),
menuItem({ href: '#archive' }, 'Archiver'),
),
)Des éléments avec icônes
menu({ trigger: 'Fichier' },
menuItem({
href: '#new',
icon: icon('plus'),
}, 'Nouvelle page'),
menuItem({
href: '#open',
icon: icon('folder'),
}, 'Ouvrir…'),
menuSeparator(),
menuItem({
href: '#build',
icon: icon('zap'),
}, 'Construire le site'),
)Des boutons plutôt que des liens
Un élément sans href rend un <button> — pour une action qui se produit sur la page plutôt qu’une navigation.
menu({ trigger: 'Exporter', variant: 'soft', color: 'primary' },
menuItem({ onclick: "import('/su/toast.js').then(m=>m.toast('Exporté en JSON.',{color:'success'}))" }, 'En JSON'),
menuItem({ onclick: "import('/su/toast.js').then(m=>m.toast('Exporté en CSV.',{color:'success'}))" }, 'En CSV'),
)Dans une barre d’application
appBar({ brand: 'sitelo' },
appBarSpacer(),
appBarActions(
themeToggle(),
menu({
align: 'end',
label: 'Plus',
variant: 'ghost',
icon: icon('more-horizontal'),
},
menuItem({ href: '/fr/docs' }, 'Documentation'),
menuItem({ href: '/fr/examples' }, 'Exemples'),
menuSeparator(),
menuItem({ href: 'https://github.com/paul-browne/sitelo' }, 'GitHub'),
),
),
)Accessibilité
Le panneau est un role="menu" dont les éléments sont des role="menuitem", et le summary porte aria-haspopup. Un <details> n’est pas un widget de menu natif : c’est donc une approximation raisonnable, pas une parfaite — pour une simple liste de liens, un nav à l’intérieur du details est tout aussi valable et promet moins.
Props
menu() — les props du déclencheur sont celles du bouton :
| Prop | Type | Défaut | Description |
|---|---|---|---|
trigger | Child | — | Libellé visible. Passez du texte, pas un button() rendu. |
icon | Child | — | Balisage avant le libellé, ou seul pour un déclencheur en icône. |
label | string | — | Nom accessible. Obligatoire quand il y a une icône et aucun texte de trigger. |
variant | 'solid' | 'soft' | 'outline' | 'ghost' | 'link' | 'outline' | Style du déclencheur. |
color | 'primary' | 'neutral' | 'success' | 'warning' | 'danger' | 'neutral' | La palette du déclencheur. |
size | 'sm' | 'md' | 'lg' | 'md' | Taille du déclencheur. |
align | 'start' | 'end' | 'start' | Le bord du déclencheur sur lequel le panneau s’aligne. |
triggerClass | string | — | Classes en plus pour le déclencheur plutôt que pour le details englobant. |
menuItem() :
| Prop | Type | Défaut | Description |
|---|---|---|---|
href | string | — | Rend une ancre ; sans lui, un bouton. |
icon | Child | — | Balisage avant le libellé. |
as | string | 'button' | Élément rendu en l’absence de href. |
menuSeparator() ne prend aucune prop — c’est le filet entre deux groupes d’éléments.