Skip to content

🎨 Palette: [UX improvement] Fix file input keyboard accessibility#443

Closed
aafre wants to merge 1 commit into
mainfrom
palette-file-input-a11y-10385395827234519467
Closed

🎨 Palette: [UX improvement] Fix file input keyboard accessibility#443
aafre wants to merge 1 commit into
mainfrom
palette-file-input-a11y-10385395827234519467

Conversation

@aafre
Copy link
Copy Markdown
Owner

@aafre aafre commented Apr 4, 2026

💡 What: Refactored custom file input components to use screen-reader friendly styling instead of display: none and added explicit keyboard focus states to their labels.
🎯 Why: Previously, using Tailwind's hidden class completely removed the file inputs from the DOM's accessibility tree, meaning users navigating with keyboards (tabbing) or screen readers could not interact with the file upload functionality.
📸 Before/After: No visual changes for mouse users, but keyboard users will now see a bright focus ring when tabbing onto the file upload areas.
Accessibility: Replaced hidden with sr-only peer on inputs and added peer-focus-visible:ring-2 to the corresponding labels, ensuring full keyboard navigation and screen reader support for file uploads.


PR created automatically by Jules for task 10385395827234519467 started by @aafre

- Refactored multiple `<input type="file">` elements in `UploadResumeModal`, `TemplateStartModal`, and `IconManager`.
- Replaced inaccessible `hidden` (display: none) CSS classes with screen-reader friendly `sr-only peer` classes.
- Updated accompanying `<label>` elements to use `htmlFor` and apply `peer-focus-visible` styles to ensure proper keyboard tab order focus rings.
- Added a UX journal entry documenting this critical accessibility learning.

Co-authored-by: aafre <8656674+aafre@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request enhances file input accessibility across several components by replacing hidden classes with sr-only peer and adding peer-focus-visible styles to ensure keyboard navigability, while also documenting these patterns in the project's palette. Feedback highlights that disabled pseudo-classes are ineffective on label elements and recommends removing non-functional UI components during processing states to avoid user confusion. Additionally, it is suggested to use unique identifiers or element nesting to prevent potential ID collisions in the DOM.

<label
htmlFor="resume-file-input"
className="btn-primary inline-flex items-center gap-2 px-6 py-3 cursor-pointer disabled:opacity-50"
className="btn-primary inline-flex items-center gap-2 px-6 py-3 cursor-pointer disabled:opacity-50 peer-focus-visible:ring-2 peer-focus-visible:ring-offset-2 peer-focus-visible:ring-accent"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The disabled:opacity-50 utility does not work on <label> elements because they do not support the :disabled pseudo-class. Furthermore, per repository rules, if this call-to-action is non-functional while parsing is true, it should be removed from the UI entirely rather than just styled as disabled to provide better visual feedback and prevent user confusion.

References
  1. Non-functional UI elements should be removed from the DOM rather than just visually disabled to prevent user confusion, particularly for call-to-action elements.

@@ -154,7 +154,15 @@ const IconManager: React.FC<IconManagerProps> = ({
return (
<div className={`icon-manager relative w-12 h-12 ${className}`}>
<label className={`cursor-pointer relative group ${disabled ? 'pointer-events-none opacity-50' : ''}`}>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The label for the icon upload is non-functional while isUploading is true. According to repository rules, non-functional call-to-action elements should be removed from the UI to prevent user confusion, rather than just being visually disabled with CSS. Consider conditionally rendering this element only when an upload is not in progress.

References
  1. Avoid rendering non-functional UI elements. If a call-to-action button cannot perform its intended action, it should be removed to prevent user confusion.

Comment on lines +180 to +188
id="template-start-file-input"
ref={fileInputRef}
type="file"
accept=".pdf,.docx"
className="hidden"
className="sr-only peer"
onChange={handleFileInput}
/>
<label
htmlFor="template-start-file-input"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Using a hardcoded ID (template-start-file-input) in a component can lead to collisions if multiple instances of this modal or component are rendered in the DOM simultaneously. Consider nesting the <input> inside the <label> (as seen in IconManager.tsx) or using the useId hook to generate a unique identifier, which avoids the need for manual ID management.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request improves file input accessibility across several components by replacing hidden inputs with sr-only peer elements and adding focus-visible styling. The review identified a potential issue with redundant event propagation in the TemplateStartModal and suggested using peer-disabled modifiers instead of disabled pseudo-classes on labels to correctly reflect the input state.

Comment on lines 185 to 186
onChange={handleFileInput}
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Since the parent div (line 159) has an onClick handler that programmatically triggers a click on this input, any click event originating from the input itself (whether via the associated label or direct interaction) will bubble up and cause the parent's handler to fire again. This results in the file selection dialog opening twice in many browsers. Adding onClick={(e) => e.stopPropagation()} to the input prevents this redundant event propagation.

                  onChange={handleFileInput}
                  onClick={(e) => e.stopPropagation()}
                />

<label
htmlFor="resume-file-input"
className="btn-primary inline-flex items-center gap-2 px-6 py-3 cursor-pointer disabled:opacity-50"
className="btn-primary inline-flex items-center gap-2 px-6 py-3 cursor-pointer disabled:opacity-50 peer-focus-visible:ring-2 peer-focus-visible:ring-offset-2 peer-focus-visible:ring-accent"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The disabled: utility in Tailwind targets the :disabled pseudo-class, which is not applicable to label elements. To visually reflect the disabled state of the associated input (controlled by the parsing variable), you should use the peer-disabled: modifier instead.

Suggested change
className="btn-primary inline-flex items-center gap-2 px-6 py-3 cursor-pointer disabled:opacity-50 peer-focus-visible:ring-2 peer-focus-visible:ring-offset-2 peer-focus-visible:ring-accent"
className="btn-primary inline-flex items-center gap-2 px-6 py-3 cursor-pointer peer-disabled:opacity-50 peer-focus-visible:ring-2 peer-focus-visible:ring-offset-2 peer-focus-visible:ring-accent"

@aafre
Copy link
Copy Markdown
Owner Author

aafre commented Apr 11, 2026

Closing: superseded by #442 + #434 (UploadResumeModal + IconManager). Cherry-picked into #457.

@aafre aafre closed this Apr 11, 2026
@google-labs-jules
Copy link
Copy Markdown
Contributor

Closing: superseded by #442 + #434 (UploadResumeModal + IconManager). Cherry-picked into #457.

Understood. Acknowledging that this work is superseded and cherry-picked. Stopping work on this task.

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.

1 participant