-
-
Notifications
You must be signed in to change notification settings - Fork 6
🎨 Palette: Standardize tooltips and accessibility for icon-only buttons #535
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ## 2025-05-23 - [Standardizing Icon Button Accessibility and Tooltips] | ||
| **Learning:** Icon-only buttons without labels or tooltips create "mystery meat navigation" and are inaccessible to screen readers. Standardizing on `Tooltip` for visual labels and `aria-label` for parity ensures a consistent and accessible UX across desktop and mobile. | ||
| **Action:** Always wrap new icon-only buttons in `Tooltip` (desktop) and ensure `aria-label` is present for all icon-only interactions. | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,7 @@ import { PartialRelated } from '@/lib/schema/related' | |||||||||||||||||||||||||||||||||
| import { getSuggestions } from '@/lib/actions/suggest' | ||||||||||||||||||||||||||||||||||
| import { useMapData } from './map/map-data-context' | ||||||||||||||||||||||||||||||||||
| import SuggestionsDropdown from './suggestions-dropdown' | ||||||||||||||||||||||||||||||||||
| import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| interface ChatPanelProps { | ||||||||||||||||||||||||||||||||||
| messages: UIState | ||||||||||||||||||||||||||||||||||
|
|
@@ -169,17 +170,22 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i | |||||||||||||||||||||||||||||||||
| 'fixed bottom-4 left-4 flex justify-start items-center pointer-events-none z-50' | ||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||
| variant={'ghost'} | ||||||||||||||||||||||||||||||||||
| size={'icon'} | ||||||||||||||||||||||||||||||||||
| className="rounded-full transition-all hover:scale-110 pointer-events-auto text-primary" | ||||||||||||||||||||||||||||||||||
| onClick={() => handleClear()} | ||||||||||||||||||||||||||||||||||
| data-testid="new-chat-button" | ||||||||||||||||||||||||||||||||||
| title="New Chat" | ||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||
| <Sprout size={28} className="fill-primary/20" /> | ||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||
| <Tooltip> | ||||||||||||||||||||||||||||||||||
| <TooltipTrigger asChild> | ||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||
| variant={'ghost'} | ||||||||||||||||||||||||||||||||||
| size={'icon'} | ||||||||||||||||||||||||||||||||||
| className="rounded-full transition-all hover:scale-110 pointer-events-auto text-primary" | ||||||||||||||||||||||||||||||||||
| onClick={() => handleClear()} | ||||||||||||||||||||||||||||||||||
| data-testid="new-chat-button" | ||||||||||||||||||||||||||||||||||
| aria-label="New Chat" | ||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||
| <Sprout size={28} className="fill-primary/20" /> | ||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||
| </TooltipTrigger> | ||||||||||||||||||||||||||||||||||
| <TooltipContent>New Chat</TooltipContent> | ||||||||||||||||||||||||||||||||||
| </Tooltip> | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
@@ -216,18 +222,24 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i | |||||||||||||||||||||||||||||||||
| accept="text/plain,image/png,image/jpeg,image/webp" | ||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||
| {!isMobile && ( | ||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||
| variant={'ghost'} | ||||||||||||||||||||||||||||||||||
| size={'icon'} | ||||||||||||||||||||||||||||||||||
| className={cn( | ||||||||||||||||||||||||||||||||||
| 'absolute top-1/2 transform -translate-y-1/2 left-3' | ||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||
| onClick={handleAttachmentClick} | ||||||||||||||||||||||||||||||||||
| data-testid="desktop-attachment-button" | ||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||
| <Paperclip size={isMobile ? 18 : 20} /> | ||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||
| <Tooltip> | ||||||||||||||||||||||||||||||||||
| <TooltipTrigger asChild> | ||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||
| variant={'ghost'} | ||||||||||||||||||||||||||||||||||
| size={'icon'} | ||||||||||||||||||||||||||||||||||
| className={cn( | ||||||||||||||||||||||||||||||||||
| 'absolute top-1/2 transform -translate-y-1/2 left-3' | ||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||
| onClick={handleAttachmentClick} | ||||||||||||||||||||||||||||||||||
| data-testid="desktop-attachment-button" | ||||||||||||||||||||||||||||||||||
| aria-label="Attach File" | ||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||
| <Paperclip size={isMobile ? 18 : 20} /> | ||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||
| </TooltipTrigger> | ||||||||||||||||||||||||||||||||||
| <TooltipContent>Attach File</TooltipContent> | ||||||||||||||||||||||||||||||||||
| </Tooltip> | ||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||
| <Textarea | ||||||||||||||||||||||||||||||||||
| ref={inputRef} | ||||||||||||||||||||||||||||||||||
|
|
@@ -295,9 +307,14 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i | |||||||||||||||||||||||||||||||||
| <span className="text-sm text-muted-foreground truncate max-w-xs"> | ||||||||||||||||||||||||||||||||||
| {selectedFile.name} | ||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||
| <Button variant="ghost" size="icon" onClick={clearAttachment} data-testid="clear-attachment-button"> | ||||||||||||||||||||||||||||||||||
| <X size={16} /> | ||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||
| <Tooltip> | ||||||||||||||||||||||||||||||||||
| <TooltipTrigger asChild> | ||||||||||||||||||||||||||||||||||
| <Button variant="ghost" size="icon" onClick={clearAttachment} data-testid="clear-attachment-button" aria-label="Clear attachment"> | ||||||||||||||||||||||||||||||||||
| <X size={16} /> | ||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||
| </TooltipTrigger> | ||||||||||||||||||||||||||||||||||
| <TooltipContent>Clear attachment</TooltipContent> | ||||||||||||||||||||||||||||||||||
| </Tooltip> | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+310
to
+317
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Capitalization inconsistency:
- <Button ... aria-label="Clear attachment">
+ <Button ... aria-label="Clear Attachment">
...
- <TooltipContent>Clear attachment</TooltipContent>
+ <TooltipContent>Clear Attachment</TooltipContent>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,6 +20,7 @@ import { useUsageToggle } from './usage-toggle-context' | |||||||||||||
| import { useProfileToggle } from './profile-toggle-context' | ||||||||||||||
| import { useHistoryToggle } from './history-toggle-context' | ||||||||||||||
| import { useState, useEffect } from 'react' | ||||||||||||||
| import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' | ||||||||||||||
|
|
||||||||||||||
| export const Header = () => { | ||||||||||||||
| const { toggleCalendar } = useCalendarToggle() | ||||||||||||||
|
|
@@ -52,15 +53,20 @@ export const Header = () => { | |||||||||||||
| </div> | ||||||||||||||
|
|
||||||||||||||
| <div className="absolute left-1 flex items-center"> | ||||||||||||||
| <Button variant="ghost" size="icon" onClick={toggleHistory} data-testid="logo-history-toggle"> | ||||||||||||||
| <Image | ||||||||||||||
| src="/images/logo.svg" | ||||||||||||||
| alt="Logo" | ||||||||||||||
| width={20} | ||||||||||||||
| height={20} | ||||||||||||||
| className="h-5 w-auto" | ||||||||||||||
| /> | ||||||||||||||
| </Button> | ||||||||||||||
| <Tooltip> | ||||||||||||||
| <TooltipTrigger asChild> | ||||||||||||||
| <Button variant="ghost" size="icon" onClick={toggleHistory} data-testid="logo-history-toggle" aria-label="Toggle history"> | ||||||||||||||
| <Image | ||||||||||||||
| src="/images/logo.svg" | ||||||||||||||
| alt="Logo" | ||||||||||||||
| width={20} | ||||||||||||||
| height={20} | ||||||||||||||
| className="h-5 w-auto" | ||||||||||||||
| /> | ||||||||||||||
| </Button> | ||||||||||||||
| </TooltipTrigger> | ||||||||||||||
| <TooltipContent>History</TooltipContent> | ||||||||||||||
| </Tooltip> | ||||||||||||||
|
Comment on lines
+56
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The tooltip content for the logo button is SuggestionAlign the tooltip label and <Button
...
aria-label="History"
>
...
</Button>
<TooltipContent>History</TooltipContent>Or, if the intent is explicitly toggling: aria-label="Toggle history"
...
<TooltipContent>Toggle history</TooltipContent>Reply with "@CharlieHelps yes please" if you'd like me to add a commit that standardizes these labels across the header.
Comment on lines
+56
to
+69
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inconsistent pairing of Two of the three wrapped buttons have mismatched accessible names and tooltip labels:
WCAG 2.5.3 (Label in Name) requires that the visible label text be present as a substring in the accessible name; both current cases technically satisfy this, but the inconsistency makes the pattern harder to follow and maintain. Recommend either aligning tooltip and aria-label verbatim, or adopting a consistent verb-noun form ("Toggle History", "Open Calendar") across both: ♻️ Proposed alignment- <Button ... aria-label="Toggle history">
+ <Button ... aria-label="History">
...
- <TooltipContent>History</TooltipContent>
+ <TooltipContent>History</TooltipContent>- <Button ... aria-label="Open Calendar">
+ <Button ... aria-label="Calendar">
...
- <TooltipContent>Calendar</TooltipContent>
+ <TooltipContent>Calendar</TooltipContent>Also applies to: 80-87 🤖 Prompt for AI Agents |
||||||||||||||
| <h1 className="text-2xl font-poppins font-semibold text-primary"> | ||||||||||||||
| QCX | ||||||||||||||
| </h1> | ||||||||||||||
|
|
@@ -71,15 +77,25 @@ export const Header = () => { | |||||||||||||
|
|
||||||||||||||
| <MapToggle /> | ||||||||||||||
|
|
||||||||||||||
| <Button variant="ghost" size="icon" onClick={toggleCalendar} title="Open Calendar" data-testid="calendar-toggle"> | ||||||||||||||
| <CalendarDays className="h-[1.2rem] w-[1.2rem]" /> | ||||||||||||||
| </Button> | ||||||||||||||
| <Tooltip> | ||||||||||||||
| <TooltipTrigger asChild> | ||||||||||||||
| <Button variant="ghost" size="icon" onClick={toggleCalendar} data-testid="calendar-toggle" aria-label="Open Calendar"> | ||||||||||||||
| <CalendarDays className="h-[1.2rem] w-[1.2rem]" /> | ||||||||||||||
| </Button> | ||||||||||||||
| </TooltipTrigger> | ||||||||||||||
| <TooltipContent>Calendar</TooltipContent> | ||||||||||||||
| </Tooltip> | ||||||||||||||
|
|
||||||||||||||
| <div id="header-search-portal" className="contents" /> | ||||||||||||||
|
|
||||||||||||||
| <Button variant="ghost" size="icon" onClick={handleUsageToggle}> | ||||||||||||||
| <TentTree className="h-[1.2rem] w-[1.2rem]" /> | ||||||||||||||
| </Button> | ||||||||||||||
| <Tooltip> | ||||||||||||||
| <TooltipTrigger asChild> | ||||||||||||||
| <Button variant="ghost" size="icon" onClick={handleUsageToggle} aria-label="Usage"> | ||||||||||||||
| <TentTree className="h-[1.2rem] w-[1.2rem]" /> | ||||||||||||||
| </Button> | ||||||||||||||
| </TooltipTrigger> | ||||||||||||||
| <TooltipContent>Usage</TooltipContent> | ||||||||||||||
| </Tooltip> | ||||||||||||||
|
|
||||||||||||||
| <ModeToggle /> | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -89,7 +105,7 @@ export const Header = () => { | |||||||||||||
| {/* Mobile menu buttons */} | ||||||||||||||
| <div className="flex md:hidden gap-2"> | ||||||||||||||
|
|
||||||||||||||
| <Button variant="ghost" size="icon" onClick={handleUsageToggle}> | ||||||||||||||
| <Button variant="ghost" size="icon" onClick={handleUsageToggle} aria-label="Usage"> | ||||||||||||||
| <TentTree className="h-[1.2rem] w-[1.2rem]" /> | ||||||||||||||
| </Button> | ||||||||||||||
| <ProfileToggle/> | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,26 +37,26 @@ export const MobileIconsBar: React.FC<MobileIconsBarProps> = ({ onAttachmentClic | |
|
|
||
| return ( | ||
| <div className="mobile-icons-bar-content"> | ||
| <Button variant="ghost" size="icon" onClick={handleNewChat} data-testid="mobile-new-chat-button"> | ||
| <Button variant="ghost" size="icon" onClick={handleNewChat} data-testid="mobile-new-chat-button" aria-label="New Chat"> | ||
| <Plus className="h-[1.2rem] w-[1.2rem]" /> | ||
| </Button> | ||
| <ProfileToggle /> | ||
| <MapToggle /> | ||
| <Button variant="ghost" size="icon" onClick={toggleCalendar} title="Open Calendar" data-testid="mobile-calendar-button"> | ||
| <Button variant="ghost" size="icon" onClick={toggleCalendar} aria-label="Open Calendar" data-testid="mobile-calendar-button"> | ||
| <CalendarDays className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" /> | ||
| </Button> | ||
| <Button variant="ghost" size="icon" data-testid="mobile-search-button"> | ||
| <Button variant="ghost" size="icon" data-testid="mobile-search-button" aria-label="Search"> | ||
| <Search className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" /> | ||
| </Button> | ||
|
Comment on lines
+48
to
50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The button has 🤖 Prompt for AI Agents |
||
| <a href="https://buy.stripe.com/14A3cv7K72TR3go14Nasg02" target="_blank" rel="noopener noreferrer"> | ||
| <Button variant="ghost" size="icon"> | ||
| <Button variant="ghost" size="icon" aria-label="Purchase Credits"> | ||
| <TentTree className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" /> | ||
| </Button> | ||
| </a> | ||
|
Comment on lines
51
to
55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The purchase credits button is wrapped in an SuggestionIf <Button asChild variant="ghost" size="icon" aria-label="Purchase credits">
<a href="..." target="_blank" rel="noopener noreferrer">
<TentTree ... />
</a>
</Button>Alternatively, add the
Comment on lines
51
to
55
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The new ♻️ Suggested structure- <a href="https://buy.stripe.com/14A3cv7K72TR3go14Nasg02" target="_blank" rel="noopener noreferrer">
- <Button variant="ghost" size="icon" aria-label="Purchase Credits">
- <TentTree className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" />
- </Button>
- </a>
+ <Button variant="ghost" size="icon" aria-label="Purchase Credits" asChild>
+ <a href="https://buy.stripe.com/14A3cv7K72TR3go14Nasg02" target="_blank" rel="noopener noreferrer">
+ <TentTree className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" />
+ </a>
+ </Button>🤖 Prompt for AI Agents |
||
| <Button variant="ghost" size="icon" onClick={onAttachmentClick} data-testid="mobile-attachment-button"> | ||
| <Button variant="ghost" size="icon" onClick={onAttachmentClick} data-testid="mobile-attachment-button" aria-label="Attach File"> | ||
| <Paperclip className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" /> | ||
| </Button> | ||
| <Button variant="ghost" size="icon" data-testid="mobile-submit-button" onClick={onSubmitClick}> | ||
| <Button variant="ghost" size="icon" data-testid="mobile-submit-button" onClick={onSubmitClick} aria-label="Send Message"> | ||
| <ArrowRight className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" /> | ||
| </Button> | ||
| <History location="header" /> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# First, confirm History component has no aria-label or role annotation
echo "=== Checking for aria-label in History-related files ==="
rg -n "aria-label" components/history.tsx components/history-container.tsx 2>/dev/null || echo "No aria-label found"
echo ""
echo "=== Examining History component structure ==="
cat -n components/history.tsx | head -100Repository: QueueLab/QCX Length of output: 1045 🏁 Script executed: #!/bin/bash
# Check the mobile-icons-bar.tsx file to see other buttons and their aria-labels
echo "=== Mobile Icons Bar - checking icon buttons structure ==="
cat -n components/mobile-icons-bar.tsxRepository: QueueLab/QCX Length of output: 3176 🏁 Script executed: #!/bin/bash
# Check HistoryContainer implementation
echo "=== History Container ==="
cat -n components/history-container.tsxRepository: QueueLab/QCX Length of output: 538 Add The 🤖 Prompt for AI Agents |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Markdown linting violations flagged by
markdownlint-cli2Two valid linter warnings apply:
#) heading; currently uses##.♻️ Proposed fix
📝 Committable suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 1-1: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
🤖 Prompt for AI Agents