Accessibility
These guidelines ensure that all Cynosure components are usable by everyone, including people with disabilities. They are based on WCAG 2.2 AA standards and reference the specific interactive patterns used across the design system.
1. Semantic HTML first
Use native HTML elements whenever possible. Cynosure templates use semantic elements throughout — <button> for toggles, <nav> for navigation, <section> for page regions, <form> for form blocks.
1<!-- Good — native button handles keyboard and ARIA automatically -->
2<button type="button" class="c-accordion__toggle" aria-expanded="false">
3 Section title
4</button>
5
6<!-- Bad — requires manual keyboard handling and ARIA attributes -->
7<div class="c-accordion__toggle" onclick="toggle()">
8 Section title
9</div>2. Keyboard navigability
All interactive elements in Cynosure components must be operable by keyboard:
- Accordion (
accordion.js) — toggles are<button>elements; Enter/Space activates them natively - Modal (Bootstrap 5) — focus is trapped inside the modal while open; Escape closes it
- Tabs (
tabs.js) —.js-toggle-tabelements should be<button>or<a>tags so keyboard users can Tab to them and press Enter - Select2 (
selects.js) — arrow keys navigate options; Escape closes the dropdown; keyboard search is enabled whenminimumResultsForSearchis notInfinity - Magnific Popup (
popup.js) — focus moves to the popup on open; Tab cycles within it; Escape closes it - Navigation (
main-nav.js,mobile-nav.js) — dropdown menus must be keyboard accessible; mobile nav must be reachable via Tab
1// For custom interactive elements not using native button:
2element.addEventListener('keydown', (event) => {
3 if (event.key === 'Enter' || event.key === ' ') {
4 event.preventDefault();
5 // trigger action
6 }
7});3. Visible focus styles
Every interactive element must have a visible focus indicator. Cynosure SCSS should include :focus-visible states for all interactive components:
1.c-accordion__toggle:focus-visible {
2 outline: 2px solid var(--color-primary-orange);
3 outline-offset: 2px;
4}
5
6.c-header .c-main-nav a:focus-visible {
7 outline: 2px solid var(--color-primary-orange);
8 outline-offset: 2px;
9}Do not use outline: none without providing an alternative focus indicator.
4. Color and contrast
Cynosure brand colors and their usage:
- Text on white —
--color-text-base(rgba(0,0,0,.8)) meets AA at normal body sizes --color-primary-orange(#ff4713) — check contrast against the background before use as text; it meets AA at large sizes (18pt+) on white but not for small body text--color-secondary-purple(#6f2eff) — meets AA for body text on white--color-neutral-gray-600(#696969) — minimum for body text on white (3.98:1)- Status colors —
--color-extra-danger(#f25454) on white: use for error backgrounds only; pair with text that meets AA contrast independently
Always verify color combinations with WebAIM Contrast Checker. Do not convey state (error, success, warning) by color alone — pair with text or an icon.
5. ARIA labels and roles
Apply ARIA attributes where semantic HTML alone is insufficient:
Accordion:
1<button class="c-accordion__toggle" aria-expanded="false" aria-controls="accordion-panel-1">
2 Section title
3</button>
4<div id="accordion-panel-1" class="c-accordion__panel" role="region" aria-labelledby="toggle-1">
5 <!-- content -->
6</div>Modal (Bootstrap 5):
1<div class="modal" role="dialog" aria-labelledby="modal-title" aria-modal="true">
2 <div class="modal-dialog">
3 <h2 id="modal-title">Dialog heading</h2>
4 <!-- content -->
5 </div>
6</div>Navigation:
Select2 generates role="listbox" and aria-selected attributes automatically. Ensure the label element is properly associated with the native <select>:
1<label for="treatment-select">Select treatment</label>
2<div class="c-select">
3 <select id="treatment-select" name="treatment">
4 <option value="">Any</option>
5 </select>
6</div>6. Alternative text
All meaningful images must have descriptive alt text. In Handlebars templates, always surface the alt property:
SVGs loaded via svg-inline-loader.js should include a <title> element or aria-label attribute if they convey meaning. Decorative SVGs should have aria-hidden="true".
7. Headings and landmarks
Cynosure templates use landmark elements throughout:
1<header class="c-header" role="banner">...</header>
2<nav class="c-main-nav" aria-label="Main navigation">...</nav>
3<main><!-- page blocks --></main>
4<footer class="c-footer" role="contentinfo">...</footer>Block templates use <section> with a data-component attribute. Ensure each section that contains substantial content also has an accessible heading to give it a meaningful region name.
8. Dynamic content and live regions
Use ARIA live regions when JavaScript updates content without a page reload:
1<!-- Status messages (e.g., form submission feedback) -->
2<div aria-live="polite" class="c-status-message" id="status-msg"></div>The observer.js utility uses IntersectionObserver to trigger class additions as elements enter the viewport. When adding reveal animations via ScrollReveal (reveal.js), ensure the content is accessible before animation completes — do not rely on animation to reveal required content.
9. Form errors and validation
Gravity Forms and Bootstrap 5 form blocks must:
- Provide clear, specific error messages linked to the invalid field
- Not rely solely on color to indicate errors
- Associate error messages with inputs via
aria-describedby:
1<label for="email">Email</label>
2<input id="email" type="email" aria-describedby="email-error" aria-invalid="true">
3<span id="email-error" role="alert" class="c-form__error">
4 Please enter a valid email address.
5</span>10. Motion, animation, and timing
Cynosure uses ScrollReveal (reveal.js) for entrance animations and Slick Carousel for auto-advancing sliders. Both must respect prefers-reduced-motion:
1/* In style.scss for any animated component */
2@media (prefers-reduced-motion: reduce) {
3 .will-slide-right,
4 .will-slide-left,
5 .will-fade-in {
6 opacity: 1;
7 transform: none;
8 transition: none;
9 }
10}For Slick Carousel auto-play: do not enable autoplay: true by default. If auto-play is used, provide a pause/play control that is keyboard accessible.
11. Component-specific patterns
Accordion (.c-accordion)
- Toggle button must have
aria-expandedset to"true"or"false" - Panel must have
aria-hiddentoggled to match (or use CSSdisplay: nonewhich removes from accessibility tree) - Only one panel should be open at a time (current
accordion.jsenforces this)
Bootstrap Modal
- Add
aria-modal="true"andaria-labelledbypointing to the modal title - Bootstrap 5 handles focus trapping automatically
- Ensure the close button has
aria-label="Close"or visible text
Magnific Popup
- Popup containers need
role="dialog"andaria-labelledby - The close button (
.mfp-close) should havearia-label="Close popup" - Verify focus moves into the popup on open and returns to the trigger on close
Tabs (.c-tabs)
- Use
role="tablist"on the tab container,role="tab"on each tab button,role="tabpanel"on each panel - Set
aria-selected="true"on the active tab and"false"on others - Panels should have
aria-labelledbypointing to the corresponding tab
Select2 (.c-select)
- Always associate a visible
<label>with the native<select>element before Select2 initializes - The
minimumResultsForSearch: Infinitysetting hides the search; for long lists, allow search to remain visible
12. Testing
Use these tools before each release:
- axe-core (included in the project at v4.11.0) — automated rule-based checks
- Lighthouse — browser-based audit
- WAVE — visual accessibility overlay
- Keyboard-only navigation — Tab through every interactive element in the component
- Screen reader — VoiceOver (macOS/iOS), NVDA (Windows), or TalkBack (Android)
References
On This Page