Skip to content

Comments

Allow localization of TextField clear button aria-label#3879

Merged
jonrohan merged 5 commits intomainfrom
copilot/localize-clear-button-aria-label
Jan 29, 2026
Merged

Allow localization of TextField clear button aria-label#3879
jonrohan merged 5 commits intomainfrom
copilot/localize-clear-button-aria-label

Conversation

Copy link
Contributor

Copilot AI commented Jan 23, 2026

What are you trying to accomplish?

The TextField clear button had a hardcoded aria-label="Clear", preventing localization for non-English users. Added optional clear_button_label parameter to enable customization while maintaining backward compatibility.

Changes:

  • Added clear_button_label parameter to TextFieldInput
  • Updated template: aria-label="<%= @input.clear_button_label || 'Clear' %>"
  • Added tests for default and custom labels
  • Updated preview with example

Usage:

# Default (backward compatible)
Primer::Alpha::TextField.new(
  name: "search",
  label: "Search",
  show_clear_button: true
)

# Localized
Primer::Alpha::TextField.new(
  name: "search", 
  label: "Recherche",
  show_clear_button: true,
  clear_button_label: "Effacer"
)

# With Rails I18n
clear_button_label: I18n.t("primer.clear_button")

Integration

List the issues that this change affects.

Related to #3803

Risk Assessment

  • Low risk the change is small, highly observable, and easily rolled back.
  • Medium risk changes that are isolated, reduced in scope or could impact few users. The change will not impact library availability.
  • High risk changes are those that could impact customers and SLOs, low or no test coverage, low observability, or slow to rollback.

Rationale: Single optional parameter with fallback to existing value. ERB escaping handles XSS. All tests pass.

What approach did you choose and why?

Followed existing pattern from CloseButton component. Optional parameter with fallback maintains backward compatibility. ERB auto-escaping prevents XSS without additional sanitization.

Alternative considered: Using aria("label", system_arguments) pattern was unnecessary since we need a simple string parameter, not nested hash extraction.

Anything you want to highlight for special attention from reviewers?

N/A

Accessibility

  • Fixes axe scan violation - Enables proper localization of clear button for screen readers in non-English locales.
  • No new axe scan violation - This change does not introduce any new axe scan violations.

Merge checklist

  • Added/updated tests
  • Added/updated documentation
  • Added/updated previews (Lookbook)
  • Tested in Chrome
  • Tested in Firefox
  • Tested in Safari
  • Tested in Edge

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • accounts.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=7448 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/ferrum_user_data_dir_20260124-7420-6r45j4 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,13077906749086937319,7043892377557521415,262144 --enable-features=NetworkService,NetworkServiceInProcess --disable-features=IsolateOrigins,PaintHolding,TranslateUI,site-per-process --variations-seed-version --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless --disable-gpu --hide-scrollbars --mute-audio --enable-automation --disable-web-security --disable-session-crashed-bubble --disable-breakpad --disable-sync --no-first-run --use-mock-keychain --keep-alive-for-test --disable-popup-blocking --disable-extensions --disable-component-extensions-with-REDACTED-pages --disable-hang-monitor --disable-features=site-per-process,IsolateOrigins,TranslateUI --disable-translate --disable-REDACTED-networking (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=7842 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/ferrum_user_data_dir_20260124-7814-5gu04k --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,4133958615491614166,16603117403907789,262144 --enable-features=NetworkService,NetworkServiceInProcess --disable-features=IsolateOrigins,PaintHolding,TranslateUI,site-per-process --variations-seed-version --trace-process-track-uuid=3190708989122997041 (dns block)
  • clients2.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=7448 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/ferrum_user_data_dir_20260124-7420-6r45j4 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,13077906749086937319,7043892377557521415,262144 --enable-features=NetworkService,NetworkServiceInProcess --disable-features=IsolateOrigins,PaintHolding,TranslateUI,site-per-process --variations-seed-version --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless --disable-gpu --hide-scrollbars --mute-audio --enable-automation --disable-web-security --disable-session-crashed-bubble --disable-breakpad --disable-sync --no-first-run --use-mock-keychain --keep-alive-for-test --disable-popup-blocking --disable-extensions --disable-component-extensions-with-REDACTED-pages --disable-hang-monitor --disable-features=site-per-process,IsolateOrigins,TranslateUI --disable-translate --disable-REDACTED-networking (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=7842 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/ferrum_user_data_dir_20260124-7814-5gu04k --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,4133958615491614166,16603117403907789,262144 --enable-features=NetworkService,NetworkServiceInProcess --disable-features=IsolateOrigins,PaintHolding,TranslateUI,site-per-process --variations-seed-version --trace-process-track-uuid=3190708989122997041 (dns block)
  • content-autofill.googleapis.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=7842 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/ferrum_user_data_dir_20260124-7814-5gu04k --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,4133958615491614166,16603117403907789,262144 --enable-features=NetworkService,NetworkServiceInProcess --disable-features=IsolateOrigins,PaintHolding,TranslateUI,site-per-process --variations-seed-version --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless --disable-gpu --hide-scrollbars --mute-audio --enable-automation --disable-web-security --disable-session-crashed-bubble --disable-breakpad --disable-sync --no-first-run --use-mock-keychain --keep-alive-for-test --disable-popup-blocking --disable-extensions --disable-component-extensions-with-REDACTED-pages --disable-hang-monitor --disable-features=site-per-process,IsolateOrigins,TranslateUI --disable-translate --disable-REDACTED-networking (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=8079 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/ferrum_user_data_dir_20260124-8052-dfqazs --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,17947037700202989643,3241429548600794507,262144 --enable-features=NetworkService,NetworkServiceInProcess --disable-features=IsolateOrigins,PaintHolding,TranslateUI,site-per-process --variations-seed-version --trace-process-track-uuid=3190708989122997041 (dns block)
  • optimizationguide-pa.googleapis.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=7842 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/ferrum_user_data_dir_20260124-7814-5gu04k --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,4133958615491614166,16603117403907789,262144 --enable-features=NetworkService,NetworkServiceInProcess --disable-features=IsolateOrigins,PaintHolding,TranslateUI,site-per-process --variations-seed-version --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless --disable-gpu --hide-scrollbars --mute-audio --enable-automation --disable-web-security --disable-session-crashed-bubble --disable-breakpad --disable-sync --no-first-run --use-mock-keychain --keep-alive-for-test --disable-popup-blocking --disable-extensions --disable-component-extensions-with-REDACTED-pages --disable-hang-monitor --disable-features=site-per-process,IsolateOrigins,TranslateUI --disable-translate --disable-REDACTED-networking (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=8079 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/ferrum_user_data_dir_20260124-8052-dfqazs --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,17947037700202989643,3241429548600794507,262144 --enable-features=NetworkService,NetworkServiceInProcess --disable-features=IsolateOrigins,PaintHolding,TranslateUI,site-per-process --variations-seed-version --trace-process-track-uuid=3190708989122997041 (dns block)
  • safebrowsingohttpgateway.googleapis.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=7448 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/ferrum_user_data_dir_20260124-7420-6r45j4 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,13077906749086937319,7043892377557521415,262144 --enable-features=NetworkService,NetworkServiceInProcess --disable-features=IsolateOrigins,PaintHolding,TranslateUI,site-per-process --variations-seed-version --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless --disable-gpu --hide-scrollbars --mute-audio --enable-automation --disable-web-security --disable-session-crashed-bubble --disable-breakpad --disable-sync --no-first-run --use-mock-keychain --keep-alive-for-test --disable-popup-blocking --disable-extensions --disable-component-extensions-with-REDACTED-pages --disable-hang-monitor --disable-features=site-per-process,IsolateOrigins,TranslateUI --disable-translate --disable-REDACTED-networking (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=7842 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/ferrum_user_data_dir_20260124-7814-5gu04k --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,4133958615491614166,16603117403907789,262144 --enable-features=NetworkService,NetworkServiceInProcess --disable-features=IsolateOrigins,PaintHolding,TranslateUI,site-per-process --variations-seed-version --trace-process-track-uuid=3190708989122997041 (dns block)
  • www.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=7448 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/ferrum_user_data_dir_20260124-7420-6r45j4 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,13077906749086937319,7043892377557521415,262144 --enable-features=NetworkService,NetworkServiceInProcess --disable-features=IsolateOrigins,PaintHolding,TranslateUI,site-per-process --variations-seed-version --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless --disable-gpu --hide-scrollbars --mute-audio --enable-automation --disable-web-security --disable-session-crashed-bubble --disable-breakpad --disable-sync --no-first-run --use-mock-keychain --keep-alive-for-test --disable-popup-blocking --disable-extensions --disable-component-extensions-with-REDACTED-pages --disable-hang-monitor --disable-features=site-per-process,IsolateOrigins,TranslateUI --disable-translate --disable-REDACTED-networking (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=7842 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/ferrum_user_data_dir_20260124-7814-5gu04k --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,4133958615491614166,16603117403907789,262144 --enable-features=NetworkService,NetworkServiceInProcess --disable-features=IsolateOrigins,PaintHolding,TranslateUI,site-per-process --variations-seed-version --trace-process-track-uuid=3190708989122997041 (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>Text Field clear button aria-label is not localized/customizable</issue_title>
<issue_description>## To reproduce:

Render a Primer::Alpha::TextField component or the Forms DSL + 'text_field` input.

Actual behavior

It is not possible to specify an aria-label. The aria-label is hard-coded (in app/lib/primer/forms/text_field.html.erb) to the English word "clear".

Expected behavior

It should be possible to localize the aria-label.

Related

Relates to #3803

OpenProject ticket (for internal tracking)

https://community.openproject.org/wp/69915</issue_description>

Comments on the Issue (you are @copilot in this section)

@siddharthkp Hi @myabc, thanks for taking the time to create the issue.

Things are a bit slow at work right now because of the holidays, I'll try to get you a response next week once folks are back at work :)</comment_new>


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@changeset-bot
Copy link

changeset-bot bot commented Jan 23, 2026

🦋 Changeset detected

Latest commit: 5754746

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@primer/view-components Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copilot AI and others added 2 commits January 23, 2026 18:29
Co-authored-by: jonrohan <54012+jonrohan@users.noreply.github.com>
Co-authored-by: jonrohan <54012+jonrohan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix aria-label for clear button in text field Allow localization of TextField clear button aria-label Jan 23, 2026
Copilot AI requested a review from jonrohan January 23, 2026 18:40
@jonrohan jonrohan marked this pull request as ready for review January 23, 2026 22:32
@jonrohan jonrohan requested a review from a team as a code owner January 23, 2026 22:32
Copilot AI review requested due to automatic review settings January 23, 2026 22:32
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support for localizing the TextField clear button’s accessible name by introducing an optional clear_button_label parameter with a default fallback.

Changes:

  • Adds clear_button_label to Primer::Forms::Dsl::TextFieldInput and wires it to the clear button aria-label
  • Updates Lookbook preview + snapshot metadata to demonstrate a custom label
  • Adds tests covering default and custom clear button aria-label values

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
app/lib/primer/forms/dsl/text_field_input.rb Adds clear_button_label parameter to the input model.
app/lib/primer/forms/text_field.html.erb Uses clear_button_label (with default) for the clear button aria-label.
previews/primer/alpha/text_field_preview.rb Adds preview param and a new preview example for a custom label.
test/components/alpha/text_field_test.rb Adds assertions for default and custom aria-label values.
static/previews.json Registers the new preview for snapshots/visual regression.
static/info_arch.json Registers the new preview for info architecture metadata.
.changeset/fifty-cheetahs-explode.md Publishes a minor changeset for the new option.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

<% end %>
<% if @input.show_clear_button? %>
<button type="button" id="<%= @input.clear_button_id %>" class="FormControl-input-trailingAction" aria-label="Clear" data-action="click:primer-text-field#clearContents">
<button type="button" id="<%= @input.clear_button_id %>" class="FormControl-input-trailingAction" aria-label="<%= @input.clear_button_label || 'Clear' %>" data-action="click:primer-text-field#clearContents">
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

clear_button_label will override the default even when it is an empty string, resulting in aria-label="" (no accessible name) and an accessibility regression if a translation returns blank. Consider falling back to the default when the label is blank (e.g., treat blank as nil) or validate/normalize clear_button_label upstream so the rendered aria-label is always non-empty when the clear button is shown.

Suggested change
<button type="button" id="<%= @input.clear_button_id %>" class="FormControl-input-trailingAction" aria-label="<%= @input.clear_button_label || 'Clear' %>" data-action="click:primer-text-field#clearContents">
<button type="button" id="<%= @input.clear_button_id %>" class="FormControl-input-trailingAction" aria-label="<%= @input.clear_button_label.presence || 'Clear' %>" data-action="click:primer-text-field#clearContents">

Copilot uses AI. Check for mistakes.
Comment on lines 23 to +24
@clear_button_id = system_arguments.delete(:clear_button_id) || SecureRandom.uuid
@clear_button_label = system_arguments.delete(:clear_button_label)
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

clear_button_label is accepted without any normalization/validation. If callers pass an empty string (common with missing i18n keys), the template will render an empty aria-label. Consider normalizing blank strings to nil (or raising an ArgumentError when show_clear_button is true and the label is blank) to prevent generating an unlabeled button.

Copilot uses AI. Check for mistakes.
@jonrohan jonrohan enabled auto-merge January 29, 2026 05:44
@jonrohan jonrohan added this pull request to the merge queue Jan 29, 2026
Merged via the queue into main with commit c0db703 Jan 29, 2026
38 checks passed
@jonrohan jonrohan deleted the copilot/localize-clear-button-aria-label branch January 29, 2026 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Text Field clear button aria-label is not localized/customizable

4 participants