Skip to content

Remove unnecessary aria-label defaults from Details component #3534

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/popular-lizards-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/view-components": patch
---

Remove unnecessary aria-label from the Details component
34 changes: 22 additions & 12 deletions app/components/primer/beta/details.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,36 @@ class Details < Primer::Component
system_arguments[:tag] = :summary
system_arguments[:role] = "button"

aria_label_closed = system_arguments[:aria_label_closed] || ARIA_LABEL_CLOSED_DEFAULT
aria_label_open = system_arguments[:aria_label_open] || ARIA_LABEL_OPEN_DEFAULT
aria_label_closed = system_arguments[:aria_label_closed]
aria_label_open = system_arguments[:aria_label_open]

data_attributes = {
target: "details-toggle.summaryTarget",
action: "click:details-toggle#toggle",
}

# Only add aria-label data attributes if explicitly provided
if aria_label_closed || aria_label_open
data_attributes[:aria_label_closed] = aria_label_closed || ARIA_LABEL_CLOSED_DEFAULT
data_attributes[:aria_label_open] = aria_label_open || ARIA_LABEL_OPEN_DEFAULT
end

system_arguments[:data] = merge_data(
system_arguments, {
data: {
target: "details-toggle.summaryTarget",
action: "click:details-toggle#toggle",
aria_label_closed: aria_label_closed,
aria_label_open: aria_label_open,
}
data: data_attributes
}
)

aria_attributes = { expanded: open? }
# Only add aria-label if explicitly provided
if aria_label_closed || aria_label_open
current_label = open? ? (aria_label_open || ARIA_LABEL_OPEN_DEFAULT) : (aria_label_closed || ARIA_LABEL_CLOSED_DEFAULT)
aria_attributes[:label] = current_label
end
Comment on lines +56 to +60
Copy link

Choose a reason for hiding this comment

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

ahh ok I was little confused at first but the test clarified -- so if you provide only one label but not the other, the state without a label will use a default label 👍


system_arguments[:aria] = merge_aria(
system_arguments, {
aria: {
label: open? ? aria_label_open : aria_label_closed,
expanded: open?,
}
aria: aria_attributes
}
)

Expand Down
19 changes: 12 additions & 7 deletions app/components/primer/beta/details_toggle_element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import {controller, target} from '@github/catalyst'
* ensures the <details> and <summary> elements markup is properly accessible by
* updating the aria-label and aria-expanded attributes on click.
*
* aria-label values default to "Expand" and "Collapse". To override those
* values, use the `data-aria-label-open` and `data-aria-label-closed`
* attributes on the summary target.
* aria-label values are only set if provided via the `data-aria-label-open` and
* `data-aria-label-closed` attributes on the summary target. If these attributes
* are not present, no aria-label will be set, allowing screen readers to use
* the visible text content.
*
* @example
* ```html
Expand Down Expand Up @@ -37,12 +38,16 @@ class DetailsToggleElement extends HTMLElement {
toggle() {
const detailsIsOpen = this.detailsTarget.hasAttribute('open')
if (detailsIsOpen) {
const ariaLabelClosed = this.summaryTarget.getAttribute('data-aria-label-closed') || 'Expand'
this.summaryTarget.setAttribute('aria-label', ariaLabelClosed)
const ariaLabelClosed = this.summaryTarget.getAttribute('data-aria-label-closed')
if (ariaLabelClosed) {
this.summaryTarget.setAttribute('aria-label', ariaLabelClosed)
Copy link
Preview

Copilot AI Jun 10, 2025

Choose a reason for hiding this comment

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

Ensure that when no data-aria-label-closed is provided, any existing aria-label attribute is removed to avoid stale labels. For example, call this.summaryTarget.removeAttribute('aria-label') in the branch where ariaLabelClosed is falsy.

Suggested change
this.summaryTarget.setAttribute('aria-label', ariaLabelClosed)
this.summaryTarget.setAttribute('aria-label', ariaLabelClosed)
} else {
this.summaryTarget.removeAttribute('aria-label')

Copilot uses AI. Check for mistakes.

Copy link

Choose a reason for hiding this comment

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

doesn't seem like this would hurt but not sure if it can get in this state 🤔

}
this.summaryTarget.setAttribute('aria-expanded', 'false')
} else {
const ariaLabelOpen = this.summaryTarget.getAttribute('data-aria-label-open') || 'Collapse'
this.summaryTarget.setAttribute('aria-label', ariaLabelOpen)
const ariaLabelOpen = this.summaryTarget.getAttribute('data-aria-label-open')
if (ariaLabelOpen) {
this.summaryTarget.setAttribute('aria-label', ariaLabelOpen)
}
this.summaryTarget.setAttribute('aria-expanded', 'true')
}
}
Expand Down
16 changes: 16 additions & 0 deletions previews/primer/beta/details_preview.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ def open(reset: false, overlay: :default, disabled: false)
component.with_body { "Body" }
end
end

# @label With aria labels
#
# @param overlay [Symbol] select [none, default, dark]
# @param reset [Boolean] toggle
# @param disabled [Boolean] toggle
def with_aria_labels(reset: false, overlay: :default, disabled: false)
render Primer::Beta::Details.new(reset: reset, overlay: overlay, disabled: disabled) do |component|
component.with_summary(aria_label_closed: "Expand details", aria_label_open: "Collapse details") do
"Summary with aria labels"
end
component.with_body do
"Body"
end
end
end
end
end
end
62 changes: 58 additions & 4 deletions test/components/beta/details_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,22 @@ def test_accepts_custom_values_for_summary_aria_label
assert_selector("summary[data-aria-label-closed='Open me']")
end

def test_accepts_partial_aria_label_values
render_inline(Primer::Beta::Details.new) do |component|
component.with_summary(aria_label_closed: "Open me") do
"Summary"
end
component.with_body do
"Body"
end
end

assert_selector("summary")
assert_selector("summary[aria-label='Open me']")
assert_selector("summary[data-aria-label-closed='Open me']")
assert_selector("summary[data-aria-label-open='Collapse']") # Uses default when only one is provided
end

def test_prevents_rendering_without_slots
render_inline(Primer::Beta::Details.new)

Expand Down Expand Up @@ -159,6 +175,44 @@ def test_status
assert_component_state(Primer::Beta::Details, :beta)
end

def test_renders_no_aria_labels_when_not_provided
render_inline(Primer::Beta::Details.new) do |component|
component.with_summary do
"Summary"
end
component.with_body do
"Body"
end
end

# Should not have aria-label attribute
refute_selector("summary[aria-label]")
# Should not have data-aria-label attributes
refute_selector("summary[data-aria-label-closed]")
refute_selector("summary[data-aria-label-open]")
# Should still have aria-expanded
assert_selector("summary[aria-expanded=false]")
Comment on lines +188 to +194
Copy link

Choose a reason for hiding this comment

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

:copilot: is pretty verbose with the comments 😅

end

def test_renders_no_aria_labels_when_not_provided_and_open
render_inline(Primer::Beta::Details.new(open: true)) do |component|
component.with_summary do
"Summary"
end
component.with_body do
"Body"
end
end

# Should not have aria-label attribute
refute_selector("summary[aria-label]")
# Should not have data-aria-label attributes
refute_selector("summary[data-aria-label-closed]")
refute_selector("summary[data-aria-label-open]")
# Should still have aria-expanded
assert_selector("summary[aria-expanded=true]")
end

def test_renders_details_catalyst_element_and_data_attributes
render_inline(Primer::Beta::Details.new) do |component|
component.with_summary do
Expand All @@ -171,11 +225,11 @@ def test_renders_details_catalyst_element_and_data_attributes

assert_selector("details-toggle")
assert_selector("details[data-target='details-toggle.detailsTarget']")
assert_selector("summary[aria-label='Expand']")
refute_selector("summary[aria-label]") # No aria-label when not explicitly provided
assert_selector("summary[aria-expanded=false]")
assert_selector("summary[data-action='click:details-toggle#toggle']")
assert_selector("summary[data-aria-label-closed='Expand']")
assert_selector("summary[data-aria-label-open='Collapse']")
refute_selector("summary[data-aria-label-closed]") # No data attributes when not explicitly provided
refute_selector("summary[data-aria-label-open]") # No data attributes when not explicitly provided
assert_selector("summary[data-target='details-toggle.summaryTarget']")
end

Expand All @@ -190,7 +244,7 @@ def test_renders_correct_aria_attributes_when_details_open_true
end

assert_selector("details[open]")
assert_selector("summary[aria-label='Collapse']")
refute_selector("summary[aria-label]") # No aria-label when not explicitly provided
assert_selector("summary[aria-expanded=true]")
end
end
61 changes: 51 additions & 10 deletions test/system/beta/details_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,45 @@ module Beta
class IntegrationDetailsTest < System::TestCase
include Primer::KeyboardTestHelpers

def test_aria_labels_on_click
def test_no_aria_labels_by_default_on_click
visit_preview(:default)
# Details is closed by default
assert_selector(".details-overlay summary[aria-label='Expand']", text: "Summary")
# Details is closed by default - should have no aria-label
refute_selector(".details-overlay summary[aria-label]")
assert_selector(".details-overlay summary", text: "Summary")

# Open details menu
find(".details-overlay summary").click
assert_selector(".details-overlay summary[aria-label='Collapse']", text: "Summary")
# Should still have no aria-label after opening
refute_selector(".details-overlay summary[aria-label]")
assert_selector(".details-overlay summary", text: "Summary")

# Close details menu
find(".details-overlay summary").click
assert_selector(".details-overlay summary[aria-label='Expand']", text: "Summary")
# Should still have no aria-label after closing
refute_selector(".details-overlay summary[aria-label]")
assert_selector(".details-overlay summary", text: "Summary")
end

def test_aria_labels_keyboard
def test_no_aria_labels_by_default_keyboard
visit_preview(:default)
# Details is closed by default
assert_selector(".details-overlay summary[aria-label='Expand']", text: "Summary")
# Details is closed by default - should have no aria-label
refute_selector(".details-overlay summary[aria-label]")
assert_selector(".details-overlay summary", text: "Summary")

# Open details menu
page.evaluate_script(<<~JS)
document.querySelector('.details-overlay summary').focus()
JS
keyboard.type(:enter)
assert_selector(".details-overlay summary[aria-label='Collapse']", text: "Summary")
# Should still have no aria-label after opening
refute_selector(".details-overlay summary[aria-label]")
assert_selector(".details-overlay summary", text: "Summary")

# Close details menu
keyboard.type(:enter)
assert_selector(".details-overlay summary[aria-label='Expand']", text: "Summary")
# Should still have no aria-label after closing
refute_selector(".details-overlay summary[aria-label]")
assert_selector(".details-overlay summary", text: "Summary")
end

def test_aria_expanded_on_click
Expand Down Expand Up @@ -68,5 +78,36 @@ def test_aria_expanded_keyboard
keyboard.type(:enter)
assert_selector(".details-overlay summary[aria-expanded=false]", text: "Summary")
end

def test_explicit_aria_labels_on_click
visit_preview(:with_aria_labels)
# Details is closed by default - should have explicit aria-label
assert_selector(".details-overlay summary[aria-label='Expand details']", text: "Summary with aria labels")

# Open details menu
find(".details-overlay summary").click
assert_selector(".details-overlay summary[aria-label='Collapse details']", text: "Summary with aria labels")

# Close details menu
find(".details-overlay summary").click
assert_selector(".details-overlay summary[aria-label='Expand details']", text: "Summary with aria labels")
end

def test_explicit_aria_labels_keyboard
visit_preview(:with_aria_labels)
# Details is closed by default - should have explicit aria-label
assert_selector(".details-overlay summary[aria-label='Expand details']", text: "Summary with aria labels")

# Open details menu
page.evaluate_script(<<~JS)
document.querySelector('.details-overlay summary').focus()
JS
keyboard.type(:enter)
assert_selector(".details-overlay summary[aria-label='Collapse details']", text: "Summary with aria labels")

# Close details menu
keyboard.type(:enter)
assert_selector(".details-overlay summary[aria-label='Expand details']", text: "Summary with aria labels")
end
end
end
Loading