Files
goodgo-platform/docs/audits/ACCESSIBILITY_FIXES_REPORT.md
Ho Ngoc Hai 59272e9321 chore(docs): consolidate 22 audit files from root into docs/audits/
Root directory had accumulated audit/exploration markdown files cluttering
the project root. Moved all audit-related files to docs/audits/ with a
README.md index, and updated cross-references in K6_LOAD_TESTING_GUIDE.md
and README_FRONTEND_DOCS.md.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-10 23:16:00 +07:00

8.4 KiB

GoodGo Frontend Accessibility Issues - Code Fixes Required

Date: 2026-04-10
Scope: apps/web (GoodGo Frontend)
Status: ACTIONABLE ITEMS - Ready for Implementation


Summary

Found 4 specific accessibility issues that require code fixes across the GoodGo frontend. Below are the exact file paths, line numbers, problematic code snippets, and required fixes.


ISSUE 1: Form Inputs Missing aria-label or Associated Labels

1.1 File Upload Input Without aria-label

File: apps/web/components/listings/image-upload.tsx
Line: 118
Problem: Hidden file input has no aria-label or associated label element

Current Code:

<input
  ref={inputRef}
  type="file"
  accept="image/jpeg,image/png,image/webp"
  multiple
  className="hidden"
  onChange={(e) => {
    if (e.target.files) addFiles(e.target.files);
    e.target.value = '';
  }}
/>

Fix Required: Add aria-label

<input
  ref={inputRef}
  type="file"
  accept="image/jpeg,image/png,image/webp"
  multiple
  className="hidden"
  aria-label="Chọn ảnh để tải lên"
  onChange={(e) => {
    if (e.target.files) addFiles(e.target.files);
    e.target.value = '';
  }}
/>

1.2 Search Save Dialog - Text Input Without aria-label

File: apps/web/app/[locale]/(public)/search/page.tsx
Line: 189
Problem: Text input for saving search name has no associated label or aria-label

Current Code:

<input
  type="text"
  value={saveName}
  onChange={(e) => setSaveName(e.target.value)}
  placeholder="Tên tìm kiếm (VD: Chung cư Q7 dưới 3 tỷ)"
  className="mb-3 w-full rounded-md border bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-primary"
  maxLength={100}
  onKeyDown={(e) => e.key === 'Enter' && handleSaveSearch()}
/>

Fix Required: Add aria-label

<input
  type="text"
  value={saveName}
  onChange={(e) => setSaveName(e.target.value)}
  placeholder="Tên tìm kiếm (VD: Chung cư Q7 dưới 3 tỷ)"
  aria-label="Tên bộ lọc tìm kiếm"
  className="mb-3 w-full rounded-md border bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-primary"
  maxLength={100}
  onKeyDown={(e) => e.key === 'Enter' && handleSaveSearch()}
/>

1.3 Admin Moderation - Select All Checkbox Without aria-label

File: apps/web/app/[locale]/(admin)/admin/moderation/page.tsx
Line: 222
Problem: Table header checkbox for "select all" has no aria-label

Current Code:

<TableHead className="w-10">
  <input
    type="checkbox"
    checked={selected.size === result.data.length && result.data.length > 0}
    onChange={toggleSelectAll}
    className="rounded border-input"
  />
</TableHead>

Fix Required: Add aria-label

<TableHead className="w-10">
  <input
    type="checkbox"
    aria-label="Chọn tất cả tin đăng"
    checked={selected.size === result.data.length && result.data.length > 0}
    onChange={toggleSelectAll}
    className="rounded border-input"
  />
</TableHead>

1.4 Admin Moderation - Row Checkboxes Without aria-label

File: apps/web/app/[locale]/(admin)/admin/moderation/page.tsx
Line: 242
Problem: Individual row checkboxes in table have no aria-label

Current Code:

<TableCell>
  <input
    type="checkbox"
    checked={selected.has(item.listingId)}
    onChange={() => toggleSelect(item.listingId)}
    className="rounded border-input"
  />
</TableCell>

Fix Required: Add aria-label with dynamic content

<TableCell>
  <input
    type="checkbox"
    aria-label={`Chọn tin đăng: ${item.title || item.listingId}`}
    checked={selected.has(item.listingId)}
    onChange={() => toggleSelect(item.listingId)}
    className="rounded border-input"
  />
</TableCell>

ISSUE 2: Mock Image Component Missing alt Attribute

2.1 Test Mock Image Component

File: apps/web/app/[locale]/(public)/search/__tests__/search.spec.tsx
Line: 46
Problem: Mock Image component spreads all props including missing alt attribute

Current Code:

default: (props: Record<string, unknown>) => <img {...props} />,

Fix Required: Ensure alt is always included in mock or add default

default: (props: Record<string, unknown>) => <img {...props} alt={props.alt || ''} />,

OR better approach - require alt in mock setup:

default: (props: Record<string, unknown>) => {
  if (!props.alt) {
    console.warn('Missing alt attribute in Image mock:', props);
  }
  return <img {...props} alt={props.alt || 'image'} />;
},

ISSUE 3: Hidden File Input Needs Better Accessibility

3.1 Image Upload Drag-Drop Area Needs Better Labeling

File: apps/web/components/listings/image-upload.tsx
Lines: 86-128
Problem: The clickable div that triggers file input has descriptive text but no label element linking to the hidden input

Current Implementation:

<div
  onDragOver={handleDragOver}
  onDragLeave={handleDragLeave}
  onDrop={handleDrop}
  onClick={() => inputRef.current?.click()}
  className={cn(...)}
>
  <svg>...</svg>
  <p className="text-sm font-medium">Kéo thả nh vào đây hoặc nhấp để chọn</p>
  <p className="mt-1 text-xs text-muted-foreground">
    JPG, PNG, WebP - Tối đa {maxFiles} nh, mỗi nh 10MB
  </p>
  <input
    ref={inputRef}
    type="file"
    accept="image/jpeg,image/png,image/webp"
    multiple
    className="hidden"
    onChange={(e) => {...}}
  />
</div>

Recommended Enhancement: Add proper label or role

<div
  onDragOver={handleDragOver}
  onDragLeave={handleDragLeave}
  onDrop={handleDrop}
  onClick={() => inputRef.current?.click()}
  role="button"
  tabIndex={0}
  aria-label="Khu vực kéo thả hoặc nhấp để tải ảnh lên"
  onKeyDown={(e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      inputRef.current?.click();
    }
  }}
  className={cn(...)}
>
  {/* ... rest of content ... */}
</div>

ISSUE 4: Verification of Properly Implemented Accessibility

CORRECT - Image Components with alt attributes

The following files already have proper alt attributes and require NO changes:

  • apps/web/components/listings/image-gallery.tsx - All Image components have alt (lines 34, 77)
  • apps/web/components/listings/image-upload.tsx - All img tags have alt (line 135-138)
  • apps/web/components/search/property-card.tsx - Image has alt (line 44)
  • apps/web/app/[locale]/(dashboard)/listings/page.tsx - All Images have alt (lines 192, 272)
  • apps/web/app/[locale]/(dashboard)/dashboard/page.tsx - Image has alt (line 252)
  • apps/web/app/[locale]/(admin)/admin/kyc/page.tsx - All Images have alt (lines 102, 116, 130)

CORRECT - Icon-only Buttons with aria-label

The following files already have proper aria-labels and require NO changes:

  • apps/web/components/listings/image-gallery.tsx - Navigation buttons have aria-labels (lines 47, 54)
  • apps/web/app/[locale]/(public)/layout.tsx - Mobile menu button has aria-label (line 91)

CORRECT - Dialogs with Semantic Titles

The following dialogs already have proper DialogTitle elements and require NO changes:

  • apps/web/app/[locale]/(dashboard)/dashboard/subscription/page.tsx - DialogTitle present (line 327-329)
  • apps/web/app/[locale]/(admin)/admin/kyc/page.tsx - Both dialogs have DialogTitle (approval and rejection dialogs)

CORRECT - Checkbox with Associated Label

  • apps/web/app/[locale]/(public)/search/page.tsx (line 199) - Checkbox has associated <label> element

Implementation Priority

Priority 1 (High Impact):

  1. Add aria-label to file input (image-upload.tsx:118)
  2. Add aria-label to search name input (search/page.tsx:189)

Priority 2 (High Impact): 3. Add aria-label to table checkboxes (moderation/page.tsx:222, 242) 4. Fix mock Image component to require alt (search.spec.tsx:46)

Priority 3 (Enhancement): 5. Improve image upload drag-drop area accessibility with role and keyboard support (image-upload.tsx:86-128)


Testing Checklist

After implementing fixes, verify:

  • Screen readers announce all form inputs correctly
  • File input has meaningful aria-label when focused
  • Search dialog inputs are accessible via keyboard
  • Table checkboxes have descriptive labels for each row
  • No console warnings about missing alt attributes in tests
  • Keyboard navigation works for all interactive elements
  • WCAG 2.1 Level AA compliance verified with automated tools