Skip to content

Conversation

haoAddsearch
Copy link
Collaborator

@haoAddsearch haoAddsearch commented Oct 15, 2025

Summary by CodeRabbit

  • New Features

    • Extensive accessibility improvements: ARIA roles/labels, live regions, combobox semantics, clearer labels and announcements across search, autocomplete, filters, AI answers, pagination, and results.
    • Live announcements for suggestion counts and search-result updates; show-more and interactive controls expose expanded state.
  • Refactor

    • More reliable active-filter and hierarchical facet interactions with improved ARIA behavior.
  • Style

    • Enhanced focus indicators, arrow controls, list/layout resets, and a screen-reader-only utility class.
  • Documentation

    • README documents a new filter-group label option.

Copy link

coderabbitai bot commented Oct 15, 2025

Walkthrough

Adds broad accessibility and semantic markup changes across UI components, introduces live-region announcement logic in Autocomplete and SearchResults, synchronizes ARIA state in SearchField, refines event handling in ActiveFilters, registers a Handlebars add helper, and updates README docs.

Changes

Cohort / File(s) Summary
Documentation
README.md
Updates Filters settings table to document a new label option and adjusts table header separators.
Active Filters
src/components/activefilters/activefilters.js, src/components/activefilters/activefilters.scss, src/components/activefilters/precompile-templates/activefilters.handlebars
JS: read data-* attributes from e.currentTarget in click handler. SCSS: remove explicit display:block, add nested ul reset and flex layout. Template: switch to semantic ul/li, accessible remove buttons and move "Clear all" into list.
AI Answers
src/components/aianswersresult/aianswersresult.js, src/components/aianswersresult/precompile-templates/aianswersresult.handlebars
JS: keep show-more button aria-expanded in sync when toggling/initializing. Template: add ARIA roles/labels to icons, controls, sentiment buttons, live attributes, and relationships for accessibility.
Autocomplete
src/components/autocomplete/autocomplete.js, src/components/autocomplete/precompile-templates/autocomplete.handlebars
JS: add lastAnnouncedCount, updateLiveRegion(container, suggestions, customFields) and reset in clearRenderedHtml to announce suggestion counts with dedupe. Template: add role="listbox"/role="option", ids, aria-selected, and a status live region.
Filters (checkbox/radio/range/select/tabs/tags)
src/components/filters/precompile-templates/filters_checkboxgroup.handlebars, .../filters_radiogroup.handlebars, .../filters_range.handlebars, .../filters_selectlist.handlebars, .../filters_tabs.handlebars, .../filters_tags.handlebars
Add ARIA semantics: role (group/radiogroup/tablist), aria-label/aria-labelledby, visible/hidden labels, aria-describedby help text, improved clear-button labeling, and per-control ARIA (aria-selected/aria-pressed).
Hierarchical Facets
src/components/hierarchicalfacets/hierarchicalfacets.js, src/components/hierarchicalfacets/hierarchicalfacets.scss, src/components/hierarchicalfacets/precompile-templates/hierarchical_facets.handlebars, .../subHierarchicalFacetsTemplate.handlebars
JS: toggle via facet wrapper and update aria-expanded/aria-label. SCSS: replace pseudo-arrow with button+rotated span and add focus styles. Templates: add role="tree"/treeitem, aria-level, aria-owns, conditional expand/collapse button, and pass level to partials.
Pagination
src/components/pagination/precompile-templates/pagination.handlebars
Add role="navigation" and aria-label="Search results pagination" on pagination root container.
Range Facets — Slider
src/components/rangefacets/precompile-templates/sliderrangefacets.handlebars
Add role="group" and labeling, screen-reader label, a role="status" live region for displayed range, explicit sr-only min/max labels, and structured range display.
Search Field
src/components/searchfield/precompile-templates/searchfield.handlebars, src/components/searchfield/searchfield.js
Template: input gains role="combobox", aria-autocomplete, aria-expanded, and aria-controls. JS: add updateAriaAttributes and updateAriaAttributesForBoundFields, initialize ARIA on bound fields, and call updates after autocomplete changes.
Search Results
src/components/searchresults/precompile-templates/no_results.handlebars, .../numberOfResultsTemplate.handlebars, .../searchresults.handlebars, src/components/searchresults/searchresults.js, src/components/searchresults/searchresults.scss
Templates: add live-region status elements, wrap results in role="region" with label, convert hits to ol/li with roles. JS: add lastAnnouncedResults and updateLiveRegion(container, data) and call after render (dedup). SCSS: reset ol styles.
Sort By
src/components/sortby/precompile-templates/sortby_radiogroup.handlebars, .../sortby_select.handlebars, src/components/sortby/sortby.scss
Templates: radiogroup role="radiogroup" and aria-label; radio inputs now use concrete value="{{sortBy}}-{{order}}"; select gets aria-label. SCSS: add focus outlines and improved control styling.
Utilities & Global Styles
src/util/handlebars.js, src/index.scss
Handlebars: register new add helper (sums two args). Styles: add .addsearch-sr-only utility class for screen-reader-only content.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Input as SearchField (combobox)
  participant AC as Autocomplete
  participant LR as LiveRegion (status)

  User->>Input: Type / navigate
  Input->>AC: onAutocompleteUpdate(state)
  AC->>AC: render suggestions
  AC->>LR: updateLiveRegion(totalSuggestions)
  Input->>Input: updateAriaAttributes(state)
  Input->>Input: updateAriaAttributesForBoundFields(state)
Loading
sequenceDiagram
  autonumber
  participant Renderer as SearchResults
  participant LR as LiveRegion (status)

  Renderer->>Renderer: render(container, data)
  Renderer->>LR: updateLiveRegion(totalHits, keyword) (dedupe via lastAnnouncedResults)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly summarizes the primary change—enhancing components for WCAG 2.2 AA compliance—and directly relates to the extensive accessibility updates across the codebase.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sc-12778/improve-components-to-be-wcag-2.2-aa-compliant

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/aianswersresult/precompile-templates/aianswersresult.handlebars (1)

146-160: Remove redundant new tab announcement.

The source links currently include both an aria-label with “(opens in new tab)” and a sr-only <span> with the same text. Remove one to avoid double announcements. Prefer Option 2 (keep the sr-only span):

- <a href='{{url}}' class='source' target="_blank" aria-label="{{title}} (opens in new tab)">
+ <a href='{{url}}' class='source' target="_blank">
    {{title}}
    <span class="addsearch-sr-only"> (opens in new tab)</span>

Show-more button’s aria-expanded is toggled correctly in the JavaScript.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ec28d2 and bfa77b7.

📒 Files selected for processing (32)
  • README.md (1 hunks)
  • src/components/activefilters/activefilters.js (1 hunks)
  • src/components/activefilters/activefilters.scss (1 hunks)
  • src/components/activefilters/precompile-templates/activefilters.handlebars (1 hunks)
  • src/components/aianswersresult/aianswersresult.js (4 hunks)
  • src/components/aianswersresult/precompile-templates/aianswersresult.handlebars (9 hunks)
  • src/components/autocomplete/autocomplete.js (4 hunks)
  • src/components/autocomplete/precompile-templates/autocomplete.handlebars (1 hunks)
  • src/components/filters/precompile-templates/filters_checkboxgroup.handlebars (1 hunks)
  • src/components/filters/precompile-templates/filters_radiogroup.handlebars (1 hunks)
  • src/components/filters/precompile-templates/filters_range.handlebars (1 hunks)
  • src/components/filters/precompile-templates/filters_selectlist.handlebars (1 hunks)
  • src/components/filters/precompile-templates/filters_tabs.handlebars (1 hunks)
  • src/components/filters/precompile-templates/filters_tags.handlebars (1 hunks)
  • src/components/hierarchicalfacets/hierarchicalfacets.js (1 hunks)
  • src/components/hierarchicalfacets/hierarchicalfacets.scss (3 hunks)
  • src/components/hierarchicalfacets/precompile-templates/hierarchical_facets.handlebars (1 hunks)
  • src/components/hierarchicalfacets/precompile-templates/subHierarchicalFacetsTemplate.handlebars (1 hunks)
  • src/components/pagination/precompile-templates/pagination.handlebars (2 hunks)
  • src/components/rangefacets/precompile-templates/sliderrangefacets.handlebars (1 hunks)
  • src/components/searchfield/precompile-templates/searchfield.handlebars (1 hunks)
  • src/components/searchfield/searchfield.js (3 hunks)
  • src/components/searchresults/precompile-templates/no_results.handlebars (1 hunks)
  • src/components/searchresults/precompile-templates/numberOfResultsTemplate.handlebars (1 hunks)
  • src/components/searchresults/precompile-templates/searchresults.handlebars (1 hunks)
  • src/components/searchresults/searchresults.js (3 hunks)
  • src/components/searchresults/searchresults.scss (1 hunks)
  • src/components/sortby/precompile-templates/sortby_radiogroup.handlebars (1 hunks)
  • src/components/sortby/precompile-templates/sortby_select.handlebars (1 hunks)
  • src/components/sortby/sortby.scss (1 hunks)
  • src/index.scss (1 hunks)
  • src/util/handlebars.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/searchresults/searchresults.js (3)
src/actions/search.js (1)
  • hits (99-99)
src/util/history.js (1)
  • keyword (237-237)
src/reducers/keyword.js (1)
  • keyword (11-29)
🔇 Additional comments (55)
src/util/handlebars.js (1)

76-78: Add type validation to add helper and confirm its usage
I didn’t find any {{add calls in .hbs/.handlebars templates—please verify where this helper is used and, if it’s used for numeric output, coerce inputs to numbers and return 0 on NaN:

 registerHelper('add', (a, b) => {
-  return a + b;
+  const numA = Number(a);
+  const numB = Number(b);
+  if (isNaN(numA) || isNaN(numB)) return 0;
+  return numA + numB;
 });
src/components/pagination/precompile-templates/pagination.handlebars (1)

1-13: Good semantic HTML and accessibility improvements!

The change from <div> to <nav> is semantically correct for pagination controls, and the aria-label="Search results pagination" appropriately distinguishes this navigation landmark from others on the page. The existing ARIA attributes on buttons (lines 3, 6, 11) and the aria-current="true" for the active page (line 6) are well-implemented.

src/components/activefilters/activefilters.scss (1)

9-19: LGTM! Proper styling for semantic list structure.

The list style resets and flex properties correctly support the semantic <ul> structure introduced in the template while maintaining the existing layout behavior.

src/components/activefilters/activefilters.js (1)

164-173: Excellent fix for safer event handling.

Using e.currentTarget instead of e.target is the correct approach here. Since the template includes nested <span> elements within buttons, e.target could reference the child span when clicked, potentially missing the required data attributes. e.currentTarget reliably references the button element that owns the event listener.

src/components/activefilters/precompile-templates/activefilters.handlebars (4)

7-16: Excellent ARIA implementation for accessible remove buttons.

The combination of aria-label on the button and aria-hidden="true" on the decorative × symbol follows WCAG best practices. The label provides clear context ("Remove [filter name] filter") while hiding the visual-only symbol from screen readers.


22-22: Good accessible label for the clear all button.

The aria-label="Clear all active filters" provides clear context for screen reader users about the button's purpose.


20-24: gt helper registration confirmed The gt helper is defined in src/util/handlebars.js via registerHelper('gt', …), so this template usage is valid.


2-2: .addsearch-sr-only is correctly defined with all required screen-reader-only styles in src/index.scss (lines 16–24); no changes needed.

src/components/filters/precompile-templates/filters_selectlist.handlebars (1)

2-2: LGTM! Accessibility improvement.

The aria-label provides context for screen reader users when the select doesn't have a visible label element.

README.md (1)

511-514: LGTM! Documentation is clear.

The table formatting adjustment and the new label option documentation align with the accessibility improvements being made to filter groups.

src/components/filters/precompile-templates/filters_tabs.handlebars (1)

2-4: LGTM! Correct ARIA tabs implementation.

The tablist/tab roles with aria-selected properly implement the ARIA tabs pattern, making the filter tabs accessible to assistive technologies.

src/components/searchfield/searchfield.js (3)

143-171: LGTM! Consistent ARIA management for bound fields.

The method correctly applies the same ARIA attribute updates to all bound input fields, ensuring consistent combobox behavior across multiple search fields.


296-300: LGTM! Proper ARIA initialization.

The combobox ARIA attributes are correctly initialized on bound fields, establishing the foundation for the combobox pattern before dynamic updates occur.


99-122: Good ARIA combobox implementation. ID patterns verified. The autocomplete templates include matching IDs (addsearch-suggestion-{{@index}}, addsearch-customfield-{{@index}}).

src/components/searchfield/precompile-templates/searchfield.handlebars (1)

4-13: LGTM! Complete combobox ARIA attributes.

The input element now has all required ARIA attributes for the combobox pattern, matching the JavaScript implementation that manages these attributes dynamically.

src/index.scss (1)

15-26: LGTM! Standard screen-reader-only utility.

The .addsearch-sr-only class correctly implements the standard pattern for visually hiding content while keeping it accessible to screen readers.

src/components/sortby/precompile-templates/sortby_select.handlebars (1)

2-2: LGTM! Clear and descriptive label.

The aria-label provides clear context about the select's purpose for screen reader users.

src/components/searchresults/searchresults.scss (1)

5-10: Approve semantic <ol> reset: Confirmed <ol> usage in searchresults template; list styling reset is appropriate.

src/components/hierarchicalfacets/precompile-templates/hierarchical_facets.handlebars (1)

1-3: LGTM! Correct tree role and level tracking.

The accessibility implementation is sound:

  • role="tree" is the appropriate ARIA role for hierarchical navigation
  • aria-label provides context for the hierarchical filter structure
  • Passing level=1 ensures proper aria-level tracking for nested tree items in the child template
src/components/searchresults/precompile-templates/numberOfResultsTemplate.handlebars (1)

1-4: LGTM! Proper live region for result count updates.

The live region configuration is appropriate:

  • role="status" correctly identifies this as a status message
  • aria-live="polite" ensures screen readers announce updates without interrupting the user
src/components/searchresults/precompile-templates/no_results.handlebars (1)

2-2: LGTM! Properly configured screen-reader-only live region.

The live region implementation follows best practices:

  • role="status" and aria-live="polite" enable non-disruptive announcements
  • aria-atomic="true" ensures the entire announcement is read when updated
  • addsearch-sr-only class hides the region visually while keeping it accessible to screen readers

This pattern allows JavaScript to dynamically announce status updates without visual clutter.

src/components/sortby/precompile-templates/sortby_radiogroup.handlebars (1)

1-7: LGTM! Proper radiogroup semantics and explicit values.

The accessibility enhancements are well-implemented:

  • role="radiogroup" correctly identifies the group of related radio buttons
  • aria-label provides clear context for the sort options
  • Explicit value attribute improves form semantics and programmatic access
src/components/aianswersresult/aianswersresult.js (4)

95-95: LGTM! Correct aria-expanded management in initial expanded state.

The aria-expanded="true" attribute correctly reflects the expanded state of the show-more button during initial render when the answer is expanded.


105-105: LGTM! Correct aria-expanded management for overflow content.

The aria-expanded="false" attribute correctly reflects the collapsed state when content overflows and the show-more button is displayed.


122-122: LGTM! Proper aria-expanded update when collapsing.

The attribute is correctly updated to "false" when transitioning from expanded to collapsed state.


134-134: LGTM—aria-expanded updates correctly and the show-more button already includes aria-controls="ai-answer-content" to link it to the content.

src/components/filters/precompile-templates/filters_checkboxgroup.handlebars (1)

1-2: LGTM! Proper group semantics with conditional labeling.

The accessibility implementation is correct:

  • role="group" appropriately groups related checkboxes
  • aria-labelledby correctly references the visible label when present
  • ID pattern checkbox-label-{{containerId}} ensures uniqueness
  • Conditional rendering ({{#if label}}) maintains consistency between the label element and ARIA reference
src/components/filters/precompile-templates/filters_radiogroup.handlebars (1)

1-2: LGTM! Proper radiogroup semantics with conditional labeling.

The accessibility implementation follows best practices:

  • role="radiogroup" correctly identifies the group of related radio buttons
  • aria-labelledby appropriately references the visible label when present
  • ID pattern radio-label-{{containerId}} ensures uniqueness across multiple instances
  • Conditional rendering maintains consistency between the label element and ARIA reference
src/components/sortby/sortby.scss (2)

22-37: LGTM: Consistent focus indicators.

The select element focus indicators match the input styling, providing a consistent accessibility experience. The same contrast verification from the previous comment applies here.


14-19: Focus indicator contrast is sufficient
#0066cc yields contrast ratios of 5.57:1 on white and 4.14:1 on #dedede, exceeding the WCAG 2.2 AA minimum of 3:1.

src/components/rangefacets/precompile-templates/sliderrangefacets.handlebars (1)

1-2: LGTM: Proper ARIA labeling.

The group role with aria-labelledby provides appropriate semantic structure and labeling for the slider control. The screen-reader-only label ensures accessibility without visual clutter.

src/components/hierarchicalfacets/hierarchicalfacets.js (1)

150-162: LGTM: Correct ARIA state synchronization.

The ARIA state management is properly implemented:

  • aria-expanded correctly reflects the current state after the toggle
  • aria-label appropriately describes the next action (e.g., "Collapse" when expanded, "Expand" when collapsed)
  • The DOM manipulation is clean with the facetWrap variable

This follows WCAG best practices for accessible expandable/collapsible controls.

src/components/searchresults/searchresults.js (1)

106-107: LGTM: Proper live region integration.

The updateLiveRegion call is correctly placed after rendering the HTML, ensuring the live region element exists in the DOM before updating it. The deduplication logic prevents unnecessary announcements on re-renders.

src/components/hierarchicalfacets/precompile-templates/subHierarchicalFacetsTemplate.handlebars (3)

1-1: LGTM: Proper tree semantics.

The treeitem role with aria-level, aria-expanded, and aria-owns correctly implements ARIA tree semantics for hierarchical navigation. The conditional aria-owns ensures the attribute is only present when children exist.


17-19: LGTM: correct ARIA group semantics and recursion
role="group" usage and level increment via the add helper are correct; helper is registered in src/util/handlebars.js.


4-10: Verify keyboard accessibility of expand/collapse control. The <button> uses tabindex="-1", removing it from the tab order—ensure keyboard users can still trigger expand/collapse (e.g., via arrow-key navigation on the treeitem or another focusable control) for WCAG 2.1.1 compliance.

src/components/autocomplete/precompile-templates/autocomplete.handlebars (2)

3-9: LGTM: Correct listbox ARIA implementation.

The suggestions list correctly implements the ARIA listbox pattern with:

  • role="listbox" on the container
  • role="option" with unique IDs on each item
  • aria-selected reflecting the active state
  • Semantic alignment between class="active" and aria-selected="true"

11-20: Live region updates confirmed: The updateLiveRegion method in src/components/autocomplete/autocomplete.js correctly sets and clears the [role="status"] element’s textContent with suggestion counts and navigation instructions.

src/components/searchresults/precompile-templates/searchresults.handlebars (3)

3-4: LGTM: Proper landmark and live region.

The search results region is correctly marked with role="region" and aria-label, creating an accessible landmark. The live region setup aligns with the updateLiveRegion method in searchresults.js that was reviewed earlier.


9-11: Explicit list roles for robustness.

The role="list" on <ol> and role="listitem" on <li> are technically redundant (semantic HTML provides these implicitly), but they ensure list semantics are preserved even if CSS removes list styling (e.g., list-style: none). This is a defensive accessibility practice.


12-24: LGTM: Semantic search result structure.

Each search result is properly structured with:

  • Semantic heading (<h3>) for the title
  • Preserved analytics tracking (data-analytics-click)
  • Appropriate content organization within the list item
  • Conditional category display only when multiple categories exist

The transition from <div> to <li> elements maintains all functionality while improving semantic markup and accessibility.

src/components/aianswersresult/precompile-templates/aianswersresult.handlebars (4)

5-18: LGTM: Accessible SVG icon.

The AI icon SVG is properly made accessible with role='img' and a descriptive aria-label, ensuring screen readers announce it meaningfully.


49-66: LGTM: Accessible loading indicator.

The loading spinner SVG is properly configured with role='img' and a descriptive aria-label, informing screen reader users that content is loading.


70-91: LGTM: Accessible copy button.

The answer container has an id for ARIA relationships, and the copy button is properly labeled with aria-label. The icon is correctly marked aria-hidden='true'.


92-143: LGTM: Accessible toggle buttons with state.

The thumbs up/down buttons correctly implement toggle button semantics with:

  • aria-label describing the action
  • aria-pressed reflecting the current state
  • Icons marked aria-hidden='true'

The state management properly reflects whether positive or negative sentiment is selected.

src/components/autocomplete/autocomplete.js (4)

31-31: LGTM!

The initialization of lastAnnouncedCount is appropriate for tracking the announcement state.


140-140: LGTM!

Resetting lastAnnouncedCount ensures the next autocomplete appearance will properly announce the suggestion count.


175-176: LGTM!

The placement of updateLiveRegion after the HTML is rendered ensures the live region element exists in the DOM before attempting to update it.


231-254: Excellent accessibility implementation!

The updateLiveRegion method properly:

  • Guards against missing live region element
  • Handles null/undefined for both suggestions and customFields
  • Prevents redundant announcements by tracking lastAnnouncedCount
  • Provides clear, actionable screen reader guidance with proper pluralization
src/components/hierarchicalfacets/hierarchicalfacets.scss (3)

72-76: Verify focus outline color contrast.

Ensure the focus outline color #0066cc meets WCAG 2.2 AA requirements (3:1 contrast ratio) against all background colors where this component appears, including default and theme variations.

Consider generating a script to verify the contrast ratio:

#!/usr/bin/env python3
# Verify WCAG contrast ratio for focus outline color

def relative_luminance(rgb):
    """Calculate relative luminance of RGB color"""
    r, g, b = [x / 255.0 for x in rgb]
    r = r / 12.92 if r <= 0.03928 else ((r + 0.055) / 1.055) ** 2.4
    g = g / 12.92 if g <= 0.03928 else ((g + 0.055) / 1.055) ** 2.4
    b = b / 12.92 if b <= 0.03928 else ((b + 0.055) / 1.055) ** 2.4
    return 0.2126 * r + 0.7152 * g + 0.0722 * b

def contrast_ratio(rgb1, rgb2):
    """Calculate contrast ratio between two colors"""
    l1 = relative_luminance(rgb1)
    l2 = relative_luminance(rgb2)
    lighter = max(l1, l2)
    darker = min(l1, l2)
    return (lighter + 0.05) / (darker + 0.05)

# Focus outline color #0066cc
focus_color = (0, 102, 204)

# Common background colors to test against
backgrounds = {
    'white': (255, 255, 255),
    'light-gray': (220, 220, 220),
    'medium-gray': (128, 128, 128),
}

print("WCAG 2.2 AA requires 3:1 contrast for focus indicators\n")
for name, bg_color in backgrounds.items():
    ratio = contrast_ratio(focus_color, bg_color)
    status = "✓ PASS" if ratio >= 3.0 else "✗ FAIL"
    print(f"{status} {name:15s} - Contrast ratio: {ratio:.2f}:1")

78-82: LGTM!

The rotation transform and transition provide smooth, accessible visual feedback for the expansion state.


102-104: LGTM!

The rotation reset to 0 degrees provides clear visual feedback for the collapsed state.

src/components/filters/precompile-templates/filters_range.handlebars (3)

1-2: LGTM!

The semantic grouping with role="group" and conditional aria-labelledby provides proper accessibility structure for the range filter.


3-7: LGTM!

Excellent use of:

  • Screen-reader-only labels for explicit input identification
  • aria-describedby linking both inputs to contextual help
  • aria-hidden="true" on the visual separator to prevent screen reader confusion

9-9: LGTM!

The screen-reader-only help text with matching ID properly supports the aria-describedby associations, providing contextual guidance to assistive technology users.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/components/pagination/precompile-templates/pagination.handlebars (1)

6-6: Use aria-current="page" for better semantic clarity.

The current implementation uses aria-current="true", which is technically valid. However, aria-current="page" is more semantically appropriate for pagination contexts and provides clearer meaning to assistive technologies about the type of current item.

Apply this diff:

-    <button data-page="{{this}}" aria-label="Go to results page {{this}}" {{#equals ../currentPage this}}data-active="true" aria-current="true"{{/equals}}>
+    <button data-page="{{this}}" aria-label="Go to results page {{this}}" {{#equals ../currentPage this}}data-active="true" aria-current="page"{{/equals}}>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bfa77b7 and 14e4a39.

📒 Files selected for processing (6)
  • src/components/aianswersresult/precompile-templates/aianswersresult.handlebars (9 hunks)
  • src/components/filters/precompile-templates/filters_range.handlebars (1 hunks)
  • src/components/filters/precompile-templates/filters_tags.handlebars (1 hunks)
  • src/components/pagination/precompile-templates/pagination.handlebars (2 hunks)
  • src/components/rangefacets/precompile-templates/sliderrangefacets.handlebars (1 hunks)
  • src/components/searchresults/searchresults.js (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/aianswersresult/precompile-templates/aianswersresult.handlebars
  • src/components/filters/precompile-templates/filters_tags.handlebars
  • src/components/rangefacets/precompile-templates/sliderrangefacets.handlebars
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: haoAddsearch
PR: AddSearch/search-ui#155
File: src/components/pagination/precompile-templates/pagination.handlebars:1-1
Timestamp: 2025-10-16T13:30:52.737Z
Learning: In the AddSearch Search UI components, prefer using `<div>` with explicit ARIA roles (e.g., `<div role="navigation">`) instead of semantic HTML elements (e.g., `<nav>`) when there's a concern about customer CSS affecting component styling. This maintains accessibility while avoiding potential conflicts with customer stylesheets.
📚 Learning: 2025-10-16T13:30:52.737Z
Learnt from: haoAddsearch
PR: AddSearch/search-ui#155
File: src/components/pagination/precompile-templates/pagination.handlebars:1-1
Timestamp: 2025-10-16T13:30:52.737Z
Learning: In the AddSearch Search UI components, prefer using `<div>` with explicit ARIA roles (e.g., `<div role="navigation">`) instead of semantic HTML elements (e.g., `<nav>`) when there's a concern about customer CSS affecting component styling. This maintains accessibility while avoiding potential conflicts with customer stylesheets.

Applied to files:

  • src/components/pagination/precompile-templates/pagination.handlebars
🧬 Code graph analysis (1)
src/components/searchresults/searchresults.js (2)
src/actions/search.js (1)
  • hits (99-99)
src/util/history.js (1)
  • keyword (237-237)
🔇 Additional comments (7)
src/components/pagination/precompile-templates/pagination.handlebars (1)

1-1: LGTM! Proper use of ARIA role per team convention.

The use of <div role="navigation"> with aria-label="Search results pagination" correctly provides navigation semantics for assistive technologies while avoiding potential CSS conflicts with customer stylesheets.

Based on learnings.

src/components/searchresults/searchresults.js (3)

18-18: LGTM!

Initializing lastAnnouncedResults for deduplication tracking is appropriate and follows the pattern used in autocomplete.js.


42-71: Well-implemented live region updates with proper deduplication.

The method correctly addresses the previous concern about undefined keywords (line 50 uses data.keyword || ''). The logic covers all scenarios appropriately:

  • No results with a keyword → clear announcement
  • Results found → count with optional keyword context
  • Initial/empty state → silent

The composite key deduplication prevents announcement spam, and the early returns optimize performance.


106-107: LGTM!

Correct placement after HTML injection ensures the live region element exists before updating it.

src/components/filters/precompile-templates/filters_range.handlebars (3)

1-2: LGTM: Proper ARIA grouping and labeling.

The role="group" with conditional aria-labelledby correctly associates the range inputs with their label. Using {{field}} in the ID ensures uniqueness when multiple range filters are present.


3-7: LGTM: Excellent accessibility implementation.

The screen-reader-only labels ("Minimum value" and "Maximum value") provide clear context, while aria-describedby associates the help text with both inputs. Hiding the visual separator from screen readers prevents unnecessary announcements.


13-13: LGTM: Clear help text for assistive technology.

The help text is properly associated with both inputs via aria-describedby and uses the screen-reader-only class to remain hidden from sighted users while providing valuable context to screen reader users.

Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/searchresults/searchresults.js (1)

100-109: Bug: live region not updated when HTML is unchanged (early return).
If totalHits/keyword change but template HTML stays identical, announcement is skipped. Call updateLiveRegion even when DOM doesn’t re-render.

Apply this diff:

-    if (this.renderedHtml === html) {
-      return;
-    }
-    const container = document.getElementById(this.conf.containerId);
-    container.innerHTML = html;
-    this.renderedHtml = html;
-
-    // Update live region for screen reader announcements
-    this.updateLiveRegion(container, data);
+    const container = document.getElementById(this.conf.containerId);
+    if (this.renderedHtml === html) {
+      // Still announce changes even if markup didn't change
+      this.updateLiveRegion(container, data);
+      return;
+    }
+    container.innerHTML = html;
+    this.renderedHtml = html;
+    // Update live region for screen reader announcements
+    this.updateLiveRegion(container, data);
♻️ Duplicate comments (1)
src/components/searchresults/searchresults.js (1)

50-50: Resolved: guard undefined keyword.
Using empty-string default prevents “undefined” announcements and stabilizes the dedupe key.

🧹 Nitpick comments (2)
src/components/searchresults/searchresults.js (2)

42-47: Target the intended live region explicitly.
querySelector('[role="status"]') may pick the wrong node if templates include multiple status regions. Prefer a dedicated selector (e.g., [data-live-region="results"]) and fall back to role.

Example:

-    const liveRegion = container.querySelector('[role="status"]');
+    const liveRegion =
+      container.querySelector('[data-live-region="results"]') ||
+      container.querySelector('[role="status"]');

Please verify the container renders only one announcing live region to avoid double announcements.


62-71: Optional: externalize announcement strings for i18n.
Hard-coded English (“No search results…”, “result(s)”) hinders localization/plurals. Consider conf-provided strings or a translation utility.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 14e4a39 and 3109afc.

📒 Files selected for processing (2)
  • src/components/searchfield/searchfield.js (3 hunks)
  • src/components/searchresults/searchresults.js (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/searchfield/searchfield.js
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/searchresults/searchresults.js (2)
src/actions/search.js (1)
  • hits (99-99)
src/reducers/keyword.js (1)
  • keyword (11-29)
🔇 Additional comments (1)
src/components/searchresults/searchresults.js (1)

18-18: Good: track last announcement to prevent duplicate reads.
Helps avoid noisy screen reader updates.

@@ -1,16 +1,22 @@
<li>
<li role="treeitem" aria-level="{{level}}" aria-expanded="{{#unless (validateOpenState value)}}true{{else}}false{{/unless}}" {{#if children}}aria-owns="children-{{value}}"{{/if}}>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation uses role="tree" but mixes it with checkboxes, which violates ARIA patterns. According to https://www.w3.org/WAI/ARIA/apg/patterns/treeview/, trees are for hierarchical navigation, not selection.

Option A: Use role="group" instead of "tree"
Option B: If tree navigation is desired, remove checkboxes and use aria-selected

<div class="adds-facet-container">
<span class="addsearch-facet-group-expansion-arrow {{#unless children}}adds-visibility-hidden{{/unless}}"></span>
{{#if children}}
<button type="button" class="addsearch-facet-group-expansion-arrow" aria-label="{{#unless (validateOpenState value)}}Collapse{{else}}Expand{{/unless}} {{displayValue}}" aria-controls="children-{{value}}" tabindex="-1">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: tabindex="-1" removes the button from keyboard navigation, making it inaccessible to keyboard users.

Problem:

  • Keyboard-only users cannot expand/collapse hierarchical facets
  • Violates WCAG 2.1.1 (Keyboard) - Level A

const container = e.target.getAttribute('data-container');
const confFields = e.target.getAttribute('data-conf-fields')
? e.target.getAttribute('data-conf-fields').split(',')
const targetButton = e.currentTarget;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: While the change from e.target to e.currentTarget is correct for button clicks, the handler is directly attached to buttons. If users press Space/Enter on the button, this should work, but there's no verification in the code review of how event listeners are attached.

Recommendation: Verify in activefilters.js that event listeners are attached like:
button.addEventListener('click', this.handleFilterClick);

And ensure keyboard events (Space/Enter) are also handled, though native buttons should handle this automatically.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants