Headless Menu en Dropdown opmaken
Pas Tailwind utility-classes toe op de Menu-component van Headless UI om een verzorgde dropdown te maken met toetsenbordnavigatie en ARIA-attributen.
Headless Menu en Dropdown opmaken is een gratis Tailwind CSS Academy-les op CoddyKit. Dit is les 2 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Tailwind CSS Academy. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Tailwind CSS Academy bevat in totaal 4 lessen.
Anatomie van een Headless UI-menu
Het Headless UI-Menu bestaat uit vier onderdelen: Menu (de hoofdcontainer die de context levert), Menu.Button (de klikbare activering), Menu.Items (het vervolgkeuzepaneel dat wordt weergegeven wanneer het menu is geopend) en een of meer Menu.Item-elementen (afzonderlijke selecteerbare opties). Elk onderdeel accepteert className of een renderfunctie die de status ontvangt, zodat je op elk niveau volledige controle met Tailwind hebt.
import { Menu } from '@headlessui/react';
// Structure overview
<Menu as='div' className='relative inline-block'>
{/* 1. Trigger button */}
<Menu.Button>Open</Menu.Button>
{/* 2. Dropdown panel (shown when open) */}
<Menu.Items>
{/* 3. Individual options */}
<Menu.Item>{({ active }) => <button>Option A</button>}</Menu.Item>
<Menu.Item>{({ active }) => <button>Option B</button>}</Menu.Item>
</Menu.Items>
</Menu>De activeringsknop van het menu stylen
De activering van Menu.Button moet duidelijk maken dat de knop interactief is en de geopende of gesloten status visueel tonen. Gebruik de renderprop open om de weergave van de knop te wijzigen wanneer het vervolgkeuzemenu zichtbaar is. Veelgebruikte patronen zijn een chevronpictogram laten draaien, de achtergrondkleur aanpassen aan het geopende vervolgkeuzemenu en een ring toevoegen zodat de focus zichtbaar is.
import { Menu } from '@headlessui/react';
import { ChevronDownIcon } from '@heroicons/react/20/solid';
<Menu.Button className='inline-flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500'>
{({ open }) => (
<>
Actions
<ChevronDownIcon
className={cn(
'h-4 w-4 text-gray-400 transition-transform duration-150',
open && 'rotate-180'
)}
/>
</>
)}
</Menu.Button>Het vervolgkeuzepaneel positioneren
Het paneel van Menu.Items moet visueel onder de activering blijven staan. Geef de hoofdcontainer Menu de positie relative en het paneel de positie absolute. Gebruik top-full en right-0 of left-0 om het paneel uit te lijnen met de activering. Stel een z-index in zodat het vervolgkeuzemenu boven andere pagina-inhoud verschijnt en voeg mt-1 toe voor een kleine ruimte tussen de activering en het paneel.
<Menu as='div' className='relative'>
<Menu.Button>...</Menu.Button>
<Menu.Items
className='
absolute right-0 top-full mt-1 z-20
w-56 origin-top-right
rounded-xl bg-white shadow-lg
ring-1 ring-black/5
focus:outline-none
'
>
{/* Items */}
</Menu.Items>
</Menu>Menu-items stylen met een actieve status
Elke Menu.Item ontvangt een renderprop active die true is wanneer het item de focus heeft of wanneer je de muis erboven houdt. Gebruik deze prop om een gemarkeerde achtergrond toe te passen — meestal subtiel blauw of grijs. Gebruik ook disabled om niet-beschikbare opties te vervagen en interactie ermee te voorkomen. Plaats de inhoud in een button- of a-element zodat deze met het toetsenbord focus kan krijgen.
<Menu.Items className='absolute right-0 mt-1 w-56 rounded-xl bg-white shadow-lg ring-1 ring-black/5 p-1'>
<Menu.Item>
{({ active }) => (
<button
className={cn(
'flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm',
active ? 'bg-blue-50 text-blue-700' : 'text-gray-700'
)}
>
<PencilIcon className='h-4 w-4' />
Edit
</button>
)}
</Menu.Item>
<Menu.Item disabled>
{({ active, disabled }) => (
<button
className={cn(
'flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm',
disabled && 'opacity-40 cursor-not-allowed'
)}
>
<ShareIcon className='h-4 w-4' />
Share (unavailable)
</button>
)}
</Menu.Item>
</Menu.Items>Scheidingslijnen tussen groepen toevoegen
Groepeer verwante menu-items met visuele scheidingslijnen. Omdat Headless UI geen component voor scheidingslijnen biedt, voeg je eenvoudig een gewone div met een rand of een container met opvulling toe. Deel items in logische secties in — primaire acties, secundaire acties en een destructieve actie — en scheid ze met dunne horizontale lijnen die je met Tailwind stijlt.
<Menu.Items className='absolute right-0 mt-1 w-56 rounded-xl bg-white shadow-lg ring-1 ring-black/5 p-1 focus:outline-none'>
{/* Primary actions */}
<div className='pb-1 border-b border-gray-100'>
<Menu.Item>{({ active }) => <button className={itemCls(active)}>View</button>}</Menu.Item>
<Menu.Item>{({ active }) => <button className={itemCls(active)}>Edit</button>}</Menu.Item>
</div>
{/* Secondary actions */}
<div className='py-1 border-b border-gray-100'>
<Menu.Item>{({ active }) => <button className={itemCls(active)}>Duplicate</button>}</Menu.Item>
</div>
{/* Destructive action */}
<div className='pt-1'>
<Menu.Item>
{({ active }) => (
<button className={cn('flex w-full px-3 py-2 text-sm rounded-lg', active ? 'bg-red-50 text-red-700' : 'text-red-600')}>
Delete
</button>
)}
</Menu.Item>
</div>
</Menu.Items>Vervolgkeuzemenu met pictogramdecoraties
Een verzorgd vervolgkeuzemenu bevat pictogrammen naast elk label, zodat gebruikers opties snel kunnen herkennen. Importeer pictogrammen uit een bibliotheek zoals @heroicons/react (gebouwd door hetzelfde Tailwind Labs-team) en plaats ze als inline-elementen in de knoppen van menu-items. Gebruik een consistente pictogramgrootte — h-4 w-4 voor kleine menu's — en zachtere kleuren die de actieve status van het item overnemen.
import {
PencilSquareIcon,
DocumentDuplicateIcon,
TrashIcon
} from '@heroicons/react/20/solid';
const menuItems = [
{ label: 'Edit', icon: PencilSquareIcon, action: handleEdit },
{ label: 'Duplicate', icon: DocumentDuplicateIcon, action: handleDuplicate }
];
menuItems.map(({ label, icon: Icon, action }) => (
<Menu.Item key={label}>
{({ active }) => (
<button
onClick={action}
className={cn(
'flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm',
active ? 'bg-blue-50 text-blue-700' : 'text-gray-700'
)}
>
<Icon className='h-4 w-4 flex-shrink-0' />
{label}
</button>
)}
</Menu.Item>
))Toetsenbordnavigatie in Headless Menu
Het Menu van Headless UI implementeert automatisch volledige ondersteuning voor het toetsenbord. Als je op Enter of Space drukt terwijl de knop focus heeft, wordt het menu geopend. Met pijltoetsen navigeer je tussen items. Met Home en End ga je naar het eerste en laatste item. Met Escape sluit je het menu en keert de focus terug naar de activering. Met Tab sluit je het menu. Dit gedrag volgt het ARIA-patroon voor menuknoppen en vereist geen extra code van jou.
/*
Keyboard behavior (automatic with Headless UI):
Trigger button:
- Enter / Space → opens menu, focuses first item
- ArrowDown → opens menu, focuses first item
- ArrowUp → opens menu, focuses last item
Inside menu:
- ArrowDown → next item
- ArrowUp → previous item
- Home → first item
- End → last item
- Enter / Space → activates focused item
- Escape → closes menu, returns focus to trigger
- Tab → closes menu
- Type a character → jump to first matching item
*/Patroon voor contextmenu's
Een contextmenu (menu na rechtsklikken) gebruikt dezelfde Menu-structuur, maar positioneert het paneel ten opzichte van de cursor in plaats van de knop die het menu activeert. Vang de gebeurtenis van het rechtsklikken op, sla de cursorpositie op en geef Menu.Items weer met een vaste positionering op die coördinaten. Headless UI regelt nog steeds het focusbeheer en de toetsenbordnavigatie binnen het gepositioneerde menu.
function ContextMenuWrapper({ children }) {
const [position, setPosition] = useState(null);
const handleContextMenu = (e) => {
e.preventDefault();
setPosition({ x: e.clientX, y: e.clientY });
};
return (
<div onContextMenu={handleContextMenu}>
{children}
{position && (
<Menu>
<Menu.Items
static // keep open without button
style={{ top: position.y, left: position.x }}
className='fixed z-50 w-48 rounded-xl bg-white shadow-xl ring-1 ring-black/5 p-1'
>
{/* menu items */}
</Menu.Items>
</Menu>
)}
</div>
);
}Popover versus menu: wanneer gebruik je welk onderdeel?
Headless UI biedt zowel Menu- als Popover-componenten. Gebruik Menu voor actielijsten waarin elke optie een actie uitvoert of naar een andere pagina navigeert. Gebruik Popover voor uitgebreidere inhoudspanelen — zoals een filterformulier, meldingenoverzicht of instellingenpaneel — wanneer de inhoud geen eenvoudige lijst met acties is en mogelijk andere interactieve elementen dan knoppen bevat.
// Menu: for action lists
<Menu>
<Menu.Button>Actions</Menu.Button>
<Menu.Items>
<Menu.Item>Edit</Menu.Item> {/* action */}
<Menu.Item>Delete</Menu.Item> {/* action */}
</Menu.Items>
</Menu>
// Popover: for rich content panels
import { Popover } from '@headlessui/react';
<Popover>
<Popover.Button>Filters</Popover.Button>
<Popover.Panel className='absolute z-10 mt-2 w-72 rounded-xl bg-white shadow-lg p-4'>
{/* Rich content: form inputs, sliders, checkboxes */}
<label>Price range</label>
<input type='range' />
<button>Apply filters</button>
</Popover.Panel>
</Popover>Voor mobiel responsief vervolgkeuzemenu
Op mobiele apparaten kan een traditioneel absoluut gepositioneerd vervolgkeuzemenu buiten de randen van het scherm vallen. Pas responsieve positionering toe op het paneel Menu.Items: gebruik right-0 op kleine schermen en left-0 op grotere schermen, of stap op mobiele apparaten over op een patroon met een paneel vanaf de onderkant met de prefix sm: van Tailwind. Test vervolgkeuzemenu's bij de schermbreedtes van mobiele apparaten om te controleren of ze volledig zichtbaar en bruikbaar blijven.
<Menu as='div' className='relative'>
<Menu.Button>...</Menu.Button>
<Menu.Items
className='
absolute z-20 mt-1 w-56
rounded-xl bg-white shadow-lg ring-1 ring-black/5 p-1
/* Mobile: right-aligned to avoid left-edge overflow */
right-0
/* Desktop: left-aligned for wider viewports */
sm:right-auto sm:left-0
'
>
{/* items */}
</Menu.Items>
</Menu>
/* Also consider: on very small screens,
render as a full-width bottom sheet instead */De volledige vervolgkeuzemenucomponent afronden
Als je alles samenvoegt, combineert een compleet, verzorgd vervolgkeuzemenu alle onderdelen — een gepositioneerde container, een gestylede activering met chevron, gegroepeerde items met pictogrammen, scheidingslijnen en een destructieve optie. Deze component is herbruikbaar, volledig met het toetsenbord te bedienen en volledig met Tailwind gestyled — zonder aangepaste CSS en met alle toegankelijkheid geregeld door Headless UI.
export function ActionMenu({ onEdit, onDelete }) {
return (
<Menu as='div' className='relative'>
<Menu.Button className='rounded-lg p-2 text-gray-500 hover:bg-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none'>
<EllipsisVerticalIcon className='h-5 w-5' />
</Menu.Button>
<Menu.Items className='absolute right-0 mt-1 w-48 rounded-xl bg-white shadow-lg ring-1 ring-black/5 p-1 focus:outline-none z-20'>
<Menu.Item>
{({ active }) => (
<button onClick={onEdit} className={cn('w-full flex items-center gap-2 px-3 py-2 text-sm rounded-lg', active ? 'bg-gray-100' : 'text-gray-700')}>
<PencilIcon className='h-4 w-4' />
Edit
</button>
)}
</Menu.Item>
<div className='my-1 border-t border-gray-100' />
<Menu.Item>
{({ active }) => (
<button onClick={onDelete} className={cn('w-full flex items-center gap-2 px-3 py-2 text-sm rounded-lg', active ? 'bg-red-50 text-red-700' : 'text-red-600')}>
<TrashIcon className='h-4 w-4' />
Delete
</button>
)}
</Menu.Item>
</Menu.Items>
</Menu>
);
}Korte controle
Test je begrip van de concepten uit Tailwind CSS Mastery in deze les.
Samenvatting van de les
In deze les heb je geleerd dat Menu.Button het vervolgkeuzemenu activeert met de renderprop open voor visuele feedback, dat Menu.Items absoluut wordt gepositioneerd met Tailwind en dat Menu.Item active de markeringsstijl voor de optie met focus bepaalt. Hierna bouwen we volledig toegankelijke dialoogvensters met de Dialog-component van Headless UI.
Leer HTML met een AI-tutor — gratis
Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.
- Cursussen
- 30
- Lessen
- 120
Veelgestelde vragen
Is de les “Headless Menu en Dropdown opmaken” gratis?
Ja — de volledige tekst van “Headless Menu en Dropdown opmaken” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus Tailwind CSS Academy wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus Tailwind CSS Academy bevat in totaal 4 lessen.
Wat leer ik in “Headless Menu en Dropdown opmaken”?
Pas Tailwind utility-classes toe op de Menu-component van Headless UI om een verzorgde dropdown te maken met toetsenbordnavigatie en ARIA-attributen. Je oefent met Tailwind CSS Academy door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.
Heb ik ervaring nodig om met Tailwind CSS Academy te beginnen?
Ervaring vooraf is niet nodig. Tailwind CSS Academy op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 2 van 4.
Hoe lang duurt de les “Headless Menu en Dropdown opmaken”?
De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.
Kan ik code schrijven en uitvoeren in deze les over Tailwind CSS Academy?
Ja. Elke les over Tailwind CSS Academy bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.
Alle lessen in deze cursus
- Introductie tot Headless UI
- Headless Menu en Dropdown opmaken
- Toegankelijke dialogen bouwen
- Transities met Headless UI