Component Contract
Dropdown
Toggleable contextual menu for actions, filters, or secondary navigation within CAAT digital properties.
Live Demo
Standard Dropdown
Rendered
Split Button Dropdown
Rendered
Headers, Dividers & Disabled Items
Rendered
Directional Variants
Rendered
Dark Dropdown
Rendered
Open Menu
The menu panels shown statically (with .show) so the menu anatomy — headers, items, dividers, disabled — is visible without interaction.
Rendered
States
Follows the Interaction States matrix. Menu items implement default, hover, focus, active (selected), and disabled. The static is-* classes mirror the live pseudo-class states.
Rendered
Code sample
HTML
<!-- Standard dropdown -->
<div class="caat-dropdown dropdown" data-analytics-component="dropdown">
<button class="caat-button caat-button--primary dropdown-toggle"
type="button" data-bs-toggle="dropdown"
aria-expanded="false" aria-haspopup="true">
Trigger Label
</button>
<ul class="dropdown-menu caat-dropdown__menu">
<li><a class="dropdown-item caat-dropdown__item" href="#"
data-analytics-label="dropdown-select">Item One</a></li>
<li><a class="dropdown-item caat-dropdown__item" href="#"
data-analytics-label="dropdown-select">Item Two</a></li>
</ul>
</div>
<!-- Split button dropdown -->
<div class="caat-dropdown btn-group" data-analytics-component="dropdown">
<button type="button" class="caat-button caat-button--primary">
Primary Action
</button>
<button type="button"
class="caat-button caat-button--primary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" aria-expanded="false" aria-haspopup="true">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu caat-dropdown__menu">
<li><a class="dropdown-item caat-dropdown__item" href="#">Alt Action</a></li>
</ul>
</div>
<!-- Directional: .dropup, .dropend, .dropstart -->
<div class="caat-dropdown dropup">…</div>
<!-- Dark menu: add .dropdown-menu-dark -->
<ul class="dropdown-menu dropdown-menu-dark caat-dropdown__menu">…</ul>
<!-- Headers, dividers, disabled items -->
<li><h6 class="dropdown-header">Section Header</h6></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item disabled" aria-disabled="true">Locked</a></li>
1. Component Summary
| Component name | Dropdown |
|---|---|
| AEM component name | caat/components/dropdown |
| Recommended implementation | Custom AEM component wrapping Bootstrap 5 dropdown |
| Component group | CAAT Design System |
| Purpose | Toggleable contextual menu for actions, filters, or secondary navigation |
| Status | New |
Primary use cases
- Action menus — contextual actions on a record or section (e.g. Download, Print, Share)
- Filter menus — quick filter selections for tables or lists
- Secondary navigation — overflow or auxiliary page links
- Split buttons — primary action with alternative options
Non-goals
- Not for form field selection — use the native
<select>or Forms component instead - Not for primary navigation — use the Navigation component
- Not for mega menus or multi-level nesting
- Not for tooltips or popovers — use dedicated tooltip components
2. Design System Source / Token Dependencies
| Token | Value (reference) | Purpose |
|---|---|---|
--caat-blue-900 | #003750 | Menu item text colour on hover |
--caat-blue-700 | #0b5a80 | Primary trigger button background |
--caat-blue-100 | #e7f4fb | Active/selected item background |
--caat-grey-200 | #dfe6ef | Divider colour, item hover background |
--caat-grey-100 | #e8ecf1 | Menu background |
--caat-focus | 0 0 0 .25rem rgba(47,149,210,.35) | Focus ring on trigger button and menu items |
--caat-font-primary | "Libre Franklin", Arial, sans-serif | Menu typeface |
--caat-shadow-sm | 0 .125rem .25rem rgba(0,0,0,.075) | Menu elevation shadow |
3. Authoring Fields
| Field | Type | Required | Description |
|---|---|---|---|
| Trigger label | Textfield | Yes | Text displayed on the dropdown trigger button |
| Trigger style | Dropdown | Yes | Button variant: primary, outline-primary |
| Dropdown type | Dropdown | Yes | Standard button or split button |
| Direction | Dropdown | No | Menu direction: dropdown (default), dropup, dropstart, dropend |
| Dark menu | Toggle/Switch | No | Enables .dropdown-menu-dark variant |
| Items | Multifield | Yes | Menu items — each item has the fields below |
Items multifield
| Field | Type | Required | Description |
|---|---|---|---|
| Item type | Select | Yes | One of: link, button, header, divider |
| Label | Textfield | Yes (except divider) | Visible text for the menu item or header |
| URL | Path / URL | Conditional | Required when item type is link |
| Disabled | Toggle/Switch | No | Renders the item as non-interactive with .disabled and aria-disabled="true" |
| Icon | Textfield | No | Bootstrap Icon class (e.g. bi-eye) prepended to the label |
4. Validation Rules
| Field | Rule |
|---|---|
| Trigger label | Required, max 60 characters |
| Items | At least one item of type link or button required |
| Item label | Required for link, button, and header types; max 80 characters |
| URL | Required when item type is link; must be a valid internal path or external URL |
| Direction | Must be one of: dropdown, dropup, dropstart, dropend |
5. Content Guidance
Do
- Use concise, action-oriented labels for menu items (e.g. "Download PDF", "Edit Profile")
- Group related items using headers and dividers
- Use split buttons when there is a clear primary action with secondary alternatives
- Limit menus to 7–10 items maximum for scannability
- Use icons sparingly to aid recognition, not decoration
Don't
- Don't use dropdowns for form field selection — use
<select>instead - Don't nest dropdowns within dropdowns
- Don't use vague labels like "More" or "Options" without context
- Don't put critical, frequently-used actions only inside a dropdown — make them visible
- Don't mix navigation links and destructive actions in the same menu without visual separation
6. Semantic HTML
<!-- Standard dropdown -->
<div class="caat-dropdown ${directionClass}"
data-analytics-component="dropdown">
<button class="caat-button caat-button--${triggerVariant} dropdown-toggle"
type="button" data-bs-toggle="dropdown"
aria-expanded="false" aria-haspopup="true">
${triggerLabel}
</button>
<ul class="dropdown-menu ${darkClass} caat-dropdown__menu">
<sly data-sly-list="${items}">
<!-- header -->
<li data-sly-test="${item.type == 'header'}">
<h6 class="dropdown-header">${item.label}</h6>
</li>
<!-- divider -->
<li data-sly-test="${item.type == 'divider'}">
<hr class="dropdown-divider">
</li>
<!-- link -->
<li data-sly-test="${item.type == 'link'}">
<a class="dropdown-item caat-dropdown__item ${item.disabled ? 'disabled' : ''}"
href="${item.url}" aria-disabled="${item.disabled}"
data-analytics-label="dropdown-select">
<i class="bi ${item.icon} me-2"
data-sly-test="${item.icon}" aria-hidden="true"></i>
${item.label}
</a>
</li>
<!-- button -->
<li data-sly-test="${item.type == 'button'}">
<button class="dropdown-item caat-dropdown__item ${item.disabled ? 'disabled' : ''}"
type="button" ${item.disabled ? 'disabled' : ''}
data-analytics-label="dropdown-select">
<i class="bi ${item.icon} me-2"
data-sly-test="${item.icon}" aria-hidden="true"></i>
${item.label}
</button>
</li>
</sly>
</ul>
</div>
<!-- Split button variant -->
<div class="caat-dropdown btn-group ${directionClass}"
data-analytics-component="dropdown">
<button type="button"
class="caat-button caat-button--${triggerVariant}">
${triggerLabel}
</button>
<button type="button"
class="caat-button caat-button--${triggerVariant} dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" aria-expanded="false" aria-haspopup="true">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu ${darkClass} caat-dropdown__menu">
…items…
</ul>
</div>
7. CSS Contract
| Class | Element | Purpose |
|---|---|---|
.caat-dropdown | Root wrapper | Wraps the Bootstrap .dropdown (or .dropup, .dropend, .dropstart); applies CAAT token overrides |
.caat-dropdown__menu | Menu panel | Extends .dropdown-menu; applies CAAT font, shadow, border-radius, and background tokens |
.caat-dropdown__item | Menu item | Extends .dropdown-item; applies CAAT hover, active, focus, and disabled styles using design tokens |
CSS file
/assets/css/components/dropdown.css
All custom styles must use CAAT design tokens. No hard-coded colour values. The .caat-dropdown class wraps Bootstrap's dropdown classes to layer CAAT-specific visual overrides without breaking Bootstrap functionality.
8. Accessibility
| Requirement | Implementation |
|---|---|
aria-haspopup="true" | Applied to the trigger button to indicate it opens a menu |
aria-expanded | Set to "false" when closed, "true" when open; managed automatically by Bootstrap |
| Arrow-key navigation | ↓ / ↑ move focus between menu items (Bootstrap built-in) |
| Escape to close | Pressing Escape closes the menu and returns focus to the trigger button (Bootstrap default) |
| Disabled items | Disabled items use .disabled class and aria-disabled="true"; for <button> types, also include the disabled attribute |
| Focus management | When the menu opens, focus moves to the first non-disabled item. On close, focus returns to the trigger |
| Role semantics | Bootstrap applies role="menu" and role="menuitem" automatically via JS for keyboard-navigated dropdowns |
Keyboard interaction
- Enter / Space — open the dropdown (on trigger) or activate the focused item
- ↓ — open the dropdown (on trigger) or move focus to the next item
- ↑ — move focus to the previous item
- Escape — close the dropdown and return focus to the trigger
- Tab — close the dropdown and move focus to the next focusable element on the page
- Home / End — move focus to the first / last item in the menu
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
- Component path:
/apps/caat/components/dropdown - Resource type:
caat/components/dropdown - Component group: CAAT Design System
- Sling Model: Create a Sling Model to expose trigger label, trigger variant, dropdown type, direction, dark mode, and items multifield.
- Unique ID generation: Not required — Bootstrap manages dropdown state via
data-bs-togglewithout needing unique element IDs. - Client library:
caat.components.dropdown— includes CSS and JS for analytics event wiring. - Policies: Configure allowed item types and maximum item count in template policies.
12. Dialog Model (AEM Touch UI)
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/dropdown/_cq_dialog/.content.xml (pseudo-YAML)
jcr:root:
sling:resourceType: cq/gui/components/authoring/dialog
jcr:title: Dropdown
content:
sling:resourceType: granite/ui/components/coral/foundation/container
items:
tabs:
sling:resourceType: granite/ui/components/coral/foundation/tabs
items:
# ── Content tab ──────────────────────────────
content:
jcr:title: Content
items:
triggerLabel:
sling:resourceType: granite/ui/components/coral/foundation/form/textfield
name: ./triggerLabel
fieldLabel: Trigger label
required: true
maxlength: 60
triggerStyle:
sling:resourceType: granite/ui/components/coral/foundation/form/select
name: ./triggerStyle
fieldLabel: Trigger button style
required: true
items:
- { text: Primary, value: primary }
- { text: Outline Primary, value: outline-primary }
dropdownType:
sling:resourceType: granite/ui/components/coral/foundation/form/select
name: ./dropdownType
fieldLabel: Dropdown type
required: true
items:
- { text: Standard, value: standard }
- { text: Split Button, value: split }
items:
sling:resourceType: granite/ui/components/coral/foundation/form/multifield
name: ./items
fieldLabel: Menu items
composite: true
items:
field:
sling:resourceType: granite/ui/components/coral/foundation/container
items:
itemType:
sling:resourceType: granite/ui/components/coral/foundation/form/select
name: ./itemType
fieldLabel: Item type
required: true
items:
- { text: Link, value: link }
- { text: Button, value: button }
- { text: Header, value: header }
- { text: Divider, value: divider }
label:
sling:resourceType: granite/ui/components/coral/foundation/form/textfield
name: ./label
fieldLabel: Label
maxlength: 80
url:
sling:resourceType: granite/ui/components/coral/foundation/form/pathfield
name: ./url
fieldLabel: URL
disabled:
sling:resourceType: granite/ui/components/coral/foundation/form/switch
name: ./disabled
fieldLabel: Disabled
checked: false
icon:
sling:resourceType: granite/ui/components/coral/foundation/form/textfield
name: ./icon
fieldLabel: Icon class (e.g. bi-eye)
# ── Style tab ────────────────────────────────
style:
jcr:title: Style
items:
direction:
sling:resourceType: granite/ui/components/coral/foundation/form/select
name: ./direction
fieldLabel: Direction
items:
- { text: Down (default), value: dropdown }
- { text: Up, value: dropup }
- { text: End, value: dropend }
- { text: Start, value: dropstart }
defaultValue: dropdown
darkMenu:
sling:resourceType: granite/ui/components/coral/foundation/form/switch
name: ./darkMenu
fieldLabel: Dark menu
checked: false
# ── Behaviour tab ────────────────────────────
behaviour:
jcr:title: Behaviour
items:
analyticsTracking:
sling:resourceType: granite/ui/components/coral/foundation/form/switch
name: ./analyticsTracking
fieldLabel: Analytics tracking
checked: true
13. QA Acceptance Checklist
- Standard dropdown opens and closes on trigger click.
- Split button dropdown renders primary action button and separate toggle.
- Headers, dividers, and disabled items render correctly and disabled items are non-interactive.
- Directional variants (dropup, dropend, dropstart) position the menu correctly.
- Dark dropdown variant applies dark background and light text.
-
aria-expandedtoggles betweentrueandfalseon open/close. -
aria-haspopup="true"is present on the trigger button. - ↓ / ↑ arrow keys navigate menu items.
- Escape closes the menu and returns focus to the trigger.
- Screen reader announces trigger as a popup button and reads menu items.
- Analytics events (
dropdown-open,dropdown-select) fire correctly with item labels. - Icons render correctly when configured, with
aria-hidden="true". - Cross-browser tested: Chrome, Firefox, Safari, Edge.
- Responsive: dropdown is usable on mobile viewports without overflow.
14. Definition of Done
| Area | Done means |
|---|---|
| Design | Figma component matches this contract. All variants (standard, split, directional, dark) are documented. Tokens are used — no hard-coded colour values. |
| Development | AEM component created at /apps/caat/components/dropdown. HTL template emits semantic HTML per Section 6. CSS uses design tokens per Section 7. JS wires Bootstrap dropdown events to analytics data layer. Sling Model exposes all authoring fields. 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). Keyboard navigation verified with screen reader. |
| Launch | Component deployed to AEM production. Content authors trained on authoring dialog. Analytics dashboards confirmed receiving dropdown-open and dropdown-select events. |