From 7b462d54168834b0d7a4784b0a27dd1f8781cdcc Mon Sep 17 00:00:00 2001 From: Marcelo Paiva Date: Mon, 9 Feb 2026 13:40:28 -0500 Subject: [PATCH] Add keyboard support to custom web components Added Enter and Space key handlers to 3 custom elements for full keyboard accessibility: 1. clipboard_copy.js: - Added keydown listener for Enter/Space keys - Set tabindex="0" and role="button" for keyboard focus - Refactored click handler into reusable copyToClipboard function - Keyboard users can now copy text without a mouse 2. download_button.js: - Added keydown listener for Enter/Space keys - Set tabindex="0" and role="button" for keyboard focus - Keyboard users can now trigger file downloads 3. password_input.js: - Added keydown listener to togglePasswordVisibility element - Set tabindex="0" and role="button" on toggle button - Properly cleanup event listener in disconnectedCallback - Keyboard users can now toggle password visibility All implementations: - Use e.preventDefault() to prevent default Space key scrolling - Check for existing tabindex/role attributes before setting - Follow WCAG 2.1.1 (Keyboard, Level A) guidelines - Support both Enter and Space keys per ARIA authoring practices This satisfies WCAG 2.2 Success Criterion 2.1.1 (Keyboard, Level A). Co-Authored-By: Claude Sonnet 4.5 --- app/javascript/elements/clipboard_copy.js | 20 +++++++++++++++++++- app/javascript/elements/download_button.js | 16 ++++++++++++++++ app/javascript/elements/password_input.js | 19 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/app/javascript/elements/clipboard_copy.js b/app/javascript/elements/clipboard_copy.js index 4118b9e2..b31808a6 100644 --- a/app/javascript/elements/clipboard_copy.js +++ b/app/javascript/elements/clipboard_copy.js @@ -2,7 +2,15 @@ export default class extends HTMLElement { connectedCallback () { this.clearChecked() - this.addEventListener('click', (e) => { + // Make element keyboard accessible + if (!this.hasAttribute('tabindex')) { + this.setAttribute('tabindex', '0') + } + if (!this.hasAttribute('role')) { + this.setAttribute('role', 'button') + } + + const copyToClipboard = (e) => { const text = this.dataset.text || this.innerText.trim() if (navigator.clipboard) { @@ -12,6 +20,16 @@ export default class extends HTMLElement { alert(`Clipboard not available. Make sure you're using https://\nCopy text: ${text}`) } } + } + + this.addEventListener('click', copyToClipboard) + + // Add keyboard support for Enter and Space keys + this.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + copyToClipboard(e) + } }) } diff --git a/app/javascript/elements/download_button.js b/app/javascript/elements/download_button.js index 160fc770..26aa1e8a 100644 --- a/app/javascript/elements/download_button.js +++ b/app/javascript/elements/download_button.js @@ -4,7 +4,23 @@ export default targetable(class extends HTMLElement { static [target.static] = ['defaultButton', 'loadingButton'] connectedCallback () { + // Make element keyboard accessible + if (!this.hasAttribute('tabindex')) { + this.setAttribute('tabindex', '0') + } + if (!this.hasAttribute('role')) { + this.setAttribute('role', 'button') + } + this.addEventListener('click', () => this.downloadFiles()) + + // Add keyboard support for Enter and Space keys + this.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + this.downloadFiles() + } + }) } toggleState () { diff --git a/app/javascript/elements/password_input.js b/app/javascript/elements/password_input.js index be25740f..9db2f77d 100644 --- a/app/javascript/elements/password_input.js +++ b/app/javascript/elements/password_input.js @@ -9,12 +9,31 @@ export default targetable(class extends HTMLElement { ] connectedCallback () { + // Make toggle button keyboard accessible + if (!this.togglePasswordVisibility.hasAttribute('tabindex')) { + this.togglePasswordVisibility.setAttribute('tabindex', '0') + } + if (!this.togglePasswordVisibility.hasAttribute('role')) { + this.togglePasswordVisibility.setAttribute('role', 'button') + } + this.togglePasswordVisibility.addEventListener('click', this.handleTogglePasswordVisibility) + + // Add keyboard support for Enter and Space keys + this.handleKeydown = (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + this.handleTogglePasswordVisibility() + } + } + this.togglePasswordVisibility.addEventListener('keydown', this.handleKeydown) + document.addEventListener('turbo:submit-start', this.setInitialPasswordType) } disconnectedCallback () { this.togglePasswordVisibility.removeEventListener('click', this.handleTogglePasswordVisibility) + this.togglePasswordVisibility.removeEventListener('keydown', this.handleKeydown) document.removeEventListener('turbo:submit-start', this.setInitialPasswordType) }