Popover Component
August 15, 2025 ยท View on GitHub
An enterprise-grade floating content container with intelligent positioning using Angular CDK Portal. Perfect for tooltips, dropdowns, contextual information, and interactive overlays.
Features
- ๐ฏ Intelligent Positioning - Enhanced collision detection with 30+ fallback positions for edge cases
- โก CDK Portal Architecture - Professional positioning that escapes stacking contexts with improved viewport handling
- ๐จ Multiple Variants - Default, Success, Warning, Error, and Info styles
- ๐ Flexible Sizing - Small, Default, Large, and Extra Large options
- ๐งญ Directional Control - Top, Right, Bottom, Left positioning with Start/Center/End alignment
- โฟ Accessibility Ready - ARIA attributes, keyboard navigation, and focus management
- ๐ญ Modal Support - Optional modal behavior with backdrop
- โจ Beautiful Animations - Smooth fade-in and zoom effects
- ๐น Arrow Indicators - Optional arrow pointing to trigger element
- โ๏ธ Highly Configurable - Extensive customization options
- ๐ Enhanced Edge Case Handling - Improved corner positioning and viewport margin optimization
- ๐ Smart Auto-Hide - Automatically hides when trigger scrolls out of view for better UX
- ๐ฏ Enhanced Scroll Handling - Immediate repositioning during scroll events with intersection observer support
Installation
Install the component using our CLI tool:
npx ngsui-cli add popover
This will automatically:
- Add the Popover component to your project
- Update your
app.config.tswith required providers - Install necessary dependencies
- Configure Tailwind CSS classes
Basic Usage
import { Component, signal, ElementRef, ViewChild } from '@angular/core';
import { Popover } from '@angular-superui/lib';
@Component({
selector: 'app-example',
standalone: true,
imports: [Popover],
template: `
<button
#trigger
(click)="isOpen.set(!isOpen())"
class="px-4 py-2 bg-blue-600 text-white rounded-lg"
>
{{ isOpen() ? 'Close' : 'Open' }} Popover
</button>
<Popover
[isOpen]="isOpen()"
[triggerElement]="trigger"
(openChange)="isOpen.set($event)"
>
<div>
<h3 class="font-semibold mb-2">Welcome!</h3>
<p class="text-sm">This is a basic popover with default settings.</p>
</div>
</Popover>
`
})
export class ExampleComponent {
readonly isOpen = signal(false);
}
Advanced Examples
Positioning and Alignment
@Component({
template: `
<!-- Top positioned -->
<Popover
[isOpen]="topOpen()"
[triggerElement]="topTrigger"
side="top"
align="center"
>
<div>Top popover content</div>
</Popover>
<!-- Right positioned with start alignment -->
<Popover
[isOpen]="rightOpen()"
[triggerElement]="rightTrigger"
side="right"
align="start"
>
<div>Right aligned to start</div>
</Popover>
`
})
export class PositioningExample {}
Visual Variants
@Component({
template: `
<!-- Success variant -->
<Popover
[isOpen]="successOpen()"
[triggerElement]="successTrigger"
variant="success"
>
<div>
<h3>Success!</h3>
<p>Operation completed successfully.</p>
</div>
</Popover>
<!-- Error variant -->
<Popover
[isOpen]="errorOpen()"
[triggerElement]="errorTrigger"
variant="error"
>
<div>
<h3>Error</h3>
<p>Something went wrong.</p>
</div>
</Popover>
`
})
export class VariantsExample {}
Different Sizes
@Component({
template: `
<!-- Small popover -->
<Popover
[isOpen]="smallOpen()"
[triggerElement]="smallTrigger"
size="sm"
>
<div>Small popover content</div>
</Popover>
<!-- Large popover -->
<Popover
[isOpen]="largeOpen()"
[triggerElement]="largeTrigger"
size="lg"
>
<div>
<h3>Large Popover</h3>
<p>More space for detailed content.</p>
</div>
</Popover>
`
})
export class SizesExample {}
Modal Behavior with Backdrop
@Component({
template: `
<Popover
[isOpen]="modalOpen()"
[triggerElement]="modalTrigger"
[modal]="true"
[showBackdrop]="true"
[showCloseButton]="true"
>
<div>
<h3>Modal Popover</h3>
<p>This popover behaves like a modal with backdrop.</p>
</div>
</Popover>
`
})
export class ModalExample {}
Form in Popover
@Component({
template: `
<Popover
[isOpen]="formOpen()"
[triggerElement]="formTrigger"
size="lg"
[showCloseButton]="true"
>
<form (ngSubmit)="handleSubmit()">
<h3 class="font-semibold mb-4">Contact Form</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-1">Name</label>
<input
type="text"
class="w-full px-3 py-2 border rounded-lg"
placeholder="Your name"
>
</div>
<div>
<label class="block text-sm font-medium mb-1">Email</label>
<input
type="email"
class="w-full px-3 py-2 border rounded-lg"
placeholder="your@email.com"
>
</div>
<div class="flex gap-2">
<button
type="submit"
class="flex-1 px-3 py-2 bg-blue-600 text-white rounded-lg"
>
Send
</button>
<button
type="button"
(click)="formOpen.set(false)"
class="px-3 py-2 border rounded-lg"
>
Cancel
</button>
</div>
</div>
</form>
</Popover>
`
})
export class FormExample {
readonly formOpen = signal(false);
handleSubmit() {
// Handle form submission
this.formOpen.set(false);
}
}
Scroll Behavior and Auto-Hide
@Component({
template: `
<!-- Auto-hide when trigger scrolls out of view (default behavior) -->
<Popover
[isOpen]="scrollOpen()"
[triggerElement]="scrollTrigger"
[hideOnTriggerOutOfView]="true"
>
<div>
<p>This popover will automatically hide when you scroll and the trigger goes out of view.</p>
</div>
</Popover>
<!-- Disable auto-hide for persistent popovers -->
<Popover
[isOpen]="persistentOpen()"
[triggerElement]="persistentTrigger"
[hideOnTriggerOutOfView]="false"
>
<div>
<p>This popover stays open even when trigger scrolls out of view.</p>
</div>
</Popover>
<!-- With manual position updates -->
<Popover
#myPopover
[isOpen]="advancedScrollOpen()"
[triggerElement]="advancedTrigger"
[hideOnTriggerOutOfView]="true"
>
<div>
<p>Smart repositioning during scroll events.</p>
<button (click)="myPopover.updatePosition()">
Force Reposition
</button>
</div>
</Popover>
`
})
export class ScrollBehaviorExample {
readonly scrollOpen = signal(false);
readonly persistentOpen = signal(false);
readonly advancedScrollOpen = signal(false);
}
Custom Styling
@Component({
template: `
<Popover
[isOpen]="customOpen()"
[triggerElement]="customTrigger"
customClass="border-2 border-purple-500 shadow-2xl"
[showArrow]="false"
>
<div class="text-center">
<div class="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center mx-auto mb-3">
<svg class="w-6 h-6 text-purple-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
</div>
<h3 class="font-semibold text-purple-900">Premium Feature</h3>
<p class="text-sm text-purple-700">Unlock advanced capabilities</p>
</div>
</Popover>
`
})
export class CustomStylingExample {}
Configuration Options
Here's a comprehensive overview of all configuration options for the Popover component, organized by complexity and use case:
Essential Configuration (Beginner-Friendly)
| Option | Type | Default | Difficulty | Description | Example |
|---|---|---|---|---|---|
isOpen | boolean | false | ๐ข Easy | Controls popover visibility | [isOpen]="showPopover()" |
triggerElement | ElementRef | HTMLElement | Required | ๐ข Easy | Element that triggers the popover | [triggerElement]="buttonRef" |
side | 'top' | 'right' | 'bottom' | 'left' | 'bottom' | ๐ข Easy | Preferred placement side | side="top" |
size | 'sm' | 'default' | 'lg' | 'xl' | 'default' | ๐ข Easy | Popover size variant | size="lg" |
variant | 'default' | 'success' | 'warning' | 'error' | 'info' | 'default' | ๐ข Easy | Color theme variant | variant="success" |
Positioning Configuration (Intermediate)
| Option | Type | Default | Difficulty | Description | Example |
|---|---|---|---|---|---|
align | 'start' | 'center' | 'end' | 'center' | ๐ก Medium | Alignment along the chosen side | align="start" |
offset | number | 8 | ๐ก Medium | Distance from trigger in pixels | [offset]="12" |
avoidCollisions | boolean | true | ๐ก Medium | Enable intelligent repositioning | [avoidCollisions]="false" |
hideOnTriggerOutOfView | boolean | true | ๐ก Medium | Auto-hide when trigger scrolls out of view | [hideOnTriggerOutOfView]="false" |
Visual Enhancement (Intermediate)
| Option | Type | Default | Difficulty | Description | Example |
|---|---|---|---|---|---|
showArrow | boolean | true | ๐ก Medium | Display pointing arrow | [showArrow]="false" |
showCloseButton | boolean | false | ๐ก Medium | Add close button in corner | [showCloseButton]="true" |
class | string | '' | ๐ก Medium | Custom CSS classes | class="custom-popover" |
Modal & Backdrop Behavior (Advanced)
| Option | Type | Default | Difficulty | Description | Example |
|---|---|---|---|---|---|
modal | boolean | false | ๐ด Advanced | Modal behavior with backdrop | [modal]="true" |
showBackdrop | boolean | false | ๐ด Advanced | Show backdrop without modal | [showBackdrop]="true" |
Accessibility Configuration (Advanced)
| Option | Type | Default | Difficulty | Description | Example |
|---|---|---|---|---|---|
ariaLabelledby | string | '' | ๐ด Advanced | ARIA labelledby attribute | ariaLabelledby="heading-id" |
ariaDescribedby | string | '' | ๐ด Advanced | ARIA describedby attribute | ariaDescribedby="desc-id" |
Event Handling
| Event | Type | Difficulty | Description | Example |
|---|---|---|---|---|
openChange | boolean | ๐ข Easy | Emitted when open state changes | (openChange)="handleChange($event)" |
Public Methods
| Method | Parameters | Return Type | Description | Example |
|---|---|---|---|---|
updatePosition() | None | void | Forces the popover to recalculate and update its position | myPopover.updatePosition() |
Configuration Difficulty Legend
- ๐ข Easy: Simple properties that work out of the box
- ๐ก Medium: Requires understanding of positioning/styling concepts
- ๐ด Advanced: Requires knowledge of accessibility, modal patterns, or complex interactions
Common Configuration Patterns
1. Simple Tooltip (Easiest)
<Popover
[isOpen]="tooltipOpen()"
[triggerElement]="trigger"
size="sm"
>
<p>Simple tooltip content</p>
</Popover>
2. Dropdown Menu (Easy)
<Popover
[isOpen]="menuOpen()"
[triggerElement]="trigger"
side="bottom"
align="start"
size="default"
>
<div>Menu items here</div>
</Popover>
3. Modal Dialog (Medium)
<Popover
[isOpen]="dialogOpen()"
[triggerElement]="trigger"
[modal]="true"
[showCloseButton]="true"
size="lg"
>
<div>Modal content</div>
</Popover>
4. Form Validation Popover (Medium)
<Popover
[isOpen]="hasError()"
[triggerElement]="inputRef"
variant="error"
side="bottom"
align="start"
[showArrow]="true"
>
<p>{{ errorMessage() }}</p>
</Popover>
5. Complex Interactive Content (Advanced)
<Popover
[isOpen]="advancedOpen()"
[triggerElement]="trigger"
side="right"
size="xl"
[modal]="true"
[showCloseButton]="true"
[avoidCollisions]="true"
ariaLabelledby="popover-title"
ariaDescribedby="popover-desc"
class="custom-advanced-popover"
>
<div>
<h3 id="popover-title">Advanced Content</h3>
<p id="popover-desc">Complex interactive content here</p>
</div>
</Popover>
API Reference
Popover Component
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
isOpen | boolean | false | Controls the open/closed state of the popover |
triggerElement | ElementRef<HTMLElement> | HTMLElement | Required | The element that triggers the popover |
side | 'top' | 'right' | 'bottom' | 'left' | 'bottom' | Preferred side for popover placement |
align | 'start' | 'center' | 'end' | 'center' | Alignment along the chosen side |
offset | number | 8 | Distance in pixels from the trigger element |
size | 'sm' | 'default' | 'lg' | 'xl' | 'default' | Size variant of the popover |
variant | 'default' | 'success' | 'warning' | 'error' | 'info' | 'default' | Color variant of the popover |
showArrow | boolean | true | Whether to show the arrow pointing to trigger |
showCloseButton | boolean | false | Whether to show a close button in top-right corner |
modal | boolean | false | Whether popover behaves as a modal with backdrop |
showBackdrop | boolean | false | Whether to show a backdrop (auto-enabled for modal) |
avoidCollisions | boolean | true | Whether to use intelligent collision detection |
class | string | '' | Additional CSS classes for the popover |
ariaLabelledby | string | '' | ARIA labelledby attribute for accessibility |
ariaDescribedby | string | '' | ARIA describedby attribute for accessibility |
Outputs
| Output | Type | Description |
|---|---|---|
openChange | boolean | Emitted when the popover open state changes |
Types
type PopoverSide = 'top' | 'right' | 'bottom' | 'left';
type PopoverAlign = 'start' | 'center' | 'end';
interface PopoverPosition {
x: number;
y: number;
side: PopoverSide;
align: PopoverAlign;
}
Styling Guide
CSS Custom Properties
The component uses Tailwind CSS classes and CSS custom properties for theming:
/* Light mode colors */
--popover-background: theme('colors.white');
--popover-border: theme('colors.gray.200');
--popover-text: theme('colors.gray.900');
/* Dark mode colors */
.dark {
--popover-background: theme('colors.gray.800');
--popover-border: theme('colors.gray.700');
--popover-text: theme('colors.gray.100');
}
Size Variants
| Size | Min Width | Max Width | Padding | Use Case |
|---|---|---|---|---|
sm | 200px | 250px | 8px | Brief messages, tooltips |
default | 250px | 320px | 16px | Standard content |
lg | 300px | 400px | 24px | Detailed content, forms |
xl | 350px | 500px | 32px | Complex layouts, rich content |
Animation Classes
The component includes CSS animations for smooth transitions:
- Fade animations:
fade-in-0,fade-out-0 - Scale animations:
zoom-in-95,zoom-out-95 - Slide animations: Based on positioning side
Accessibility
ARIA Support
- Roles:
dialog,tooltip, or custom role - Labels:
aria-labelledby,aria-describedby - States:
aria-modalfor modal behavior - Hidden:
aria-hiddenfor decorative elements
Keyboard Navigation
| Key | Action |
|---|---|
Escape | Closes the popover (if enabled) |
Tab | Moves focus within popover content |
Shift + Tab | Moves focus backward within popover |
Screen Reader Support
- Announces when popover opens/closes
- Provides clear content structure
- Supports navigation landmarks
- Includes descriptive labels for interactive elements
Best Practices
Do's โ
- Use appropriate variants for different message types
- Provide clear trigger elements with descriptive labels
- Keep content concise and scannable
- Test with screen readers to ensure accessibility
- Use consistent positioning throughout your application
- Handle edge cases like viewport boundaries
- Provide fallback content for important information
Don'ts โ
- Don't overuse popovers - they can be disruptive
- Don't nest popovers - it creates poor UX
- Don't put critical actions in dismissible popovers
- Don't make content too wide on mobile devices
- Don't forget keyboard navigation support
- Don't use without proper trigger elements
- Don't disable dismissal without good reason
Performance Tips
- Lazy load content for complex popovers
- Use signals for reactive state management
- Debounce position updates for scroll events
- Clean up event listeners properly
- Minimize DOM manipulations during animations
Mobile Considerations
Touch Interactions
- Larger touch targets (minimum 44px)
- Touch-friendly spacing between interactive elements
- Swipe gestures for dismissal (when appropriate)
- Responsive positioning that adapts to screen size
Responsive Behavior
- Full-width on small screens for better readability
- Adjusted positioning to prevent overflow
- Backdrop support for modal behavior on mobile
- Accessible close buttons for touch interfaces
Browser Support
- Chrome: 90+
- Firefox: 88+
- Safari: 14+
- Edge: 90+
- Mobile Safari: 14+
- Chrome Mobile: 90+
Troubleshooting
Common Issues
Edge case positioning problems (Fixed in v2.0.1)
The component now includes enhanced edge case handling with improved viewport margin optimization and corner-specific positioning strategies. If you're experiencing positioning issues:
// Enhanced collision avoidance is now enabled by default
<Popover
[avoidCollisions]="true" // Default: true
[triggerElement]="cornerTrigger"
side="bottom"
align="start"
>
<div>Content automatically repositions near edges</div>
</Popover>
What's improved:
- 30+ fallback positions for comprehensive edge case coverage
- Enhanced viewport margin handling (16px default)
- Corner-specific optimization for top-left, top-right, bottom-left, bottom-right
- Better collision detection for small viewports
- Flexible dimensions and growth after open
Popover doesn't position correctly
// Ensure trigger element exists and has proper dimensions
@Component({
template: `
<button
#trigger
class="px-4 py-2" // Ensure proper dimensions
>
Trigger
</button>
`
})
Content gets cut off
// Use appropriate size variant or custom classes
<Popover
size="lg" // Use larger size
customClass="max-w-sm" // Or custom max-width
>
Accessibility issues
// Provide proper ARIA labels
<Popover
role="dialog"
ariaLabelledBy="popover-title"
ariaDescribedBy="popover-description"
>
<div>
<h3 id="popover-title">Title</h3>
<p id="popover-description">Description</p>
</div>
</Popover>
Animation performance
/* Optimize animations for better performance */
.popover-content {
will-change: transform, opacity;
transform: translateZ(0); /* Force hardware acceleration */
}
Debug Mode
Enable debug mode to see positioning calculations:
@Component({
template: `
<Popover
[isOpen]="isOpen()"
[triggerElement]="trigger"
(positionChange)="logPosition($event)"
>
<div>Debug content</div>
</Popover>
`
})
export class DebugExample {
logPosition(position: PopoverPosition) {
console.log('Popover position:', position);
}
}
Changelog
Version 1.0.0
- โจ Initial release with core functionality
- ๐ฏ Intelligent positioning system
- โฟ Comprehensive accessibility support
- ๐จ Visual variants and sizing options
- ๐ฑ Mobile optimization
- ๐ Dark mode support
Related Components
- Tooltip - Simple text overlays
- Dialog - Modal dialogs and overlays
- Dropdown Menu - Action menus
- Context Menu - Right-click menus
For more information and live examples, visit our documentation site.