Skip to main content
Glama
popover-usage.md13.5 kB
# Popover Component Usage ## Overview The popover component displays rich content in a floating panel triggered by a button click. Unlike tooltips, popovers can contain interactive elements, forms, and complex layouts. Popovers use JavaScript for positioning and toggle behavior. ## 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/popover.min.js" defer></script> ``` ## HTML Structure ### Basic Popover ```html <div class="popover"> <button type="button" class="btn-outline" aria-haspopup="dialog" aria-expanded="false"> Open Popover </button> <div role="dialog" aria-label="Popover content"> <p>This is the popover content.</p> </div> </div> ``` ### Popover with Header ```html <div class="popover"> <button type="button" class="btn-outline" aria-haspopup="dialog" aria-expanded="false"> Open </button> <div role="dialog" aria-labelledby="popover-title" class="w-80"> <header class="border-b px-4 py-3"> <h3 id="popover-title" class="font-medium">Popover Title</h3> </header> <section class="p-4"> <p class="text-sm text-muted-foreground">This is the popover content with a header.</p> </section> </div> </div> ``` ## Positioning ### Positions ```html <!-- Top (default) --> <div class="popover"> <button type="button" class="btn-outline">Top</button> <div role="dialog" data-side="top">Content above</div> </div> <!-- Bottom --> <div class="popover"> <button type="button" class="btn-outline">Bottom</button> <div role="dialog" data-side="bottom">Content below</div> </div> <!-- Left --> <div class="popover"> <button type="button" class="btn-outline">Left</button> <div role="dialog" data-side="left">Content to the left</div> </div> <!-- Right --> <div class="popover"> <button type="button" class="btn-outline">Right</button> <div role="dialog" data-side="right">Content to the right</div> </div> ``` ### Alignment ```html <!-- Start aligned --> <div class="popover"> <button type="button" class="btn-outline">Start</button> <div role="dialog" data-align="start">Aligned to start</div> </div> <!-- Center aligned (default) --> <div class="popover"> <button type="button" class="btn-outline">Center</button> <div role="dialog" data-align="center">Centered</div> </div> <!-- End aligned --> <div class="popover"> <button type="button" class="btn-outline">End</button> <div role="dialog" data-align="end">Aligned to end</div> </div> ``` ## Implementation Examples ### User Profile Popover ```html <div class="popover"> <button type="button" class="flex items-center gap-2" aria-haspopup="dialog" aria-expanded="false"> <img src="/avatar.png" alt="John Doe" 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> <div role="dialog" aria-label="User menu" class="w-64 p-0" data-align="end"> <header class="p-4 border-b"> <div class="flex items-center gap-3"> <img src="/avatar.png" alt="" class="size-10 rounded-full"> <div> <p class="font-medium">John Doe</p> <p class="text-sm text-muted-foreground">john@example.com</p> </div> </div> </header> <nav class="p-2"> <a href="/profile" class="block px-3 py-2 rounded hover:bg-accent">Profile</a> <a href="/settings" class="block px-3 py-2 rounded hover:bg-accent">Settings</a> <a href="/billing" class="block px-3 py-2 rounded hover:bg-accent">Billing</a> <hr class="my-2"> <button type="button" class="w-full text-left px-3 py-2 rounded hover:bg-accent text-destructive"> Sign out </button> </nav> </div> </div> ``` ### Date Picker Trigger ```html <div class="popover"> <button type="button" class="btn-outline" aria-haspopup="dialog" aria-expanded="false"> <svg class="size-4 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M8 2v4"></path> <path d="M16 2v4"></path> <rect width="18" height="18" x="3" y="4" rx="2"></rect> <path d="M3 10h18"></path> </svg> <span id="selected-date">Select date</span> </button> <div role="dialog" aria-label="Date picker" class="w-auto p-4"> <!-- Calendar component would go here --> <div class="grid grid-cols-7 gap-1 text-center text-sm"> <div class="font-medium text-muted-foreground">Su</div> <div class="font-medium text-muted-foreground">Mo</div> <div class="font-medium text-muted-foreground">Tu</div> <div class="font-medium text-muted-foreground">We</div> <div class="font-medium text-muted-foreground">Th</div> <div class="font-medium text-muted-foreground">Fr</div> <div class="font-medium text-muted-foreground">Sa</div> <!-- Days... --> </div> </div> </div> ``` ### Filter Popover ```html <div class="popover"> <button type="button" class="btn-outline" aria-haspopup="dialog" aria-expanded="false"> <svg class="size-4 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon> </svg> Filters <span class="badge-secondary ml-2">3</span> </button> <div role="dialog" aria-labelledby="filter-title" class="w-80 p-0" data-align="start"> <header class="flex items-center justify-between p-4 border-b"> <h3 id="filter-title" class="font-medium">Filters</h3> <button type="button" class="text-sm text-muted-foreground hover:text-foreground"> Clear all </button> </header> <section class="p-4 space-y-4"> <div class="grid gap-2"> <label class="label">Status</label> <select class="input"> <option value="">All</option> <option value="active">Active</option> <option value="pending">Pending</option> <option value="completed">Completed</option> </select> </div> <div class="grid gap-2"> <label class="label">Date Range</label> <div class="flex gap-2"> <input type="date" class="input flex-1"> <input type="date" class="input flex-1"> </div> </div> <div class="space-y-2"> <label class="label">Categories</label> <label class="label gap-2 font-normal"> <input type="checkbox" class="input"> Technology </label> <label class="label gap-2 font-normal"> <input type="checkbox" class="input"> Design </label> <label class="label gap-2 font-normal"> <input type="checkbox" class="input"> Marketing </label> </div> </section> <footer class="flex justify-end gap-2 p-4 border-t"> <button type="button" class="btn-outline">Cancel</button> <button type="button" class="btn">Apply Filters</button> </footer> </div> </div> ``` ### Share Popover ```html <div class="popover"> <button type="button" class="btn-icon-outline" aria-haspopup="dialog" aria-expanded="false" aria-label="Share"> <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <circle cx="18" cy="5" r="3"></circle> <circle cx="6" cy="12" r="3"></circle> <circle cx="18" cy="19" r="3"></circle> <line x1="8.59" x2="15.42" y1="13.51" y2="17.49"></line> <line x1="15.41" x2="8.59" y1="6.51" y2="10.49"></line> </svg> </button> <div role="dialog" aria-label="Share options" class="w-72 p-4" data-side="bottom" data-align="end"> <h3 class="font-medium mb-3">Share this page</h3> <div class="flex gap-2 mb-4"> <button class="btn-icon-outline flex-1" title="Share on Twitter"> <svg class="size-4"><!-- twitter icon --></svg> </button> <button class="btn-icon-outline flex-1" title="Share on Facebook"> <svg class="size-4"><!-- facebook icon --></svg> </button> <button class="btn-icon-outline flex-1" title="Share on LinkedIn"> <svg class="size-4"><!-- linkedin icon --></svg> </button> <button class="btn-icon-outline flex-1" title="Share via Email"> <svg class="size-4"><!-- mail icon --></svg> </button> </div> <div class="relative"> <input type="text" readonly class="input pr-20" value="https://example.com/page"> <button class="absolute right-1.5 top-1/2 -translate-y-1/2 btn-sm-secondary"> Copy </button> </div> </div> </div> ``` ### Settings Popover ```html <div class="popover"> <button type="button" class="btn-icon-ghost" aria-haspopup="dialog" aria-expanded="false" aria-label="Settings"> <svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path> <circle cx="12" cy="12" r="3"></circle> </svg> </button> <div role="dialog" aria-labelledby="settings-title" class="w-72 p-0" data-align="end"> <header class="px-4 py-3 border-b"> <h3 id="settings-title" class="font-medium">Display Settings</h3> </header> <section class="p-4 space-y-4"> <div class="flex items-center justify-between"> <label for="dark-mode" class="text-sm">Dark Mode</label> <input type="checkbox" role="switch" id="dark-mode" class="input"> </div> <div class="flex items-center justify-between"> <label for="compact" class="text-sm">Compact View</label> <input type="checkbox" role="switch" id="compact" class="input"> </div> <div class="grid gap-2"> <label class="text-sm">Font Size</label> <input type="range" class="slider" min="12" max="20" value="16"> </div> </section> </div> </div> ``` ## JavaScript API ### Programmatic Control ```javascript // The popover toggles automatically on button click // You can also control it programmatically: const popover = document.querySelector('.popover'); const trigger = popover.querySelector('button'); const content = popover.querySelector('[role="dialog"]'); // Check if open const isOpen = trigger.getAttribute('aria-expanded') === 'true'; // Toggle trigger.click(); // Close on outside click is handled automatically ``` ### Close Popover from Inside ```javascript // Button inside popover that closes it document.querySelector('[data-close-popover]').addEventListener('click', function() { const popover = this.closest('.popover'); const trigger = popover.querySelector('[aria-haspopup]'); trigger.click(); // Toggle to close }); ``` ## Accessibility Guidelines ### Required Attributes ```html <div class="popover"> <button type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="popover-content" > Open </button> <div role="dialog" id="popover-content" aria-labelledby="popover-heading" > <h3 id="popover-heading">Title</h3> <p>Content...</p> </div> </div> ``` ### Keyboard Navigation - **Enter/Space**: Open/close popover - **Escape**: Close popover - **Tab**: Navigate through focusable elements inside - Focus is trapped within the popover when open ### Focus Management ```javascript // Focus first focusable element when opened popover.addEventListener('toggle', function(e) { if (e.target.getAttribute('aria-expanded') === 'true') { const firstFocusable = content.querySelector('button, input, select, textarea, a[href]'); if (firstFocusable) { firstFocusable.focus(); } } }); ``` ## Best Practices ### When to Use Popovers - User profile menus - Filter panels - Settings panels - Share dialogs - Quick actions - Additional options ### When to Use Alternatives - Simple hover information: Use Tooltip - Critical confirmations: Use Alert Dialog - Complex forms: Use Dialog/Modal - Navigation menus: Use Dropdown Menu ### Content Guidelines - Keep content focused and concise - Include a clear way to close (click outside or close button) - Use headers to provide context - Group related actions ### Visual Guidelines - Position to avoid viewport edges - Use consistent spacing - Maintain visual hierarchy - Ensure adequate contrast ## Jinja/Nunjucks Macro ### Using the Macro ```jinja {% from "popover.njk" import popover %} {% set trigger %} <svg><!-- icon --></svg> Settings {% endset %} {% call popover( trigger=trigger, trigger_attrs={"class": "btn-outline"}, popover_attrs={"class": "w-72", "data-align": "end"} ) %} <h3 class="font-medium mb-3">Settings</h3> <p>Popover content here...</p> {% endcall %} ```

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