Component Contract
Accordion
A progressive-disclosure component for FAQ, plan details, and help content in CAAT digital properties.
Live Demo
Standard Accordion
Rendered
Flush Variant
Rendered
Always-Open Variant
Rendered
States
Follows the Interaction States matrix. The accordion button implements collapsed (default), expanded (selected), and focus; hover is subtle and disabled does not apply.
Rendered
Code sample
HTML
<!-- Standard accordion -->
<div class="accordion caat-accordion" id="faqAccordion"
data-analytics-component="accordion">
<div class="accordion-item">
<h3 class="accordion-header" id="faqHeading1">
<button class="accordion-button" type="button"
data-bs-toggle="collapse"
data-bs-target="#faqCollapse1"
aria-expanded="true"
aria-controls="faqCollapse1"
data-analytics-label="question-1">
What is the CAAT DBplus pension plan?
</button>
</h3>
<div id="faqCollapse1" class="accordion-collapse collapse show"
aria-labelledby="faqHeading1"
data-bs-parent="#faqAccordion">
<div class="accordion-body">
Your answer content here.
</div>
</div>
</div>
</div>
<!-- Flush variant — add .accordion-flush -->
<div class="accordion accordion-flush caat-accordion" id="flushAccordion">
<!-- items … -->
</div>
<!-- Always-open — omit data-bs-parent -->
<div class="accordion caat-accordion" id="openAccordion">
<div class="accordion-item">
<h3 class="accordion-header" id="openHeading1">
<button class="accordion-button" type="button"
data-bs-toggle="collapse"
data-bs-target="#openCollapse1"
aria-expanded="true"
aria-controls="openCollapse1">
Question text
</button>
</h3>
<div id="openCollapse1" class="accordion-collapse collapse show"
aria-labelledby="openHeading1">
<div class="accordion-body">Answer text</div>
</div>
</div>
</div>
1. Component Summary
| Component name | Accordion |
|---|---|
| AEM component name | caat/components/accordion |
| Recommended implementation | Extend the AEM Core Accordion component (core/wcm/components/accordion) |
| Component group | CAAT Design System |
| Purpose | Progressive disclosure for FAQ, plan details, and help content |
| Existing AEM components replaced | CAAT Accordion (#3), CAAT Vue Accordion (#19), CAAT Estimators Help (#14), CAAT Estimators Help Vue (#15). Consolidates 4 existing components into 1. |
Primary use cases
- FAQ pages — question-and-answer pairs with optional FAQ schema markup
- Plan detail explanations — progressive disclosure of pension plan features
- Help and guidance sections — contextual help within tools and calculators
- Terms and conditions — collapsible legal sections
- Estimator help content — replacing the existing Vue-based help accordions
Non-goals
- This component is not for navigation — do not use it as a menu or nav structure
- It is not for sequential wizards or stepped processes (use a Stepper component)
- It must not be used to hide essential legal disclosures that members are required to see
2. Design System Source / Token Dependencies
| Token | Value (reference) | Purpose |
|---|---|---|
--caat-blue-900 | #003750 | Accordion button text colour, active indicator |
--caat-blue-100 | #e7f4fb | Expanded header background tint |
--caat-blue-300 | #9dd2ed | Accordion border accent |
--caat-grey-200 | #dfe6ef | Item divider border colour |
--caat-focus | 0 0 0 .25rem rgba(47,149,210,.35) | Focus ring on accordion buttons |
--caat-font-primary | "Libre Franklin", Arial, sans-serif | Accordion typeface |
Visual variants
- Standard — bordered accordion with background tint on expanded header
- Flush (
.accordion-flush) — no outer borders or rounded corners, sits flush with surrounding content - Always-open — multiple items can be expanded simultaneously (no
data-bs-parent) - Nested / FAQ with schema — standard accordion with
itemscopeanditempropattributes for FAQ structured data
3. Authoring Fields
Content tab
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| Heading text (per item) | Text field | Yes | — | Visible label for the accordion trigger button. |
| Body content (per item) | Rich text editor | Yes | — | Supports paragraphs, lists, links, images, and embedded components. |
| Heading level | Dropdown | No | h3 | h2 | h3 | h4 | h5. Sets the semantic heading wrapping each accordion button. |
| Expanded by default | Checkbox (per item) | No | Off | When checked, the item renders in the expanded state on page load. |
Style tab
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| Variant | Dropdown | Yes | Standard | standard | flush | always-open |
| Single item expand | Toggle | No | On | When on, only one item can be open at a time (sets data-bs-parent). Disabled when variant is "always-open". |
Behaviour tab
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| FAQ schema output | Checkbox | No | Off | When checked, renders FAQ structured data (itemscope/itemprop) for eligible pages. |
| Analytics tracking | Toggle | No | On | When on, emits data-analytics-* attributes and wires expand/collapse events. |
4. Authoring Validation Rules
- At least one accordion item is required. The dialog must not allow saving an empty accordion.
- Heading text is required for every item. The dialog must not allow saving an item without a heading.
- Authors must never hide essential legal disclosures solely within collapsed accordion panels — such content must be visible by default or presented outside the accordion.
- FAQ schema output should only be enabled when the content is genuinely FAQ-eligible (question-and-answer pairs on a page primarily about answering questions).
5. Content Guidance
Good examples
- Clear, question-style headings: "How do I update my beneficiary?"
- Direct answer first, then supporting detail
- Concise answers — 1–2 sentences when possible
- Logically grouped items under a clear section heading
- Use for genuinely optional detail that supports a primary narrative
Avoid
- Vague headings: "More information", "Details", "Click here"
- Nesting accordions more than 1 level deep
- Hiding required action items inside collapsed panels
- Using accordion as the only way to read critical legal content
- Overly long body content — consider splitting into multiple items
6. Expected Semantic HTML Output
Standard accordion
<div class="accordion caat-accordion" id="faqAccordion">
<div class="accordion-item">
<h3 class="accordion-header" id="faqHeading1">
<button class="accordion-button" type="button"
data-bs-toggle="collapse"
data-bs-target="#faqCollapse1"
aria-expanded="true"
aria-controls="faqCollapse1">
Question heading text
</button>
</h3>
<div id="faqCollapse1"
class="accordion-collapse collapse show"
aria-labelledby="faqHeading1"
data-bs-parent="#faqAccordion">
<div class="accordion-body">
<p>Answer content here.</p>
</div>
</div>
</div>
</div>
FAQ schema variant
<div class="accordion caat-accordion"
itemscope itemtype="https://schema.org/FAQPage">
<div class="accordion-item"
itemscope itemprop="mainEntity"
itemtype="https://schema.org/Question">
<h3 class="accordion-header">
<button class="accordion-button" type="button"
data-bs-toggle="collapse"
data-bs-target="#schemaCollapse1"
aria-expanded="true"
aria-controls="schemaCollapse1">
<span itemprop="name">What is CAAT DBplus?</span>
</button>
</h3>
<div id="schemaCollapse1" class="accordion-collapse collapse show">
<div class="accordion-body"
itemscope itemprop="acceptedAnswer"
itemtype="https://schema.org/Answer">
<div itemprop="text">
<p>CAAT DBplus is a defined benefit pension plan…</p>
</div>
</div>
</div>
</div>
</div>
Flush variant
<div class="accordion accordion-flush caat-accordion" id="flushAccordion">
<!-- Same inner structure, no outer borders -->
</div>
7. CSS Contract
The accordion component relies on the global token layer defined in /assets/css/tokens.css.
All component-specific styles live in
/assets/css/components/accordion.css.
Base styles
.caat-accordion {
font-family: var(--caat-font-primary, "Libre Franklin", Arial, sans-serif);
}
.caat-accordion .accordion-item {
border-color: var(--caat-grey-200, #e9ecef);
}
.caat-accordion .accordion-button {
font-weight: 600;
color: var(--caat-blue-900, #003750);
background-color: transparent;
}
.caat-accordion .accordion-button:not(.collapsed) {
background-color: var(--caat-blue-100, #e6f0f7);
color: var(--caat-blue-900, #003750);
box-shadow: inset 0 -1px 0 var(--caat-grey-200, #e9ecef);
}
/* Focus ring */
.caat-accordion .accordion-button:focus-visible {
box-shadow: var(--caat-focus);
outline: 0;
}
/* Custom chevron colour */
.caat-accordion .accordion-button::after {
filter: brightness(0) saturate(100%);
}
var(--caat-blue-900))
so that theme changes propagate automatically.
8. Accessibility Requirements
| Requirement | Acceptance criteria |
|---|---|
| Keyboard access | Accordion buttons must be reachable via Tab and toggled with Enter or Space. Arrow-key navigation between headers is optional but recommended. |
| Focus state | A visible focus ring (var(--caat-focus)) must appear on :focus-visible. The ring must meet a 3 : 1 contrast ratio against the background. |
| ARIA attributes | Each accordion button must have aria-expanded (true/false) and aria-controls pointing to its collapse panel id. The panel must have aria-labelledby pointing back to its header. |
| Heading level | The heading level wrapping the accordion button must be configurable (h2–h5) so it fits the page's document outline. |
| Content visibility | When a panel is expanded, its content must be fully accessible to screen readers. Collapsed content should be hidden from assistive technology via the Bootstrap collapse mechanism. |
| Colour contrast | Text-to-background contrast must meet WCAG 2.1 AA (4.5 : 1 for normal text) in both collapsed and expanded states. |
9. SEO Requirements
- Use real heading elements (h2–h5) for accordion headers — do not rely on ARIA roles alone.
- Enable FAQ structured data (
itemscope/itemprop) only on pages with genuinely eligible FAQ content. Do not apply to every accordion. - Keep answers visible to crawlers — Google can render collapsed Bootstrap accordion content, but ensure key content is in the initial HTML response.
10. Analytics Requirements
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 Implementation Notes
Existing component mapping
This component consolidates and replaces:
- CAAT Accordion (#3) — standard server-rendered accordion
- CAAT Vue Accordion (#19) — Vue.js-based accordion
- CAAT Estimators Help (#14) — help accordion in estimator tools
- CAAT Estimators Help Vue (#15) — Vue.js version of estimator help
AEM triage
Rebuild The Vue-based components (#19, #14, #15) should be fully replaced with a standard AEM / Bootstrap accordion. The existing server-rendered accordion (#3) should be rebuilt to use design tokens and meet current accessibility requirements.
Proxy / extend guidance
Create a proxy component at /apps/caat/components/accordion that extends the
AEM Core Accordion component (core/wcm/components/accordion) where feasible.
Override the HTL template to emit the CAAT class names, data attributes, and optional FAQ schema markup.
Client library structure
- Category:
caat.components.accordion - Dependencies:
caat.base(tokens + base styles) - CSS:
accordion.css - JS:
accordion.js(analytics wiring, expand/collapse event tracking)
Suggested file structure
accordion/
├── _cq_dialog/
│ └── .content.xml # Touch UI dialog
├── _cq_editConfig.xml # Edit configuration
├── .content.xml # Component node definition
├── accordion.html # HTL template
├── clientlibs/
│ ├── .content.xml
│ ├── css/
│ │ └── accordion.css
│ └── js/
│ └── accordion.js
└── README.md # Dev notes
12. Dialog Field 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).
# caat/components/accordion — Touch UI Dialog
# _cq_dialog/.content.xml (YAML representation)
dialog:
jcr:primaryType: nt:unstructured
sling:resourceType: cq/gui/components/authoring/dialog
content:
jcr:primaryType: nt:unstructured
sling:resourceType: granite/ui/components/coral/foundation/container
items:
tabs:
jcr:primaryType: nt:unstructured
sling:resourceType: granite/ui/components/coral/foundation/tabs
items:
# ── Content tab ──────────────────────────────
content:
jcr:title: Content
items:
headingLevel:
sling:resourceType: granite/ui/components/coral/foundation/form/select
name: ./headingLevel
fieldLabel: Heading level
items:
- { text: H2, value: h2 }
- { text: H3, value: h3 }
- { text: H4, value: h4 }
- { text: H5, value: h5 }
defaultValue: h3
accordionItems:
sling:resourceType: granite/ui/components/coral/foundation/form/multifield
name: ./accordionItems
fieldLabel: Accordion items
composite: true
required: true
items:
field:
sling:resourceType: granite/ui/components/coral/foundation/container
items:
headingText:
sling:resourceType: granite/ui/components/coral/foundation/form/textfield
name: ./headingText
fieldLabel: Heading text
required: true
bodyContent:
sling:resourceType: granite/ui/components/coral/foundation/form/richtext
name: ./bodyContent
fieldLabel: Body content
required: true
expandedByDefault:
sling:resourceType: granite/ui/components/coral/foundation/form/checkbox
name: ./expandedByDefault
text: Expanded by default
# ── Style tab ────────────────────────────────
style:
jcr:title: Style
items:
variant:
sling:resourceType: granite/ui/components/coral/foundation/form/select
name: ./variant
fieldLabel: Variant
required: true
items:
- { text: Standard, value: standard }
- { text: Flush, value: flush }
- { text: Always open, value: always-open }
singleItemExpand:
sling:resourceType: granite/ui/components/coral/foundation/form/switch
name: ./singleItemExpand
fieldLabel: Single item expand
checked: true
# ── Behaviour tab ────────────────────────────
behaviour:
jcr:title: Behaviour
items:
faqSchema:
sling:resourceType: granite/ui/components/coral/foundation/form/checkbox
name: ./faqSchema
text: Enable FAQ schema output
analyticsTracking:
sling:resourceType: granite/ui/components/coral/foundation/form/switch
name: ./analyticsTracking
fieldLabel: Analytics tracking
checked: true
13. QA Acceptance Checklist
- All three variants (standard, flush, always-open) render correctly with expected styles and spacing.
- Expand and collapse behaviour works correctly — single-expand mode closes siblings; always-open mode allows multiple panels.
- Keyboard navigation works: Tab to focus accordion buttons, Enter/Space to toggle.
-
aria-expanded,aria-controls, andaria-labelledbyattributes are correct in all states. - FAQ schema markup (
itemscope/itemprop) renders correctly when enabled and validates in Google's Rich Results Test. - Analytics data attributes are present and expand/collapse events fire correctly to the data layer.
14. Definition of Done
| Area | Done means |
|---|---|
| Design | Figma component matches this contract. All variants (standard, flush, always-open, FAQ schema) are documented. Tokens are used — no hard-coded colour values. |
| Development | AEM proxy component created extending Core Accordion. HTL template emits semantic HTML per Section 6. CSS uses design tokens. JS wires analytics expand/collapse events. All 4 legacy components (#3, #19, #14, #15) are replaced. 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). FAQ schema validated. |
| Launch | Component deployed to AEM production. Legacy accordion components deprecated and migration plan communicated. Content authors trained. Analytics dashboards confirmed receiving events. |