AvesraAvesrabeta

Popover

Displays rich content in a portal triggered by a button or any custom element.

Import

import { AvPopoverImports } from '@avesra/angular';

Usage

import { Component } from '@angular/core';
import {
  AvButtonComponent,
  AvPopoverImports,
} from '@avesra/angular';

@Component({
  selector: 'app-popover-basic-demo',
  imports: [
    AvButtonComponent,
    AvPopoverImports,
  ],
  template: `<div class="flex items-center gap-4">
  <av-popover>
    <button av-button av-popover-trigger>Click me</button>
    <av-popover-content>
      <div av-popover-dialog class="max-w-64">
        <h2 av-popover-heading>Popover Title</h2>
        <p class="mt-2 text-sm text-muted">
          This is the popover content. You can put any content here.
        </p>
      </div>
    </av-popover-content>
  </av-popover>
</div>`,
})
export class PopoverBasicDemo {}

Anatomy

Import the Popover parts and compose them with element and attribute selectors. Optional pieces include av-popover-arrow and av-popover-heading.

<av-popover>
  <button av-button av-popover-trigger>Open</button>
  <av-popover-content>
    <av-popover-arrow />
    <div av-popover-dialog>
      <h2 av-popover-heading>Heading</h2>
      <!-- content goes here -->
    </div>
  </av-popover-content>
</av-popover>

With Arrow

Add av-popover-arrow inside av-popover-content to point at the trigger. Arrow rotation follows [data-placement] on the overlay panel.

import { Component } from '@angular/core';
import {
  AvButtonComponent,
  AvPopoverImports,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-popover-with-arrow-demo',
  imports: [
    AppIconComponent,
    AvButtonComponent,
    AvPopoverImports,
  ],
  template: `<div class="flex items-center gap-4">
  <av-popover>
    <button av-button variant="secondary" av-popover-trigger>With Arrow</button>
    <av-popover-content>
      <av-popover-arrow />
      <div av-popover-dialog class="max-w-64">
        <h2 av-popover-heading>Popover with Arrow</h2>
        <p class="mt-2 text-sm text-muted">
          The arrow shows which element triggered the popover.
        </p>
      </div>
    </av-popover-content>
  </av-popover>

  <av-popover>
    <button
      av-button
      icon-only
      variant="tertiary"
      av-popover-trigger
      aria-label="More options"
    >
      <app-icon icon="solar:menu-dots-linear" size="20" />
    </button>
    <av-popover-content [offset]="10">
      <av-popover-arrow />
      <div av-popover-dialog class="max-w-64">
        <h2 av-popover-heading>Popover with Arrow</h2>
        <p class="mt-2 text-sm text-muted">
          The arrow shows which element triggered the popover.
        </p>
      </div>
    </av-popover-content>
  </av-popover>
</div>`,
})
export class PopoverWithArrowDemo {}

Interactive Content

Popover panels accept focusable content — buttons, links, and form controls — while the overlay stays open. Clicking inside the panel does not dismiss the popover.

import { Component, signal } from '@angular/core';
import {
  AvAvatarImports,
  AvButtonComponent,
  AvPopoverImports,
} from '@avesra/angular';

@Component({
  selector: 'app-popover-interactive-demo',
  imports: [
    AvAvatarImports,
    AvButtonComponent,
    AvPopoverImports,
  ],
  template: `<div class="flex items-center gap-6">
  <av-popover>
    <div av-popover-trigger class="flex items-center gap-2" aria-label="User profile">
      <span av-avatar size="sm">
        <img av-avatar-image alt="Maya Ellison" src="/images/avatars/avatar-woman-glasses-auburn-hair.png" />
        <span av-avatar-fallback>ME</span>
      </span>
      <div class="flex flex-col">
        <p class="text-sm font-medium text-foreground">Maya Ellison</p>
        <p class="text-xs text-muted">&#64;mayae</p>
      </div>
    </div>
    <av-popover-content>
      <div av-popover-dialog class="w-[320px]">
        <div av-popover-heading>
          <div class="flex items-center justify-between">
            <div class="flex items-center gap-3">
              <span av-avatar size="md">
                <img av-avatar-image alt="Maya Ellison" src="/images/avatars/avatar-woman-glasses-auburn-hair.png" />
                <span av-avatar-fallback>ME</span>
              </span>
              <div>
                <p class="font-semibold text-foreground">Maya Ellison</p>
                <p class="text-sm text-muted">&#64;mayae</p>
              </div>
            </div>
            <button
              av-button
              size="sm"
              class="rounded-full"
              [variant]="following() ? 'tertiary' : 'primary'"
              (click)="toggleFollowing()"
            >
              {{ following() ? 'Following' : 'Follow' }}
            </button>
          </div>
        </div>
        <p class="mt-3 text-sm text-muted">
          Product designer based in Lisbon. Shipping interfaces that feel calm and considered.
        </p>
        <div class="mt-3 flex gap-4">
          <div>
            <span class="font-semibold text-foreground">428</span>
            <span class="ml-1 text-sm text-muted">Following</span>
          </div>
          <div>
            <span class="font-semibold text-foreground">8.2K</span>
            <span class="ml-1 text-sm text-muted">Followers</span>
          </div>
        </div>
      </div>
    </av-popover-content>
  </av-popover>
</div>`,
})
export class PopoverInteractiveDemo {
  readonly following = signal(false);

  toggleFollowing(): void {
    this.following.update((value) => !value);
  }
}

Placement

Set placement on av-popover-content — top, bottom, left, right, and aligned variants such as bottom start. The overlay flips when should-flip is enabled (default).

Click buttons
import { Component } from '@angular/core';
import {
  AvButtonComponent,
  AvPopoverImports,
} from '@avesra/angular';

@Component({
  selector: 'app-popover-placements-demo',
  imports: [
    AvButtonComponent,
    AvPopoverImports,
  ],
  template: `<div class="grid grid-cols-3 gap-4">
  <div></div>
  <av-popover>
    <button av-button class="w-full" variant="tertiary" av-popover-trigger>Top</button>
    <av-popover-content placement="top">
      <av-popover-arrow />
      <div av-popover-dialog>
        <p class="text-sm">Top placement</p>
      </div>
    </av-popover-content>
  </av-popover>
  <div></div>

  <av-popover>
    <button av-button class="w-full" variant="tertiary" av-popover-trigger>Left</button>
    <av-popover-content placement="left">
      <av-popover-arrow />
      <div av-popover-dialog>
        <p class="text-sm">Left placement</p>
      </div>
    </av-popover-content>
  </av-popover>

  <div class="flex items-center justify-center">
    <span class="text-sm text-muted">Click buttons</span>
  </div>

  <av-popover>
    <button av-button class="w-full" variant="tertiary" av-popover-trigger>Right</button>
    <av-popover-content placement="right">
      <av-popover-arrow />
      <div av-popover-dialog>
        <p class="text-sm">Right placement</p>
      </div>
    </av-popover-content>
  </av-popover>

  <div></div>
  <av-popover>
    <button av-button class="w-full" variant="tertiary" av-popover-trigger>Bottom</button>
    <av-popover-content placement="bottom">
      <av-popover-arrow />
      <div av-popover-dialog>
        <p class="text-sm">Bottom placement</p>
      </div>
    </av-popover-content>
  </av-popover>
  <div></div>
</div>`,
})
export class PopoverPlacementsDemo {}

Styling

Passing Tailwind CSS classes

Style the portaled panel with overlay-class on av-popover-content, or pass utility classes on div[av-popover-dialog] and other part hosts.

import { Component } from '@angular/core';
import {
  AvButtonComponent,
  AvPopoverImports,
} from '@avesra/angular';

@Component({
  selector: 'app-popover-custom-styling-demo',
  imports: [
    AvButtonComponent,
    AvPopoverImports,
  ],
  template: `<div class="flex items-center gap-4">
  <av-popover>
    <button av-button av-popover-trigger>Open</button>
    <av-popover-content overlay-class="bg-accent text-accent-foreground">
      <div av-popover-dialog class="max-w-64">
        <h2 av-popover-heading>Custom Styled</h2>
        <p class="mt-2 text-sm opacity-90">This popover has custom styling</p>
      </div>
    </av-popover-content>
  </av-popover>
</div>`,
})
export class PopoverCustomStylingDemo {}

Customizing the component classes

To customize the Popover classes, use the @layer components directive. Learn more.

@layer components {
  .av-popover {
    @apply rounded-xl shadow-2xl;
  }

  .av-popover__dialog {
    @apply p-4;
  }

  .av-popover__heading {
    @apply text-lg font-bold;
  }
}

Avesra follows the BEM methodology so component variants and states stay reusable and easy to customize.

CSS Classes

The Popover component uses these CSS classes:

Base Classes

  • .av-popover — Base popover container styles
  • .av-popover__dialog — Dialog content wrapper
  • .av-popover__heading — Heading text styles
  • .av-popover__trigger — Trigger element styles
  • [data-slot="popover-arrow"] — Arrow positioning wrapper
  • [data-slot="popover-overlay-arrow"] — SVG fill matched to overlay background

Interactive States

The component supports these animation and interaction states:

  • Entering:[data-entering] — Applied during popover appearance
  • Exiting:[data-exiting] — Applied during popover disappearance
  • Placement:[data-placement="*"] — Applied based on popover position
  • Focus::focus-visible or [data-focus-visible="true"]
  • Expanded:[aria-expanded] on the trigger reflects open state

API Reference

Compound API across av-popover, [av-popover-trigger], av-popover-content, and child parts. Root props control open state and dismiss behavior; content props control placement and viewport flipping.

PropTypeDefaultDescription
openbooleanfalseControls whether the popover is open. Supports two-way binding with [(open)] (av-popover).
dismissablebooleantrueCloses the popover when clicking outside (av-popover).
keyboard-dismiss-disabledbooleanfalseDisables closing via the Escape key (av-popover).
aria-labelstring—Accessible label when the trigger has no visible text ([av-popover-trigger]).
disabledbooleanfalseDisables the trigger ([av-popover-trigger]).
placement'top' | 'top start' | 'top end' | 'bottom' | 'bottom start' | 'bottom end' | 'left' | 'left top' | 'left bottom' | 'right' | 'right top' | 'right bottom' | 'start' | 'start top' | 'start bottom' | 'end' | 'end top' | 'end bottom''bottom'Preferred placement relative to the trigger (av-popover-content).
offsetnumber8Distance between trigger and popover in pixels (av-popover-content).
should-flipbooleantrueWhether the popover can flip to fit the viewport (av-popover-content).
overlay-classstring—Extra classes applied to the portaled overlay panel (av-popover-content).
overlay-styleRecord<string, string | number>—Inline styles applied to the portaled overlay panel (av-popover-content).

Made with ❤ by SyntaxHertz. Open source and available on GitHub.