Popover
A non-modal floating panel with focus management, Escape-to-close, and group coordination.
Basic Popover
Click the button to open the panel. Focus moves into the panel automatically. Press Escape or click outside to close — focus returns to the button. Unlike a Dialog, the rest of the page stays interactive.
bit Bheadless
A headless component library for Blazor. Zero styles, full accessibility, complete control over your design.
View on GitHub →<BitBhPopover class="popover">
<BitBhPopoverButton class="popover-button">
<Template Context="b">
Learn more
<span class="popover-chevron @(b.IsOpen ? "open" : "")">▾</span>
</Template>
</BitBhPopoverButton>
<BitBhPopoverPanel class="popover-panel">
<Template Context="p">
<h3 class="popover-title">bit Bheadless</h3>
<p class="popover-body">
A headless component library for Blazor. Zero styles, full accessibility,
complete control over your design.
</p>
<a href="https://github.com/bitfoundation/bitplatform/tree/develop/src/Utilities/Bheadless" target="_blank" class="popover-link">
View on GitHub →
</a>
@* BitBhPopoverButton inside the panel acts as a Close button *@
<BitBhPopoverButton class="popover-close-btn">Close</BitBhPopoverButton>
</Template>
</BitBhPopoverPanel>
</BitBhPopover>/* All styles are consumer-authored — the library ships zero CSS */
.popover { position: relative; display: inline-block; }
.popover-button {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 0.9rem;
border: 1px solid #d4d4d8;
border-radius: 0.5rem;
background: #fff;
font-size: 0.9rem;
font-family: inherit;
cursor: pointer;
transition: border-color 120ms ease;
}
.popover-button:hover { border-color: #a1a1af; }
.popover-button:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
.popover-button[data-state="open"] { border-color: #2563eb; }
.popover-chevron { transition: transform 200ms ease; color: #71717a; }
.popover-chevron.open { transform: rotate(180deg); }
/* Panel — sits above the click-outside overlay (z-index: 30 inline) */
.popover-panel {
position: absolute;
top: calc(100% + 0.5rem);
left: 0;
z-index: 31;
min-width: 16rem;
background: #fff;
border: 1px solid #e4e4e7;
border-radius: 0.5rem;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.6rem;
}
.popover-title { font-size: 0.95rem; font-weight: 600; color: #18181b; margin: 0; }
.popover-body { font-size: 0.875rem; color: #71717a; margin: 0; }
.popover-link { font-size: 0.875rem; color: #2563eb; text-decoration: none; }
.popover-link:hover { text-decoration: underline; }
.popover-close-btn {
all: unset;
font-size: 0.8rem;
color: #71717a;
cursor: pointer;
text-decoration: underline;
text-underline-offset: 2px;
}Popover Group (mutual exclusion)
Wrap multiple popovers in an BitBhPopoverGroup so opening one
automatically closes the others. Useful for navigation bars.
Products
Solutions
<BitBhPopoverGroup class="popover-group">
<BitBhPopover class="popover">
<BitBhPopoverButton class="popover-button">
<Template Context="b">
Products
<span class="popover-chevron @(b.IsOpen ? "open" : "")">▾</span>
</Template>
</BitBhPopoverButton>
<BitBhPopoverPanel class="popover-panel">
<h3 class="popover-title">Products</h3>
<ul class="popover-nav-list">
<li><a href="#" class="popover-nav-link">Analytics</a></li>
<li><a href="#" class="popover-nav-link">Automation</a></li>
<li><a href="#" class="popover-nav-link">Commerce</a></li>
</ul>
</BitBhPopoverPanel>
</BitBhPopover>
<BitBhPopover class="popover">
<BitBhPopoverButton class="popover-button">
<Template Context="b">
Solutions
<span class="popover-chevron @(b.IsOpen ? "open" : "")">▾</span>
</Template>
</BitBhPopoverButton>
<BitBhPopoverPanel class="popover-panel">
<h3 class="popover-title">Solutions</h3>
<ul class="popover-nav-list">
<li><a href="#" class="popover-nav-link">Marketing</a></li>
<li><a href="#" class="popover-nav-link">Engineering</a></li>
<li><a href="#" class="popover-nav-link">Finance</a></li>
</ul>
</BitBhPopoverPanel>
</BitBhPopover>
<BitBhPopover class="popover">
<BitBhPopoverButton class="popover-button">
<Template Context="b">
Company
<span class="popover-chevron @(b.IsOpen ? "open" : "")">▾</span>
</Template>
</BitBhPopoverButton>
<BitBhPopoverPanel class="popover-panel">
<h3 class="popover-title">Company</h3>
<ul class="popover-nav-list">
<li><a href="#" class="popover-nav-link">About</a></li>
<li><a href="#" class="popover-nav-link">Blog</a></li>
<li><a href="#" class="popover-nav-link">Careers</a></li>
</ul>
</BitBhPopoverPanel>
</BitBhPopover>
</BitBhPopoverGroup>
@* Opening one popover automatically closes the others. *@/* All styles are consumer-authored — the library ships zero CSS */
.popover { position: relative; display: inline-block; }
.popover-button {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 0.9rem;
border: 1px solid #d4d4d8;
border-radius: 0.5rem;
background: #fff;
font-size: 0.9rem;
font-family: inherit;
cursor: pointer;
transition: border-color 120ms ease;
}
.popover-button:hover { border-color: #a1a1af; }
.popover-button:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
.popover-button[data-state="open"] { border-color: #2563eb; }
.popover-chevron { transition: transform 200ms ease; color: #71717a; }
.popover-chevron.open { transform: rotate(180deg); }
/* Panel — sits above the click-outside overlay (z-index: 30 inline) */
.popover-panel {
position: absolute;
top: calc(100% + 0.5rem);
left: 0;
z-index: 31;
min-width: 16rem;
background: #fff;
border: 1px solid #e4e4e7;
border-radius: 0.5rem;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.6rem;
}
.popover-title { font-size: 0.95rem; font-weight: 600; color: #18181b; margin: 0; }
.popover-body { font-size: 0.875rem; color: #71717a; margin: 0; }
.popover-link { font-size: 0.875rem; color: #2563eb; text-decoration: none; }
.popover-link:hover { text-decoration: underline; }
.popover-close-btn {
all: unset;
font-size: 0.8rem;
color: #71717a;
cursor: pointer;
text-decoration: underline;
text-underline-offset: 2px;
}
/* Wrap popovers in a flex row for a nav-bar layout */
.popover-group {
display: flex;
gap: 0.5rem;
align-items: center;
}
/* Opening one popover closes the others — no extra CSS needed. */Controlled
Drive the open state externally with Open and
OnOpenChange. The button below opens or closes the popover
programmatically.
Either trigger updates the same state — fully controlled.
<div style="display:flex; flex-direction:column; gap:1rem; align-items:flex-start;">
<div class="demo-row">
<BitBhButton OnClick="ToggleControlled" class="btn">
Toggle from outside
</BitBhButton>
<span class="demo-output">Open: <strong>@controlledOpen.ToString().ToLowerInvariant()</strong></span>
</div>
<BitBhPopover Open="controlledOpen"
OnOpenChange="v => controlledOpen = v"
class="popover">
<BitBhPopoverButton class="popover-button">
<Template Context="b">
Internal trigger
<span class="popover-chevron @(b.IsOpen ? "open" : "")">▾</span>
</Template>
</BitBhPopoverButton>
<BitBhPopoverPanel class="popover-panel">
<p class="popover-body">
Either trigger updates the same state — fully controlled.
</p>
</BitBhPopoverPanel>
</BitBhPopover>
</div>
@code {
private bool controlledOpen;
private void ToggleControlled() => controlledOpen = !controlledOpen;
}/* All styles are consumer-authored — the library ships zero CSS */
.popover { position: relative; display: inline-block; }
.popover-button {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 0.9rem;
border: 1px solid #d4d4d8;
border-radius: 0.5rem;
background: #fff;
font-size: 0.9rem;
font-family: inherit;
cursor: pointer;
transition: border-color 120ms ease;
}
.popover-button:hover { border-color: #a1a1af; }
.popover-button:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
.popover-button[data-state="open"] { border-color: #2563eb; }
.popover-chevron { transition: transform 200ms ease; color: #71717a; }
.popover-chevron.open { transform: rotate(180deg); }
/* Panel — sits above the click-outside overlay (z-index: 30 inline) */
.popover-panel {
position: absolute;
top: calc(100% + 0.5rem);
left: 0;
z-index: 31;
min-width: 16rem;
background: #fff;
border: 1px solid #e4e4e7;
border-radius: 0.5rem;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.6rem;
}
.popover-title { font-size: 0.95rem; font-weight: 600; color: #18181b; margin: 0; }
.popover-body { font-size: 0.875rem; color: #71717a; margin: 0; }
.popover-link { font-size: 0.875rem; color: #2563eb; text-decoration: none; }
.popover-link:hover { text-decoration: underline; }
.popover-close-btn {
all: unset;
font-size: 0.8rem;
color: #71717a;
cursor: pointer;
text-decoration: underline;
text-underline-offset: 2px;
}
/* Controlled mode adds no new CSS hooks — data-state simply reflects
whatever Open value you bind to. */
.demo-row { display: flex; gap: 0.75rem; align-items: center; }
.demo-output { font-size: 0.85rem; color: #71717a; }
.btn {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 0.9rem;
border: 1px solid #e4e4e7;
border-radius: 0.5rem;
background: #fff;
font-size: 0.9rem;
font-weight: 500;
cursor: pointer;
}
.btn:hover { background: #f4f4f5; }Anchor Positioning
Set Anchor on BitBhPopoverPanel to automatically position
the panel relative to the button. The panel uses position: fixed,
flips when there's not enough space, and auto-updates on scroll/resize.
No position: relative on the parent or position: absolute
on the panel needed.
This panel is anchored to bottom start with an 8px gap. It will flip to the top if there's not enough space below.
This panel is anchored to top end. It flips to the bottom if there's not enough space above.
This panel is anchored to the right side.
@* Anchor positions the panel relative to the button automatically.
Supports all sides: top, right, bottom, left — with start/end alignment. *@
<BitBhPopover class="popover-anchored">
<BitBhPopoverButton class="popover-button">
<Template Context="b">
Bottom Start ▾
</Template>
</BitBhPopoverButton>
<BitBhPopoverPanel Anchor="@(new BitBhAnchorOptions { To = "bottom start", Gap = 8 })"
class="popover-panel-anchored">
<Template Context="p">
<p>This panel is anchored with auto-flip and shift.</p>
</Template>
</BitBhPopoverPanel>
</BitBhPopover>
@* Available placements: top, top start, top end,
bottom, bottom start, bottom end,
left, left start, left end,
right, right start, right end *@/* All styles are consumer-authored — the library ships zero CSS */
.popover-button {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 0.9rem;
border: 1px solid #d4d4d8;
border-radius: 0.5rem;
background: #fff;
font-size: 0.9rem;
font-family: inherit;
cursor: pointer;
transition: border-color 120ms ease;
}
.popover-button:hover { border-color: #a1a1af; }
.popover-button:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
.popover-button[data-state="open"] { border-color: #2563eb; }
.popover-chevron { transition: transform 200ms ease; color: #71717a; }
.popover-chevron.open { transform: rotate(180deg); }
.popover-body { font-size: 0.875rem; color: #71717a; margin: 0; }
/* When using Anchor, the panel is position:fixed by the JS engine.
No position:relative on parent needed. */
.popover-anchored { display: inline-block; }
.popover-panel-anchored {
z-index: 31;
min-width: 16rem;
background: #fff;
border: 1px solid #e4e4e7;
border-radius: 0.5rem;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
padding: 1rem;
}Rich panel content
The panel can contain any content — forms, images, links, nested interactive elements. Tab navigates freely inside and out (no focus trap).
Notifications
3 new-
Alice commented on your post 2 minutes ago
-
Your export is ready 15 minutes ago
-
New team member joined 1 hour ago
<BitBhPopover class="popover">
<BitBhPopoverButton class="popover-button">
<Template Context="b">
Notifications
<span class="popover-badge">3</span>
<span class="popover-chevron @(b.IsOpen ? "open" : "")">▾</span>
</Template>
</BitBhPopoverButton>
<BitBhPopoverPanel class="popover-panel popover-panel-wide">
<div class="popover-header">
<h3 class="popover-title">Notifications</h3>
<span class="popover-badge">3 new</span>
</div>
<ul class="notification-list">
<li class="notification-item">
<span class="notification-dot"></span>
<div>
<strong>Alice commented on your post</strong>
<span>2 minutes ago</span>
</div>
</li>
<li class="notification-item">
<span class="notification-dot"></span>
<div>
<strong>Your export is ready</strong>
<span>15 minutes ago</span>
</div>
</li>
<li class="notification-item">
<span class="notification-dot"></span>
<div>
<strong>New team member joined</strong>
<span>1 hour ago</span>
</div>
</li>
</ul>
<a href="#" class="popover-footer-link">View all notifications →</a>
</BitBhPopoverPanel>
</BitBhPopover>/* All styles are consumer-authored — the library ships zero CSS */
.popover { position: relative; display: inline-block; }
.popover-button {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 0.9rem;
border: 1px solid #d4d4d8;
border-radius: 0.5rem;
background: #fff;
font-size: 0.9rem;
font-family: inherit;
cursor: pointer;
transition: border-color 120ms ease;
}
.popover-button:hover { border-color: #a1a1af; }
.popover-button:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
.popover-button[data-state="open"] { border-color: #2563eb; }
.popover-chevron { transition: transform 200ms ease; color: #71717a; }
.popover-chevron.open { transform: rotate(180deg); }
/* Panel — sits above the click-outside overlay (z-index: 30 inline) */
.popover-panel {
position: absolute;
top: calc(100% + 0.5rem);
left: 0;
z-index: 31;
min-width: 16rem;
background: #fff;
border: 1px solid #e4e4e7;
border-radius: 0.5rem;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.6rem;
}
.popover-title { font-size: 0.95rem; font-weight: 600; color: #18181b; margin: 0; }
.popover-body { font-size: 0.875rem; color: #71717a; margin: 0; }
.popover-panel-wide { min-width: 22rem; padding: 0; overflow: hidden; }
.popover-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
border-bottom: 1px solid #e4e4e7;
}
.popover-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.1rem 0.45rem;
background: #2563eb;
color: #fff;
border-radius: 999px;
font-size: 0.7rem;
font-weight: 700;
}
.notification-list {
list-style: none;
margin: 0;
padding: 0.25rem 0;
}
.notification-item {
display: flex;
align-items: flex-start;
gap: 0.65rem;
padding: 0.6rem 1rem;
transition: background 120ms ease;
}
.notification-item:hover { background: #f4f4f5; }
.notification-dot {
width: 0.5rem;
height: 0.5rem;
background: #2563eb;
border-radius: 999px;
flex-shrink: 0;
margin-top: 0.3rem;
}
.notification-item div {
display: flex;
flex-direction: column;
gap: 0.1rem;
}
.notification-item strong { font-size: 0.85rem; color: #18181b; }
.notification-item span { font-size: 0.75rem; color: #71717a; }
.popover-footer-link {
display: block;
padding: 0.6rem 1rem;
font-size: 0.85rem;
color: #2563eb;
text-decoration: none;
border-top: 1px solid #e4e4e7;
text-align: center;
}
.popover-footer-link:hover { background: #f4f4f5; }