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
Rendered
Rich Content Popover
Rendered
Help / Info Popover
Rendered
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="<ul><li>Item</li></ul>">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 name | caat/components/popover |
| Purpose | Rich 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 |
| Variants | Directional (top, right, bottom, left), Dismissible, Rich Content (HTML), Help/Info Icon |
| Options | Placement direction, trigger type (click / focus), HTML content toggle, custom class |
| AEM resource type | caat/components/popover |
| AEM category | CAAT Components – Overlays |
2. Design Tokens
| Token | Role | Default |
|---|---|---|
--caat-blue-900 | Popover header text colour | #003750 |
--caat-blue-700 | Body link colour | #0b5a80 |
--caat-blue-100 | Header background | #e7f4fb |
--caat-green | Reserved – success context | #55a546 |
--caat-grey-200 | Border colour, help-icon border | #dfe6ef |
--caat-focus | Focus ring on trigger element | #0d6efd |
--caat-ink | Body text colour | #102637 |
--caat-radius | Border radius | .75rem |
--caat-font-primary | Font family | Libre Franklin |
3. Authoring Fields
| Field | Type | Required | Notes |
|---|---|---|---|
| Trigger label | Text | Yes | Visible text or aria-label on the trigger element. |
| Trigger style | Select | Yes | Options: Button Primary, Button Outline, Icon Only (help). |
| Popover title | Text | No | Rendered in .popover-header. Omit for title-less popovers. |
| Popover content | Rich Text | Yes | Body content. Supports plain text or HTML (lists, links, icons). |
| Placement | Select | Yes | top | right | bottom | left. Default: top. |
| Trigger type | Select | Yes | click | focus. Default: click. |
| Allow HTML | Checkbox | No | Enables data-bs-html="true" for rich content. |
| Aria label | Text | Conditional | Required when trigger style is Icon Only. |
4. Validation Rules
| Rule | Behaviour |
|---|---|
| Content required | Component does not render if popover content is empty. |
| Title max length | 60 characters. Truncated with ellipsis if exceeded. |
| Content max length | 300 characters recommended. Scrollable body if exceeded. |
| Icon-only trigger requires aria-label | Dialog prevents save if trigger style is Icon Only and aria-label is empty. |
| HTML sanitisation | When 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
| Selector | Purpose |
|---|---|
.caat-popover | Custom class applied via data-bs-custom-class; scopes all overrides. |
.caat-popover .popover-header | Header — blue-100 background, blue-900 text, 700-weight font. |
.caat-popover .popover-body | Body — white background, ink text, 0.8125rem font size. |
.caat-popover .popover-arrow | Arrow borders inherit grey-200 border colour. |
.caat-popover-help | Circular icon-only trigger button for field-level help. |
File: /assets/css/components/popover.css
8. Accessibility
| Requirement | Implementation |
|---|---|
| Keyboard operable | Trigger receives focus via Tab. Popover opens on Enter/Space. Dismiss via Escape or focus-loss. |
| Focus management | Focus remains on trigger when popover opens; returns on close. |
| Screen reader | Bootstrap adds aria-describedby linking trigger to popover content. |
| Icon-only label | aria-label required on icon-only triggers (.caat-popover-help). |
| Colour contrast | Header text (blue-900 on blue-100) and body text (ink on white) both exceed WCAG AA 4.5:1. |
| Focus indicator | 2px solid --caat-focus outline with 2px offset on :focus-visible. |
| Motion | No 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 oncaat.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
| Area | Done 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. |