Component Contract
Button
A reusable, accessible call-to-action component for the CAAT Pension Plan digital properties.
Live Demo
Variants
Sizes
States
Follows the Interaction States matrix. Buttons implement default, hover, focus-visible, active, disabled, and loading; error / selected / read-only do not apply.
Special use cases
Rendered
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 name | Button |
|---|---|
| AEM component name | caat/components/button |
| Recommended implementation | Extend or proxy the AEM Core Button component (core/wcm/components/button/v2/button) |
| Component group | CAAT Design System |
| Purpose | Provide a reusable, accessible call-to-action component that aligns with CAAT brand standards |
| Existing AEM component replaced | CAAT Button (#4 in existing library) |
Primary use cases
- Navigate a member to a new page (internal link)
- Open an external resource in a new tab
- Trigger a form submission
- Download a file (PDF, XLSX)
- Anchor-link within a long-form page
- Call-to-action within a card, hero, or alert component
Non-goals
- This component does not handle icon-only toggle buttons (use a separate Toggle component)
- It does not render navigation menus or dropdowns
- It does not manage client-side routing or SPA behaviour
- It does not replace inline text hyperlinks within body copy
2. Design System Source / Token Dependencies
| Token | Value (reference) | Purpose |
|---|---|---|
--caat-blue-900 | #003750 | Primary button background & border |
--caat-blue-700 | #0b5a80 | Primary hover background |
--caat-green | #55a546 | Secondary button background |
--caat-green-700 | #367f32 | Secondary hover / outline-secondary border |
--caat-focus | 0 0 0 .25rem rgba(47,149,210,.35) | Focus ring on all variants |
--caat-radius | .75rem | Border radius |
--caat-font-primary | "Libre Franklin", Arial, sans-serif | Button 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
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| Button text | Text field | Yes | — | Visible label. Max 60 characters recommended. |
| Link URL | Path browser | Yes | — | Internal or external URL. Validated by link checker. |
| Link target | Checkbox | No | Same tab | If checked, opens in new tab and adds rel="noopener". |
| Accessible label | Text field | No | — | Overrides the visible text for screen readers (aria-label). Use when visible text alone is ambiguous. |
| Icon | Dropdown | No | None | Optional leading or trailing icon. Options: none, download, external-link, arrow-right, chevron-right. |
Style tab
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| Variant | Dropdown | Yes | Primary | primary | secondary | outline-primary | outline-secondary | link |
| Size | Dropdown | No | Default | sm | default | lg |
| Full width on mobile | Checkbox | No | Off | Adds .caat-button--full-mobile. |
| Alignment | Dropdown | No | Start | start | centre | end. Applied to the wrapper element. |
Behaviour tab
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| Link behaviour | Dropdown | No | Navigate | navigate | download | submit |
| Download file name | Text field | No | — | Shown only when link behaviour = download. Sets the download attribute value. |
| Analytics event name | Text field | No | cta_click | Custom event name pushed to the data layer. |
| No-follow | Checkbox | No | Off | Adds rel="nofollow" for sponsored or external links. |
4. Authoring Validation Rules
- Button text is required. The dialog must not allow saving without it.
- Link URL is required. A valid path or URL must be provided.
- Button text must not exceed 60 characters. Display a warning above 40 characters.
- If Link behaviour is set to "download", the Link URL must point to a DAM asset path (
/content/dam/). - If Link target is set to "New tab", the system must automatically append
rel="noopener"to the rendered anchor. - If Accessible label is left empty, the rendered component must not output an empty
aria-labelattribute.
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
| Selector | Purpose |
|---|---|
.caat-button--primary | Solid dark-blue background, white text. |
.caat-button--secondary | Solid green background, white text. |
.caat-button--outline-primary | Transparent bg, dark-blue border & text. Fills on hover. |
.caat-button--outline-secondary | Transparent bg, green border & text. Fills on hover. |
.caat-button--outline-white | Transparent bg, white border & text. For dark backgrounds (hero, blue sections). Fills white with dark text on hover. |
.caat-button--link | No background/border — styled as text link with underline on hover. |
var(--caat-blue-900))
so that theme changes propagate automatically.
8. Accessibility Requirements
| Requirement | Acceptance 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
| Area | Done 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. |