Component Contract

Tabs

Organize related content into switchable panels — reduce page length while keeping content discoverable.

Live Demo

Standard Horizontal Tabs

Rendered

CAAT DBplus Plan Overview

The CAAT DBplus pension plan is a defined benefit plan that provides predictable, lifetime retirement income. Unlike defined contribution plans, your benefit is based on a formula — not market performance — so you always know what to expect in retirement.

Key features include immediate vesting, portability between participating employers, and conditional inflation protection.

Benefits for Members

As a CAAT DBplus member, you receive a secure, predictable pension income for life. Your contributions are matched by your employer, and your benefit grows with every year of service.

  • Immediate vesting — your pension is yours from day one
  • Survivor benefits for your spouse or beneficiary
  • Conditional inflation protection to help maintain purchasing power
  • Early retirement options available from age 55

Information for Employers

Offering CAAT DBplus helps you attract and retain talent with a best-in-class pension benefit — without the governance burden of running your own plan.

Contribution rates are stable and predictable. CAAT manages all investments, administration, and regulatory compliance on your behalf.

Pill-Style Tabs

Rendered

Both you and your employer contribute to your CAAT DBplus pension. Contribution rates are set by the plan and are designed to keep the plan fully funded while providing a meaningful retirement benefit.

Your retirement income is calculated using a formula based on your contributions and years of service. You can begin receiving an unreduced pension at age 60, or opt for a reduced pension as early as age 55.

If you pass away before or after retirement, your spouse or designated beneficiary may be eligible for a survivor pension or a lump-sum death benefit, depending on your elections and membership status.

Vertical Tabs

Rendered

Who is Eligible?

All employees of participating employers are eligible to join CAAT DBplus. There is no minimum age or service requirement — you can begin earning pension benefits from your first day of employment.

How to Enrol

Enrolment is handled by your employer's HR or payroll department. Once enrolled, contributions are automatically deducted from your pay and your employer makes matching contributions on your behalf.

Options When Leaving

If you leave a participating employer, your earned pension benefit stays with CAAT. You can choose to leave it in the plan and collect it at retirement, or explore transfer options if available.

States

Follows the Interaction States matrix. Tab buttons implement default, hover, active (selected), and focus-visible. The static is-* classes mirror the live pseudo-class states for documentation and Figma export.

Rendered — underline tabs

Rendered — pill tabs

Code sample

HTML

<!-- Standard horizontal tabs -->
<div class="caat-tabs" data-analytics-component="tabs">
  <ul class="nav nav-tabs" id="myTabs" role="tablist">
    <li class="nav-item" role="presentation">
      <button class="nav-link active" id="tab1-tab"
              data-bs-toggle="tab"
              data-bs-target="#tab1-panel"
              type="button" role="tab"
              aria-controls="tab1-panel"
              aria-selected="true"
              data-analytics-label="tab-1">
        Tab Label
      </button>
    </li>
  </ul>
  <div class="tab-content">
    <div class="tab-pane fade show active"
         id="tab1-panel" role="tabpanel"
         aria-labelledby="tab1-tab" tabindex="0">
      Panel content here.
    </div>
  </div>
</div>

<!-- Pill variant — replace .nav-tabs with .nav-pills -->
<ul class="nav nav-pills" role="tablist">…</ul>

<!-- Vertical variant — add flex + aria-orientation -->
<div class="caat-tabs caat-tabs--vertical d-flex">
  <ul class="nav nav-tabs flex-column me-3"
      role="tablist" aria-orientation="vertical">…</ul>
  <div class="tab-content flex-grow-1">…</div>
</div>

1. Component Summary

Component nameTabs
AEM component namecaat/components/tabs
Recommended implementationExtend the AEM Core Tabs component (core/wcm/components/tabs)
Component groupCAAT Design System
PurposeOrganize related content into switchable panels; reduce page length while keeping content discoverable
Existing AEM components replaced Tabs (#50). Replaces 1 existing component — rebuild required.
AEM Triage Rebuild

Primary use cases

  1. Plan details pages — separating member, employer, and general information into parallel views
  2. Account dashboard — switching between contribution history, benefit projections, and personal info
  3. Product comparison — side-by-side feature comparison across plan types
  4. Service pages — organizing content by audience or topic without long scroll

Non-goals

  1. This component is not for sequential workflows — use a Stepper component for multi-step processes
  2. It is not for primary site navigation — use the Navigation component instead
  3. Do not use tabs to hide critical information that all users must read — such content must be visible without interaction

2. Design System Source / Token Dependencies

TokenValue (reference)Purpose
--caat-blue-900#003750Active tab text colour, bottom border indicator
--caat-green#55a546Active pill background
--caat-blue-100#e7f4fbTab hover background tint
--caat-grey-200#dfe6efTab border colour, panel divider
--caat-focus0 0 0 .25rem rgba(47,149,210,.35)Focus ring on tab buttons
--caat-ink#102637Panel body text colour
--caat-font-primary"Libre Franklin", Arial, sans-serifTab typeface

Visual variants

  • Standard tabs (.nav-tabs) — underlined active indicator, bordered content panel
  • Pills (.nav-pills) — filled capsule-style active indicator
  • Vertical (.caat-tabs--vertical) — side-navigation layout with tabs stacked vertically

3. Authoring Fields

Content tab

FieldTypeRequiredDefaultNotes
Tab label (per item)Text fieldYesVisible text on the tab button. Keep concise (1–3 words).
Panel content (per item)Rich text editorYesSupports paragraphs, lists, links, images, and embedded components.
Icon (per item)Icon pickerNoOptional Bootstrap icon displayed before the tab label.
Active by defaultToggle (per item)NoFirst itemDesignates which tab panel is visible on initial page load. Only one item should be active.

Style tab

FieldTypeRequiredDefaultNotes
VariantDropdownYesStandardstandard | pills | vertical
Full-width tabsToggleNoOffWhen on, tabs stretch to fill the available width equally (.nav-justified).

Behaviour tab

FieldTypeRequiredDefaultNotes
Analytics trackingToggleNoOnWhen on, emits data-analytics-* attributes and fires tab-switch events.
Deep-link tabsToggleNoOffWhen on, appends a hash to the URL when a tab is activated, enabling direct linking to a specific tab.

4. Authoring Validation Rules

  1. At least two tab items are required. A single-tab interface provides no benefit — use a standard content block instead.
  2. Tab label is required for every item. The dialog must not allow saving a tab without a label.
  3. Maximum of 7 tabs recommended. More than 7 creates cognitive overload and responsive layout issues. The dialog should display a warning above this threshold.
  4. Only one tab may be marked "active by default." If none is selected, the first tab is active. If multiple are checked, only the first applies.
  5. Authors must never hide essential action items or legal disclosures in non-default tab panels that members are required to acknowledge.

5. Content Guidance

Good examples

  • Short, scannable tab labels: "Overview", "Benefits", "Eligibility"
  • Parallel content structure — each panel covers a similar scope for its topic
  • Use when audiences differ: "For Members" / "For Employers"
  • Keep panels roughly equal in content length
  • Ensure the default tab contains the most commonly sought information

Avoid

  • Long tab labels: "Information About Your Retirement Benefits"
  • More than 7 tabs — consider a different pattern (e.g., accordion or sub-pages)
  • Nesting tabs within tabs — use a flat hierarchy
  • Using tabs for sequential content that must be read in order
  • Extremely uneven panel lengths (one sentence vs. one full page)

6. Expected Semantic HTML Output

Standard tabs

<div class="caat-tabs" data-analytics-component="tabs">
  <ul class="nav nav-tabs" id="planTabs" role="tablist">
    <li class="nav-item" role="presentation">
      <button class="nav-link active" id="tab1-tab"
              data-bs-toggle="tab"
              data-bs-target="#tab1-panel"
              type="button" role="tab"
              aria-controls="tab1-panel"
              aria-selected="true">
        Tab Label
      </button>
    </li>
    <li class="nav-item" role="presentation">
      <button class="nav-link" id="tab2-tab"
              data-bs-toggle="tab"
              data-bs-target="#tab2-panel"
              type="button" role="tab"
              aria-controls="tab2-panel"
              aria-selected="false">
        Tab Label 2
      </button>
    </li>
  </ul>
  <div class="tab-content">
    <div class="tab-pane fade show active"
         id="tab1-panel" role="tabpanel"
         aria-labelledby="tab1-tab" tabindex="0">
      <p>Panel content.</p>
    </div>
    <div class="tab-pane fade"
         id="tab2-panel" role="tabpanel"
         aria-labelledby="tab2-tab" tabindex="0">
      <p>Panel content.</p>
    </div>
  </div>
</div>

Vertical tabs

<div class="caat-tabs caat-tabs--vertical d-flex"
     data-analytics-component="tabs">
  <ul class="nav nav-tabs flex-column me-3"
      id="vertTabs" role="tablist"
      aria-orientation="vertical">
    <li class="nav-item" role="presentation">
      <button class="nav-link active" id="vtab1-tab"
              data-bs-toggle="tab"
              data-bs-target="#vtab1-panel"
              type="button" role="tab"
              aria-controls="vtab1-panel"
              aria-selected="true">
        Tab Label
      </button>
    </li>
  </ul>
  <div class="tab-content flex-grow-1">
    <div class="tab-pane fade show active"
         id="vtab1-panel" role="tabpanel"
         aria-labelledby="vtab1-tab" tabindex="0">
      Panel content.
    </div>
  </div>
</div>

Tab with icon

<button class="nav-link" role="tab" …>
  <i class="bi bi-people me-1" aria-hidden="true"></i>
  Members
</button>

7. CSS Contract

The tabs component relies on the global token layer defined in /assets/css/tokens.css. All component-specific styles live in /assets/css/components/tabs.css.

Base styles

.caat-tabs {
  font-family: var(--caat-font-primary, "Libre Franklin", Arial, sans-serif);
}

.caat-tabs .nav-tabs {
  border-bottom-color: var(--caat-grey-200, #e9ecef);
}

.caat-tabs .nav-tabs .nav-link {
  font-weight: 600;
  color: var(--caat-ink, #212529);
  border: none;
  border-bottom: 3px solid transparent;
  padding: .75rem 1.25rem;
}

.caat-tabs .nav-tabs .nav-link:hover {
  background-color: var(--caat-blue-100, #e6f0f7);
  border-bottom-color: var(--caat-grey-200, #e9ecef);
}

.caat-tabs .nav-tabs .nav-link.active {
  color: var(--caat-blue-900, #003750);
  border-bottom-color: var(--caat-blue-900, #003750);
  background-color: transparent;
}

/* Pills variant */
.caat-tabs .nav-pills .nav-link.active {
  background-color: var(--caat-green, #00874c);
  color: #fff;
}

/* Tab content panel */
.caat-tabs .tab-content {
  padding: 1.5rem 0;
  color: var(--caat-ink, #212529);
}

/* Focus ring */
.caat-tabs .nav-link:focus-visible {
  box-shadow: var(--caat-focus);
  outline: 0;
}

/* Vertical variant */
.caat-tabs--vertical .nav-tabs {
  border-bottom: none;
  border-right: 1px solid var(--caat-grey-200, #e9ecef);
}

.caat-tabs--vertical .nav-tabs .nav-link {
  border-bottom: none;
  border-right: 3px solid transparent;
  text-align: left;
}

.caat-tabs--vertical .nav-tabs .nav-link.active {
  border-right-color: var(--caat-blue-900, #003750);
  border-bottom-color: transparent;
}
Note: Do not duplicate token values inside the component CSS. Always reference the custom property (e.g. var(--caat-blue-900)) so that theme changes propagate automatically.

8. Accessibility Requirements

RequirementAcceptance criteria
Tab list role The <ul> containing tab buttons must have role="tablist".
Tab role Each tab button must have role="tab".
Tab panel role Each content panel must have role="tabpanel" and aria-labelledby pointing to its corresponding tab button.
aria-selected The active tab must have aria-selected="true"; all others must have aria-selected="false".
aria-controls Each tab button must have aria-controls pointing to the id of its associated panel.
Keyboard — Arrow keys Left/Right arrow keys move focus between tabs in horizontal mode. Up/Down arrows in vertical mode. Focus wraps at boundaries.
Keyboard — Activation Focused tab is activated on Enter or Space (or automatically on focus — follow Bootstrap default).
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.
Vertical orientation Vertical tabs must include aria-orientation="vertical" on the tablist so assistive technology announces correct arrow key behaviour.
Colour contrast Text-to-background contrast must meet WCAG 2.1 AA (4.5 : 1 for normal text) in all tab states (default, hover, active, focus).

9. SEO Requirements

  1. All tab panel content must be present in the initial HTML response — do not lazy-load panel content via AJAX, as search engines must be able to index all panels.
  2. Use semantic headings within panels where appropriate to support document outline.
  3. If deep-linking is enabled, ensure each tab panel URL is crawlable and renders the correct panel content for social sharing and link previews.
  4. Avoid duplicating the same content across multiple tab panels — each panel should contain unique information.

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 replaces:

  • Tabs (#50) — existing tab component requiring rebuild to meet accessibility and design token standards

AEM triage

Rebuild The existing Tabs component (#50) must be rebuilt to use Bootstrap 5 tab markup, CAAT design tokens, proper ARIA roles, and analytics data attributes.

Proxy / extend guidance

Create a proxy component at /apps/caat/components/tabs that extends the AEM Core Tabs component (core/wcm/components/tabs) where feasible. Override the HTL template to emit CAAT class names, role attributes, and data-analytics attributes.

Client library structure

  • Category: caat.components.tabs
  • Dependencies: caat.base (tokens + base styles)
  • CSS: tabs.css
  • JS: tabs.js (analytics wiring, deep-link hash handling, tab-switch event tracking)

Suggested file structure

tabs/
├── _cq_dialog/
│   └── .content.xml          # Touch UI dialog
├── _cq_editConfig.xml         # Edit configuration
├── .content.xml                # Component node definition
├── tabs.html                   # HTL template
├── clientlibs/
│   ├── .content.xml
│   ├── css/
│   │   └── tabs.css
│   └── js/
│       └── tabs.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/tabs — 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:
              tabItems:
                sling:resourceType: granite/ui/components/coral/foundation/form/multifield
                name: ./tabItems
                fieldLabel: Tab items
                composite: true
                required: true
                items:
                  field:
                    sling:resourceType: granite/ui/components/coral/foundation/container
                    items:
                      label:
                        sling:resourceType: granite/ui/components/coral/foundation/form/textfield
                        name: ./label
                        fieldLabel: Tab label
                        required: true
                      panelContent:
                        sling:resourceType: granite/ui/components/coral/foundation/form/richtext
                        name: ./panelContent
                        fieldLabel: Panel content
                        required: true
                      icon:
                        sling:resourceType: granite/ui/components/coral/foundation/form/textfield
                        name: ./icon
                        fieldLabel: Icon (Bootstrap Icons class)
                        emptyText: "e.g. bi-people"
                      activeByDefault:
                        sling:resourceType: granite/ui/components/coral/foundation/form/checkbox
                        name: ./activeByDefault
                        text: Active 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 (underline), value: standard }
                  - { text: Pills,               value: pills }
                  - { text: Vertical,            value: vertical }
              fullWidth:
                sling:resourceType: granite/ui/components/coral/foundation/form/switch
                name: ./fullWidth
                fieldLabel: Full-width tabs
                checked: false

          # ── Behaviour tab ────────────────────────────
          behaviour:
            jcr:title: Behaviour
            items:
              deepLink:
                sling:resourceType: granite/ui/components/coral/foundation/form/checkbox
                name: ./deepLink
                text: Enable deep-linking (hash URL)
              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, pills, vertical) render correctly with expected styles and spacing.
  • Tab switching works — clicking a tab shows its panel and hides others.
  • Keyboard navigation works: Arrow keys move between tabs, Enter/Space activates.
  • role="tablist", role="tab", and role="tabpanel" are present and correct.
  • aria-selected, aria-controls, and aria-labelledby update correctly on tab switch.
  • Vertical tabs include aria-orientation="vertical" on the tablist.
  • Deep-link hash updates the URL and activating a tab via hash on page load works correctly.
  • Analytics data-analytics-* attributes are present and tab-switch events fire to the data layer.
  • Responsive behaviour: tabs stack or scroll horizontally on small viewports without content clipping.
  • Focus ring is visible and meets 3 : 1 contrast on all tab buttons.

14. Definition of Done

AreaDone means
Design Figma component matches this contract. All variants (standard, pills, vertical) are documented. Tokens are used — no hard-coded colour values.
Development AEM proxy component created extending Core Tabs. HTL template emits semantic HTML per Section 6. CSS uses design tokens. JS wires analytics tab-switch events and optional deep-link hash. Legacy Tabs (#50) 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). Screen reader tested with NVDA and VoiceOver.
Launch Component deployed to AEM production. Legacy Tabs component deprecated and migration plan communicated. Content authors trained. Analytics dashboards confirmed receiving tab-switch events.