Component Contract

Popover

Rich content popup used for contextual help, supplementary information, and inline guidance across the CAAT Pension Plan member experience.

Live Demo

Directional Popovers

Rendered

Dismissible Popover

Rich Content Popover

Rendered

Help / Info Popover

Rendered

Contribution Rate

Popover Anatomy

The rendered bubble appearance. The live popovers above are injected by Bootstrap on click/focus; this static instance shows the styled header + body for the Figma export.

Rendered

Code sample

HTML

<!-- Directional popover -->
<button type="button" class="caat-button btn btn-primary"
  data-bs-toggle="popover" data-bs-placement="top"
  data-bs-custom-class="caat-popover"
  title="Top Popover"
  data-bs-content="Popover content text.">Top</button>

<!-- Dismissible popover (focus trigger) -->
<a tabindex="0" class="caat-button btn btn-outline-primary" role="button"
  data-bs-toggle="popover" data-bs-trigger="focus"
  data-bs-custom-class="caat-popover"
  title="Dismissible"
  data-bs-content="Click elsewhere to dismiss.">Dismiss</a>

<!-- Rich content popover (HTML) -->
<button type="button" class="caat-button btn btn-primary"
  data-bs-toggle="popover" data-bs-html="true"
  data-bs-custom-class="caat-popover"
  title="Plan Details"
  data-bs-content="&lt;ul&gt;&lt;li&gt;Item&lt;/li&gt;&lt;/ul&gt;">Rich</button>

<!-- Help icon popover -->
<button type="button" class="caat-popover-help"
  data-bs-toggle="popover" data-bs-trigger="focus"
  data-bs-custom-class="caat-popover"
  title="Help"
  data-bs-content="Explanation text."
  aria-label="Help: Field name">
  <i class="bi bi-question-circle-fill" aria-hidden="true"></i>
</button>

<!-- Initialise all popovers -->
<script>
document.querySelectorAll('[data-bs-toggle="popover"]')
  .forEach(el => new bootstrap.Popover(el));
</script>

1. Summary

Component namecaat/components/popover
PurposeRich content popup triggered on click or focus, used to present supplementary information, contextual help, and inline guidance without navigating away from the current page. Common in member dashboards, contribution tables, and form field explanations.
Status New
VariantsDirectional (top, right, bottom, left), Dismissible, Rich Content (HTML), Help/Info Icon
OptionsPlacement direction, trigger type (click / focus), HTML content toggle, custom class
AEM resource typecaat/components/popover
AEM categoryCAAT Components – Overlays

2. Design Tokens

TokenRoleDefault
--caat-blue-900Popover header text colour#003750
--caat-blue-700Body link colour#0b5a80
--caat-blue-100Header background#e7f4fb
--caat-greenReserved – success context#55a546
--caat-grey-200Border colour, help-icon border#dfe6ef
--caat-focusFocus ring on trigger element#0d6efd
--caat-inkBody text colour#102637
--caat-radiusBorder radius.75rem
--caat-font-primaryFont familyLibre Franklin

3. Authoring Fields

FieldTypeRequiredNotes
Trigger labelTextYesVisible text or aria-label on the trigger element.
Trigger styleSelectYesOptions: Button Primary, Button Outline, Icon Only (help).
Popover titleTextNoRendered in .popover-header. Omit for title-less popovers.
Popover contentRich TextYesBody content. Supports plain text or HTML (lists, links, icons).
PlacementSelectYestop | right | bottom | left. Default: top.
Trigger typeSelectYesclick | focus. Default: click.
Allow HTMLCheckboxNoEnables data-bs-html="true" for rich content.
Aria labelTextConditionalRequired when trigger style is Icon Only.

4. Validation Rules

RuleBehaviour
Content requiredComponent does not render if popover content is empty.
Title max length60 characters. Truncated with ellipsis if exceeded.
Content max length300 characters recommended. Scrollable body if exceeded.
Icon-only trigger requires aria-labelDialog prevents save if trigger style is Icon Only and aria-label is empty.
HTML sanitisationWhen Allow HTML is enabled, content is sanitised server-side to permit only <ul>, <li>, <a>, <strong>, <em>, <i>.

5. Content Guidance

  • Keep popover content concise — ideally 1–3 short sentences or a brief list.
  • Use popovers for supplementary information, not critical content that users must see.
  • Popover titles should be short noun phrases (e.g., "Contribution Rate", "Plan Details").
  • If content requires a call-to-action link, limit to one link per popover.
  • Avoid nesting interactive elements (buttons, form controls) inside popover content.
  • Use the Help/Info icon variant for inline field-level guidance on forms.
  • Do not use popovers on mobile-critical flows — consider inline text or accordions instead.

6. Semantic HTML

HTML structure

<!-- Button trigger -->
<button type="button"
  class="caat-button btn btn-primary"
  data-bs-toggle="popover"
  data-bs-placement="top"
  data-bs-custom-class="caat-popover"
  title="Popover Title"
  data-bs-content="Popover body content.">
  Trigger Text
</button>

<!-- Icon-only help trigger -->
<button type="button"
  class="caat-popover-help"
  data-bs-toggle="popover"
  data-bs-trigger="focus"
  data-bs-custom-class="caat-popover"
  title="Field Help"
  data-bs-content="Explanation."
  aria-label="Help: Field Name">
  <i class="bi bi-question-circle-fill" aria-hidden="true"></i>
</button>

7. CSS Contract

SelectorPurpose
.caat-popoverCustom class applied via data-bs-custom-class; scopes all overrides.
.caat-popover .popover-headerHeader — blue-100 background, blue-900 text, 700-weight font.
.caat-popover .popover-bodyBody — white background, ink text, 0.8125rem font size.
.caat-popover .popover-arrowArrow borders inherit grey-200 border colour.
.caat-popover-helpCircular icon-only trigger button for field-level help.

File: /assets/css/components/popover.css

8. Accessibility

RequirementImplementation
Keyboard operableTrigger receives focus via Tab. Popover opens on Enter/Space. Dismiss via Escape or focus-loss.
Focus managementFocus remains on trigger when popover opens; returns on close.
Screen readerBootstrap adds aria-describedby linking trigger to popover content.
Icon-only labelaria-label required on icon-only triggers (.caat-popover-help).
Colour contrastHeader text (blue-900 on blue-100) and body text (ink on white) both exceed WCAG AA 4.5:1.
Focus indicator2px solid --caat-focus outline with 2px offset on :focus-visible.
MotionNo animation by default. Respects prefers-reduced-motion.

9. SEO

Transient / structural UI with no indexable content of its own — no SEO markup required. Ensure any content this component wraps or reveals stays server-rendered and crawlable.

10. Analytics

Analytics are captured through the shared data-analytics-component attribute on the component root (emitted automatically) and reported to the central data layer. Event names and payloads follow the organisation's standard analytics schema — defined centrally with the analytics team, not invented per component — so no bespoke tracking is specified here.

11. AEM Notes

  • Resource type: caat/components/popover
  • Component group: CAAT Components – Overlays
  • Proxy component: Yes — create under /apps/caat/components/popover.
  • Sling Model: Provides title, content, placement, triggerType, allowHtml, triggerStyle, ariaLabel.
  • HTL: Renders trigger element with data-bs-* attributes; popover markup generated by Bootstrap JS at runtime.
  • Client library: caat.components.popover (category), depends on caat.vendor.bootstrap.
  • Edit mode: Popover is non-functional in AEM editor; placeholder text shown instead.

12. Dialog Model

Simplified model — shows field intent and Granite UI resource types, not the literal cq:dialog node tree (e.g. select options are authored as child items nodes).

cq:dialog (Coral UI 3)

<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
  xmlns:cq="http://www.day.com/jcr/cq/1.0"
  xmlns:jcr="http://www.jcp.org/jcr/1.0"
  jcr:primaryType="nt:unstructured"
  jcr:title="Popover"
  sling:resourceType="cq/gui/components/authoring/dialog">
  <content jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/container">
    <items jcr:primaryType="nt:unstructured">
      <tabs jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/tabs">
        <items jcr:primaryType="nt:unstructured">
          <popover jcr:primaryType="nt:unstructured"
            jcr:title="Popover" sling:resourceType="granite/ui/components/coral/foundation/container">
            <items jcr:primaryType="nt:unstructured">
              <triggerLabel jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                fieldLabel="Trigger Label" name="./triggerLabel" required="{Boolean}true"/>
              <triggerStyle jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/form/select"
                fieldLabel="Trigger Style" name="./triggerStyle">
                <items jcr:primaryType="nt:unstructured">
                  <primary jcr:primaryType="nt:unstructured" text="Button Primary" value="primary"/>
                  <outline jcr:primaryType="nt:unstructured" text="Button Outline" value="outline"/>
                  <icon jcr:primaryType="nt:unstructured" text="Icon Only (Help)" value="icon"/>
                </items>
              </triggerStyle>
              <title jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                fieldLabel="Popover Title" name="./popoverTitle"/>
              <content jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/form/textarea"
                fieldLabel="Popover Content" name="./popoverContent" required="{Boolean}true"/>
              <placement jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/form/select"
                fieldLabel="Placement" name="./placement">
                <items jcr:primaryType="nt:unstructured">
                  <top jcr:primaryType="nt:unstructured" text="Top" value="top" selected="{Boolean}true"/>
                  <end jcr:primaryType="nt:unstructured" text="Right" value="right"/>
                  <bottom jcr:primaryType="nt:unstructured" text="Bottom" value="bottom"/>
                  <start jcr:primaryType="nt:unstructured" text="Left" value="left"/>
                </items>
              </placement>
              <triggerType jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/form/select"
                fieldLabel="Trigger Type" name="./triggerType">
                <items jcr:primaryType="nt:unstructured">
                  <click jcr:primaryType="nt:unstructured" text="Click" value="click" selected="{Boolean}true"/>
                  <focus jcr:primaryType="nt:unstructured" text="Focus" value="focus"/>
                </items>
              </triggerType>
              <allowHtml jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
                fieldLabel="Allow HTML Content" name="./allowHtml" text="Enable rich HTML in popover body"/>
              <ariaLabel jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                fieldLabel="Aria Label" name="./ariaLabel"/>
            </items>
          </popover>
        </items>
      </tabs>
    </items>
  </content>
</jcr:root>

13. QA Acceptance Checklist

  • All four directional placements (top, right, bottom, left) render correctly with arrow pointing to trigger.
  • Dismissible popover closes on next click / focus loss.
  • Rich content popover renders HTML (lists, links, icons) correctly inside body.
  • Help icon popover displays on focus and dismisses on blur.
  • Popover header uses blue-100 background with blue-900 text per design tokens.
  • Max-width is 300px; content wraps and does not overflow.
  • Keyboard accessible: Tab to trigger, Enter/Space to open, Escape to close.
  • Screen reader announces popover content via aria-describedby.
  • Icon-only trigger has visible focus ring and announced aria-label.
  • Component does not render when popover content field is empty.
  • Analytics events (popover-opened, popover-dismissed, popover-link-click) fire correctly.
  • Cross-browser tested: Chrome, Firefox, Safari, Edge.

14. Definition of Done

AreaDone means
Design Figma component matches this contract. All four variants documented. Token usage specified for header, body, border, and focus states.
Development AEM proxy component created. HTL template emits semantic HTML per Section 6. CSS uses design tokens exclusively. Bootstrap Popover JS initialised. Icon-only trigger includes aria-label. Unit tests pass.
QA All items in the QA Acceptance Checklist (Section 13) pass. Manual accessibility audit completed. Cross-browser tested (Chrome, Firefox, Safari, Edge).
Launch Component deployed to AEM production. Content authors trained on trigger styles, placement, and HTML content options. Documentation published. Analytics dashboards confirmed receiving events.