Component Contract

Button

A reusable, accessible call-to-action component for the CAAT Pension Plan digital properties.

Live Demo

Variants

Sizes

Rendered

States

Follows the Interaction States matrix. Buttons implement default, hover, focus-visible, active, disabled, and loading; error / selected / read-only do not apply.

Rendered

Special use cases

Code sample

HTML

<!-- Primary link button -->
<a href="/learn-more" class="caat-button caat-button--primary">Learn more</a>

<!-- Secondary button -->
<a href="/get-started" class="caat-button caat-button--secondary">Get started</a>

<!-- Outline primary -->
<a href="/details" class="caat-button caat-button--outline-primary">View details</a>

<!-- Small size -->
<a href="/info" class="caat-button caat-button--primary caat-button--sm">Small button</a>

<!-- With icon -->
<a href="/report.pdf" download class="caat-button caat-button--primary">
  <i class="bi bi-download caat-button__icon" aria-hidden="true"></i> Download
</a>

<!-- External link -->
<a href="https://example.com" target="_blank" rel="noopener"
   class="caat-button caat-button--outline-primary">
  Visit site <i class="bi bi-box-arrow-up-right caat-button__icon" aria-hidden="true"></i>
</a>

<!-- Submit button -->
<button type="submit" class="caat-button caat-button--secondary">Submit</button>

1. Component Summary

Component nameButton
AEM component namecaat/components/button
Recommended implementationExtend or proxy the AEM Core Button component (core/wcm/components/button/v2/button)
Component groupCAAT Design System
PurposeProvide a reusable, accessible call-to-action component that aligns with CAAT brand standards
Existing AEM component replacedCAAT Button (#4 in existing library)

Primary use cases

  1. Navigate a member to a new page (internal link)
  2. Open an external resource in a new tab
  3. Trigger a form submission
  4. Download a file (PDF, XLSX)
  5. Anchor-link within a long-form page
  6. Call-to-action within a card, hero, or alert component

Non-goals

  1. This component does not handle icon-only toggle buttons (use a separate Toggle component)
  2. It does not render navigation menus or dropdowns
  3. It does not manage client-side routing or SPA behaviour
  4. It does not replace inline text hyperlinks within body copy

2. Design System Source / Token Dependencies

TokenValue (reference)Purpose
--caat-blue-900#003750Primary button background & border
--caat-blue-700#0b5a80Primary hover background
--caat-green#55a546Secondary button background
--caat-green-700#367f32Secondary hover / outline-secondary border
--caat-focus0 0 0 .25rem rgba(47,149,210,.35)Focus ring on all variants
--caat-radius.75remBorder radius
--caat-font-primary"Libre Franklin", Arial, sans-serifButton typeface

Visual variants

  • Primary — solid dark-blue background, white text
  • Secondary — solid green background, white text
  • Outline Primary — transparent background, dark-blue border and text
  • Outline Secondary — transparent background, green border and text
  • Text Link — no background or border, styled as an underline-on-hover link

Sizes

  • Small (.caat-button--sm) — min-height 2.25 rem
  • Default — min-height 2.75 rem
  • Large (.caat-button--lg) — min-height 3.25 rem

3. Authoring Fields

Content tab

FieldTypeRequiredDefaultNotes
Button textText fieldYesVisible label. Max 60 characters recommended.
Link URLPath browserYesInternal or external URL. Validated by link checker.
Link targetCheckboxNoSame tabIf checked, opens in new tab and adds rel="noopener".
Accessible labelText fieldNoOverrides the visible text for screen readers (aria-label). Use when visible text alone is ambiguous.
IconDropdownNoNoneOptional leading or trailing icon. Options: none, download, external-link, arrow-right, chevron-right.

Style tab

FieldTypeRequiredDefaultNotes
VariantDropdownYesPrimaryprimary | secondary | outline-primary | outline-secondary | link
SizeDropdownNoDefaultsm | default | lg
Full width on mobileCheckboxNoOffAdds .caat-button--full-mobile.
AlignmentDropdownNoStartstart | centre | end. Applied to the wrapper element.

Behaviour tab

FieldTypeRequiredDefaultNotes
Link behaviourDropdownNoNavigatenavigate | download | submit
Download file nameText fieldNoShown only when link behaviour = download. Sets the download attribute value.
Analytics event nameText fieldNocta_clickCustom event name pushed to the data layer.
No-followCheckboxNoOffAdds rel="nofollow" for sponsored or external links.

4. Authoring Validation Rules

  1. Button text is required. The dialog must not allow saving without it.
  2. Link URL is required. A valid path or URL must be provided.
  3. Button text must not exceed 60 characters. Display a warning above 40 characters.
  4. If Link behaviour is set to "download", the Link URL must point to a DAM asset path (/content/dam/).
  5. If Link target is set to "New tab", the system must automatically append rel="noopener" to the rendered anchor.
  6. If Accessible label is left empty, the rendered component must not output an empty aria-label attribute.

5. Content Guidance

Good examples

  • "Learn about your pension"
  • "Download the 2025 annual report"
  • "Contact us"
  • "Get a pension estimate"
  • "View contribution details"

Avoid

  • "Click here"
  • "Read more"
  • "Submit" (without context)
  • "LEARN MORE ABOUT YOUR PENSION OPTIONS NOW!!!"
  • "Btn 1"

6. Expected Semantic HTML Output

Internal link

<a href="/en/members/pension-overview"
   class="caat-button caat-button--primary">
  Learn about your pension
</a>

External link — new tab

<a href="https://example.com/resource"
   class="caat-button caat-button--outline-primary"
   target="_blank"
   rel="noopener">
  Visit resource
  <i class="bi bi-box-arrow-up-right caat-button__icon" aria-hidden="true"></i>
</a>

Submit button

<button type="submit"
        class="caat-button caat-button--secondary">
  Submit your request
</button>

Download button

<a href="/content/dam/caat/reports/annual-report-2025.pdf"
   class="caat-button caat-button--primary"
   download="CAAT-Annual-Report-2025.pdf">
  <i class="bi bi-download caat-button__icon" aria-hidden="true"></i>
  Download the 2025 annual report
</a>

7. CSS Contract

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

Base styles

.caat-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  min-height: 2.75rem;
  padding: .7rem 1.15rem;
  border-radius: var(--caat-radius, .75rem);
  font-family: var(--caat-font-primary, "Libre Franklin", Arial, sans-serif);
  font-weight: 700;
  line-height: 1.25;
  text-decoration: none;
  border: 2px solid transparent;
  cursor: pointer;
  transition: all .18s ease;
}

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

Variant classes

SelectorPurpose
.caat-button--primarySolid dark-blue background, white text.
.caat-button--secondarySolid green background, white text.
.caat-button--outline-primaryTransparent bg, dark-blue border & text. Fills on hover.
.caat-button--outline-secondaryTransparent bg, green border & text. Fills on hover.
.caat-button--outline-whiteTransparent bg, white border & text. For dark backgrounds (hero, blue sections). Fills white with dark text on hover.
.caat-button--linkNo background/border — styled as text link with underline on hover.
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
Keyboard access All buttons must be reachable and operable via Tab and Enter/Space.
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.
Accessible name Every button must have a non-empty accessible name. If the visible text is ambiguous, use aria-label. Never render an empty aria-label.
Colour contrast Text-to-background contrast must meet WCAG 2.1 AA (4.5 : 1 for normal text, 3 : 1 for large text) in all states.
New tab behaviour Links opening in a new tab must include rel="noopener" and the visible label or aria-label should indicate the new-tab behaviour (e.g. "(opens in a new tab)").
Icon usage Decorative icons must have aria-hidden="true". The icon must never be the sole accessible name.

9. SEO Requirements

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 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 CAAT Button (component #4) in the existing AEM component library.

AEM triage

Rebuild Rebuild to new brand standards. The existing component does not meet current accessibility or design-token requirements.

Proxy / extend guidance

Create a proxy component at /apps/caat/components/button that extends the AEM Core Button v2 (core/wcm/components/button/v2/button). Override button.html to emit the CAAT class names and data attributes.

Client library structure

  • Category: caat.components.button
  • Dependencies: caat.base (tokens + base styles)
  • CSS: button.css
  • JS: button.js (analytics wiring, impression observer)

Suggested file structure

button/
├── _cq_dialog/
│   └── .content.xml          # Touch UI dialog
├── _cq_editConfig.xml         # Edit configuration
├── .content.xml                # Component node definition
├── button.html                 # HTL template
├── clientlibs/
│   ├── .content.xml
│   ├── css/
│   │   └── button.css
│   └── js/
│       └── button.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/button — 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:
              buttonText:
                sling:resourceType: granite/ui/components/coral/foundation/form/textfield
                name: ./buttonText
                fieldLabel: Button text
                required: true
                maxlength: 60
              linkURL:
                sling:resourceType: granite/ui/components/coral/foundation/form/pathfield
                name: ./linkURL
                fieldLabel: Link URL
                required: true
                rootPath: /content
              linkTarget:
                sling:resourceType: granite/ui/components/coral/foundation/form/checkbox
                name: ./linkTarget
                text: Open in new tab
                value: _blank
              accessibleLabel:
                sling:resourceType: granite/ui/components/coral/foundation/form/textfield
                name: ./accessibleLabel
                fieldLabel: Accessible label (optional)
              icon:
                sling:resourceType: granite/ui/components/coral/foundation/form/select
                name: ./icon
                fieldLabel: Icon
                items:
                  - { text: None,          value: none }
                  - { text: Download,      value: download }
                  - { text: External link, value: external-link }
                  - { text: Arrow right,   value: arrow-right }
                  - { text: Chevron right, value: chevron-right }

          # ── 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: Primary,           value: primary }
                  - { text: Secondary,         value: secondary }
                  - { text: Outline primary,   value: outline-primary }
                  - { text: Outline secondary, value: outline-secondary }
                  - { text: Text link,         value: link }
              size:
                sling:resourceType: granite/ui/components/coral/foundation/form/select
                name: ./size
                fieldLabel: Size
                items:
                  - { text: Small,   value: sm }
                  - { text: Default, value: default }
                  - { text: Large,   value: lg }
              fullMobile:
                sling:resourceType: granite/ui/components/coral/foundation/form/checkbox
                name: ./fullMobile
                text: Full width on mobile
              alignment:
                sling:resourceType: granite/ui/components/coral/foundation/form/select
                name: ./alignment
                fieldLabel: Alignment
                items:
                  - { text: Start,  value: start }
                  - { text: Centre, value: center }
                  - { text: End,    value: end }

          # ── Behaviour tab ────────────────────────────
          behaviour:
            jcr:title: Behaviour
            items:
              linkBehaviour:
                sling:resourceType: granite/ui/components/coral/foundation/form/select
                name: ./linkBehaviour
                fieldLabel: Link behaviour
                items:
                  - { text: Navigate, value: navigate }
                  - { text: Download, value: download }
                  - { text: Submit,   value: submit }
              downloadFileName:
                sling:resourceType: granite/ui/components/coral/foundation/form/textfield
                name: ./downloadFileName
                fieldLabel: Download file name
              analyticsEvent:
                sling:resourceType: granite/ui/components/coral/foundation/form/textfield
                name: ./analyticsEvent
                fieldLabel: Analytics event name
                value: cta_click
              noFollow:
                sling:resourceType: granite/ui/components/coral/foundation/form/checkbox
                name: ./noFollow
                text: Add rel="nofollow"

13. QA Acceptance Checklist

  • All five variants render correctly with expected colours and spacing.
  • All three sizes (sm, default, lg) meet the documented min-height values.
  • Hover, focus, active, and disabled states are visually distinct and meet WCAG contrast requirements.
  • Keyboard navigation works: Tab to focus, Enter/Space to activate.
  • External links open in a new tab with rel="noopener"; download links trigger a file download.
  • Analytics data attributes are present in the DOM and fire the correct events on click and impression.

14. Definition of Done

AreaDone means
Design Figma component matches this contract. All variants, sizes, and states are documented. Tokens are used — no hard-coded colour values.
Development AEM proxy component created. HTL template emits semantic HTML per Section 6. CSS uses design tokens. JS wires analytics events. 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. Documentation published. Analytics dashboards confirmed receiving events.