Skip to main content
Glama
dropdown-usage.md13.1 kB
# Dropdown Menu Component Usage ## Overview The dropdown menu component displays a list of actions or options in a floating panel triggered by a button. It supports keyboard navigation, submenus, separators, and icon integration. Dropdown menus use JavaScript for positioning and interaction. ## JavaScript Requirements ### Required Scripts ```html <!-- Include Basecoat CSS and JavaScript --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@latest/dist/basecoat.cdn.min.css"> <script src="https://cdn.jsdelivr.net/npm/basecoat-css@latest/dist/js/basecoat.min.js" defer></script> <script src="https://cdn.jsdelivr.net/npm/basecoat-css@latest/dist/js/dropdown-menu.min.js" defer></script> ``` ## HTML Structure ### Basic Dropdown Menu ```html <div class="dropdown-menu"> <button type="button" class="btn-outline" aria-haspopup="menu" aria-expanded="false"> Options <svg class="size-4 ml-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="m6 9 6 6 6-6"></path> </svg> </button> <nav role="menu"> <button type="button" role="menuitem">New File</button> <button type="button" role="menuitem">Open</button> <button type="button" role="menuitem">Save</button> <hr role="separator"> <button type="button" role="menuitem">Exit</button> </nav> </div> ``` ### Menu Items with Icons ```html <div class="dropdown-menu"> <button type="button" class="btn-outline" aria-haspopup="menu" aria-expanded="false"> Actions </button> <nav role="menu"> <button type="button" role="menuitem"> <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path> <polyline points="14 2 14 8 20 8"></polyline> </svg> New File </button> <button type="button" role="menuitem"> <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path> </svg> Open Folder </button> <button type="button" role="menuitem"> <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path> <polyline points="17 21 17 13 7 13 7 21"></polyline> <polyline points="7 3 7 8 15 8"></polyline> </svg> Save </button> </nav> </div> ``` ## Menu Item Types ### Standard Items ```html <button type="button" role="menuitem">Standard Item</button> <a href="/page" role="menuitem">Link Item</a> ``` ### Items with Keyboard Shortcuts ```html <button type="button" role="menuitem"> <svg class="size-4"><!-- icon --></svg> New File <kbd class="ml-auto text-muted-foreground tracking-widest">⌘N</kbd> </button> ``` ### Disabled Items ```html <button type="button" role="menuitem" aria-disabled="true"> Disabled Action </button> ``` ### Destructive Items ```html <button type="button" role="menuitem" class="text-destructive hover:bg-destructive/10 focus:bg-destructive/10 focus:text-destructive [&_svg]:!text-destructive"> <svg class="size-4"><!-- trash icon --></svg> Delete </button> ``` ### Checkbox Menu Items ```html <div role="menuitemcheckbox" aria-checked="true" tabindex="0"> <svg class="size-4"><!-- check icon when checked --></svg> Show Toolbar </div> ``` ### Radio Menu Items ```html <div role="group" aria-label="View options"> <div role="menuitemradio" aria-checked="true" tabindex="0"> <svg class="size-4"><!-- dot icon when selected --></svg> List View </div> <div role="menuitemradio" aria-checked="false" tabindex="-1"> <svg class="size-4 invisible"><!-- placeholder --></svg> Grid View </div> </div> ``` ## Implementation Examples ### File Menu ```html <div class="dropdown-menu"> <button type="button" class="btn-ghost" aria-haspopup="menu" aria-expanded="false"> File </button> <nav role="menu" class="min-w-48"> <button type="button" role="menuitem"> <svg class="size-4"><!-- file-plus icon --></svg> New <kbd class="ml-auto text-muted-foreground tracking-widest">⌘N</kbd> </button> <button type="button" role="menuitem"> <svg class="size-4"><!-- folder-open icon --></svg> Open <kbd class="ml-auto text-muted-foreground tracking-widest">⌘O</kbd> </button> <button type="button" role="menuitem"> <svg class="size-4"><!-- save icon --></svg> Save <kbd class="ml-auto text-muted-foreground tracking-widest">⌘S</kbd> </button> <button type="button" role="menuitem"> Save As... <kbd class="ml-auto text-muted-foreground tracking-widest">⇧⌘S</kbd> </button> <hr role="separator"> <button type="button" role="menuitem"> <svg class="size-4"><!-- printer icon --></svg> Print <kbd class="ml-auto text-muted-foreground tracking-widest">⌘P</kbd> </button> <hr role="separator"> <button type="button" role="menuitem"> Exit </button> </nav> </div> ``` ### Context Menu ```html <div class="dropdown-menu"> <button type="button" class="btn-icon-ghost" aria-haspopup="menu" aria-expanded="false" aria-label="More options"> <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <circle cx="12" cy="12" r="1"></circle> <circle cx="19" cy="12" r="1"></circle> <circle cx="5" cy="12" r="1"></circle> </svg> </button> <nav role="menu" data-align="end"> <button type="button" role="menuitem"> <svg class="size-4"><!-- edit icon --></svg> Edit </button> <button type="button" role="menuitem"> <svg class="size-4"><!-- copy icon --></svg> Duplicate </button> <button type="button" role="menuitem"> <svg class="size-4"><!-- share icon --></svg> Share </button> <hr role="separator"> <button type="button" role="menuitem"> <svg class="size-4"><!-- archive icon --></svg> Archive </button> <button type="button" role="menuitem" class="text-destructive hover:bg-destructive/10 focus:bg-destructive/10 [&_svg]:!text-destructive"> <svg class="size-4"><!-- trash icon --></svg> Delete </button> </nav> </div> ``` ### User Menu ```html <div class="dropdown-menu"> <button type="button" class="flex items-center gap-2" aria-haspopup="menu" aria-expanded="false"> <img src="/avatar.png" alt="" class="size-8 rounded-full"> <span class="hidden sm:inline">John Doe</span> <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="m6 9 6 6 6-6"></path> </svg> </button> <nav role="menu" class="w-56" data-align="end"> <div class="px-2 py-1.5"> <p class="text-sm font-medium">John Doe</p> <p class="text-xs text-muted-foreground">john@example.com</p> </div> <hr role="separator"> <a href="/profile" role="menuitem"> <svg class="size-4"><!-- user icon --></svg> Profile <kbd class="ml-auto text-muted-foreground tracking-widest">⇧⌘P</kbd> </a> <a href="/settings" role="menuitem"> <svg class="size-4"><!-- settings icon --></svg> Settings <kbd class="ml-auto text-muted-foreground tracking-widest">⌘,</kbd> </a> <a href="/billing" role="menuitem"> <svg class="size-4"><!-- credit-card icon --></svg> Billing </a> <hr role="separator"> <a href="/team" role="menuitem"> <svg class="size-4"><!-- users icon --></svg> Team </a> <a href="/invite" role="menuitem"> <svg class="size-4"><!-- user-plus icon --></svg> Invite users </a> <hr role="separator"> <a href="/docs" role="menuitem"> <svg class="size-4"><!-- book icon --></svg> Documentation </a> <a href="/support" role="menuitem"> <svg class="size-4"><!-- help-circle icon --></svg> Support </a> <hr role="separator"> <button type="button" role="menuitem"> <svg class="size-4"><!-- log-out icon --></svg> Log out <kbd class="ml-auto text-muted-foreground tracking-widest">⇧⌘Q</kbd> </button> </nav> </div> ``` ### Menu with Groups ```html <div class="dropdown-menu"> <button type="button" class="btn-outline" aria-haspopup="menu" aria-expanded="false"> Insert </button> <nav role="menu" class="min-w-48"> <div role="group" aria-labelledby="text-group"> <span role="heading" id="text-group" class="px-2 py-1.5 text-xs font-medium text-muted-foreground">Text</span> <button type="button" role="menuitem">Heading</button> <button type="button" role="menuitem">Paragraph</button> <button type="button" role="menuitem">Quote</button> </div> <hr role="separator"> <div role="group" aria-labelledby="media-group"> <span role="heading" id="media-group" class="px-2 py-1.5 text-xs font-medium text-muted-foreground">Media</span> <button type="button" role="menuitem">Image</button> <button type="button" role="menuitem">Video</button> <button type="button" role="menuitem">Audio</button> </div> </nav> </div> ``` ## Positioning ### Position and Alignment ```html <!-- Bottom aligned to end (common for user menus) --> <nav role="menu" data-side="bottom" data-align="end"> <!-- menu items --> </nav> <!-- Top aligned to start --> <nav role="menu" data-side="top" data-align="start"> <!-- menu items --> </nav> ``` ## JavaScript API ### Handling Menu Item Clicks ```javascript document.querySelectorAll('[role="menuitem"]').forEach(item => { item.addEventListener('click', function(e) { const action = this.textContent.trim(); console.log('Menu action:', action); // Menu closes automatically after click }); }); ``` ### Checkbox Menu Items ```javascript document.querySelectorAll('[role="menuitemcheckbox"]').forEach(item => { item.addEventListener('click', function() { const isChecked = this.getAttribute('aria-checked') === 'true'; this.setAttribute('aria-checked', !isChecked); }); }); ``` ### Radio Menu Items ```javascript document.querySelectorAll('[role="group"]').forEach(group => { const radios = group.querySelectorAll('[role="menuitemradio"]'); radios.forEach(radio => { radio.addEventListener('click', function() { radios.forEach(r => r.setAttribute('aria-checked', 'false')); this.setAttribute('aria-checked', 'true'); }); }); }); ``` ## Accessibility Guidelines ### Required ARIA Attributes ```html <div class="dropdown-menu"> <button type="button" id="menu-trigger" aria-haspopup="menu" aria-expanded="false" aria-controls="menu-list" > Options </button> <nav role="menu" id="menu-list" aria-labelledby="menu-trigger"> <button type="button" role="menuitem" tabindex="-1">Item 1</button> <button type="button" role="menuitem" tabindex="-1">Item 2</button> </nav> </div> ``` ### Keyboard Navigation - **Enter/Space**: Open menu, activate item - **Arrow Down**: Move to next item - **Arrow Up**: Move to previous item - **Home**: Move to first item - **End**: Move to last item - **Escape**: Close menu - **Type character**: Jump to item starting with that character ## Best Practices ### When to Use Dropdown Menus - Actions on an item (edit, delete, share) - Navigation submenus - User account menus - Settings menus - Context menus ### When to Use Alternatives - Simple selection: Use Select - Yes/No confirmation: Use Alert Dialog - Complex content: Use Popover or Dialog ### Content Guidelines - Keep menu items concise - Use icons consistently - Group related items - Order items logically - Include keyboard shortcuts for common actions ### Visual Guidelines - Maintain consistent item height - Use adequate touch targets on mobile - Indicate destructive actions clearly - Show disabled states appropriately ## Jinja/Nunjucks Macro ### Using the Macro ```jinja {% from "dropdown-menu.njk" import dropdown_menu %} {% set trigger %} Options <svg><!-- chevron-down --></svg> {% endset %} {% call dropdown_menu( trigger=trigger, trigger_attrs={"class": "btn-outline"}, popover_attrs={"data-align": "end"} ) %} <button type="button" role="menuitem"> <svg><!-- icon --></svg> Edit </button> <button type="button" role="menuitem"> <svg><!-- icon --></svg> Duplicate </button> <hr role="separator"> <button type="button" role="menuitem" class="text-destructive"> <svg><!-- icon --></svg> Delete </button> {% endcall %} ``` ### Using Items Array ```jinja {{ dropdown_menu( trigger="Actions", trigger_attrs={"class": "btn-outline"}, items=[ { type: "item", label: "Edit", icon: icon_edit }, { type: "item", label: "Duplicate", icon: icon_copy }, { type: "separator" }, { type: "item", label: "Delete", destructive: true, icon: icon_trash } ] ) }} ```

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/GustavoGomezPG/basecoat-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server