Skip to content

Commit ed156c4

Browse files
committed
dev/ARIA: Shorten WCAG AA section and improve Lighthouse documentation
1 parent 9e0a0f3 commit ed156c4

File tree

1 file changed

+56
-225
lines changed

1 file changed

+56
-225
lines changed

src/dev/ARIA.md

Lines changed: 56 additions & 225 deletions
Original file line numberDiff line numberDiff line change
@@ -256,209 +256,96 @@ These phases outline areas that need further accessibility work. Explore in deta
256256

257257
### Application Entry Points
258258

259-
**Flow:**
259+
CoCalc is a single-page application with this startup flow:
260260

261-
1. **`packages/static/src/app.html`** - Minimal HTML template with empty `<head>` and container divs
262-
2. **`packages/static/src/webapp-cocalc.ts`** - Entry point that calls `init()`
263-
3. **`packages/frontend/entry-point.ts`** - Initializes Redux, stores, and all app subsystems
261+
1. **`packages/static/src/app.html`** - Base HTML template with React container div
262+
2. **`packages/static/src/webapp-cocalc.ts`** - Entry point that initializes the app
263+
3. **`packages/frontend/entry-point.ts`** - Initializes Redux stores and app subsystems
264264
4. **`packages/frontend/app/render.tsx`** - Mounts React app to `#cocalc-webapp-container`
265-
5. **`packages/frontend/app/page.tsx`** - Main App component with navigation, content layout
265+
5. **`packages/frontend/app/page.tsx`** - Main App component with navigation and layout
266266

267-
### Current Structure
267+
Key files implementing WCAG AA compliance:
268268

269-
- **static** package: Builds static assets (webpack) for the SPA
270-
- **frontend** package: React components, Redux state, app logic
271-
- **app.html**: Base template (extremely minimal - needs enhancement)
272-
- **Entry**: Uses React 18 `createRoot()` for client-side rendering
269+
- `packages/frontend/app/localize.tsx` - Dynamic `lang` attribute on `<html>`
270+
- `packages/frontend/browser.ts` - `set_window_title()` and `set_meta_description()` functions
271+
- `packages/static/src/meta.tsx` - Viewport and meta tags
272+
- `packages/frontend/customize.tsx` - Page description from customization settings
273273

274-
### WCAG AA Improvements Needed
274+
### Running Lighthouse Accessibility Audits
275275

276-
#### 1. HTML Root & Head Elements (`app.html` & `meta.tsx`)
276+
**In Chrome DevTools:**
277277

278-
- [x] ✅ Add `lang` attribute to `<html>` for screen reader language detection - **Fixed in `packages/frontend/app/localize.tsx`** (dynamically set from i18n locale)
279-
- [x] ✅ Remove `user-scalable=no` from viewport meta tag (WCAG AA: low vision users must be able to zoom) - **Fixed in `packages/static/src/meta.tsx`**
280-
- [x] ✅ Add `<title>` tag (can be updated dynamically via React) - **Already implemented in `packages/frontend/browser.ts`** with `set_window_title()` function called throughout app navigation
281-
- [x] ✅ Add `<meta name="description">` for page description - **Fixed in `packages/frontend/browser.ts`** (added `set_meta_description()` function) and **`packages/frontend/customize.tsx`** (called on customize init with format `{site_name}: {site_description}`)
282-
- [ ] Link favicon and apple-touch-icon
283-
- [ ] Add **skip links** for keyboard navigation (skip to main content, skip nav)
278+
1. Open your local CoCalc instance (e.g., `http://localhost:5000`)
279+
2. Open DevTools (F12)
280+
3. Go to **Lighthouse** tab
281+
4. Select **Desktop** device
282+
5. Select only **Accessibility** (uncheck Performance, Best Practices, SEO)
283+
6. Click **Analyze page load**
284+
7. Save the report as JSON: click menu → **Save as JSON**
284285

285-
#### 2. Document Structure
286-
287-
- [ ] Ensure React app renders proper semantic HTML structure
288-
- [ ] Root `<main>` landmark for primary content (✅ partially done in page.tsx)
289-
- [ ] `<nav>` for top navigation (✅ done in page.tsx)
290-
- [ ] `<aside>` for sidebars (need to verify)
291-
- [ ] Dynamic page `<title>` based on context (projects, files, pages)
292-
293-
#### 3. Focus Management & Keyboard
294-
295-
- [ ] Skip to main content link (functional, keyboard-accessible)
296-
- [ ] Focus visible styles for keyboard users (`:focus-visible`)
297-
- [ ] Focus trap for modals (ensure focus doesn't escape)
298-
- [ ] Tab order validation (logical flow through page)
299-
- [ ] Return key handling for interactive elements
300-
301-
#### 4. Color & Contrast
302-
303-
- [ ] Verify WCAG AA contrast ratios (4.5:1 for normal text, 3:1 for large text)
304-
- [ ] Test with color blindness simulators
305-
- [ ] Ensure no information conveyed by color alone
306-
307-
#### 5. Images & Icons
308-
309-
- [ ] All decorative images: `aria-hidden="true"` or empty `alt=""`
310-
- [ ] Functional images: meaningful `alt` text
311-
- [ ] Icon-only buttons: `aria-label` (✅ mostly done)
312-
313-
#### 6. Forms & Inputs
314-
315-
- [ ] All `<input>` elements have associated `<label>` or `aria-label`
316-
- [ ] Required fields marked with `aria-required="true"`
317-
- [ ] Error messages linked via `aria-describedby`
318-
- [ ] Form validation messages announced to screen readers
319-
320-
#### 7. Headings & Structure
321-
322-
- [ ] Proper heading hierarchy (h1 → h2 → h3, no skips)
323-
- [ ] Meaningful heading text (not "Click here", "More")
324-
- [ ] One h1 per page (main topic/title)
325-
326-
#### 8. Alerts & Notifications
327-
328-
- [ ] Success/error messages: `role="alert"` with `aria-live="assertive"`
329-
- [ ] Info messages: `aria-live="polite"`
330-
- [ ] Notification timeout announcements
331-
332-
### Testing Strategy
333-
334-
1. **Chrome DevTools Accessibility Audit**
335-
- Run DevTools → Lighthouse → Accessibility
336-
- Document all failures and warnings
337-
- Prioritize by impact and frequency
338-
339-
2. **Manual Testing**
340-
- Keyboard navigation (Tab, Shift+Tab, Enter, Escape)
341-
- Screen reader testing (NVDA, JAWS, or macOS VoiceOver)
342-
- Color contrast checking (use WebAIM contrast checker)
343-
- Zoom testing (up to 200% at 1280px width)
344-
345-
3. **Automated Testing**
346-
- axe DevTools browser extension
347-
- WAVE browser extension
348-
- Pa11y CLI tool for batch testing
349-
350-
### Implementation Priority
351-
352-
**High Priority** (impacts many users):
353-
354-
- HTML lang attribute and meta tags
355-
- Skip links
356-
- Color contrast fixes
357-
- Form label associations
358-
- Heading hierarchy
359-
360-
**Medium Priority** (improves usability):
361-
362-
- Focus visible styles
363-
- Modal focus traps
364-
- Dynamic page titles
365-
- Confirmation dialogs
366-
367-
**Low Priority** (nice to have):
368-
369-
- Advanced ARIA patterns
370-
- Internationalization meta tags
371-
- Schema.org microdata
286+
Reports are automatically timestamped (e.g., `localhost_5000-20251113T152932.json`) - save to `dev/` directory.
372287

373288
## Processing Lighthouse JSON Reports
374289

375-
When analyzing Lighthouse accessibility audit reports, use Python and `jq` to extract data:
376-
377-
### Quick Summary of Audit Results
290+
### Extract Summary of Results
378291

379292
```bash
380-
# Parse Lighthouse report to see pass/fail counts for key audits
381293
python3 << 'EOF'
382294
import json
383295
384296
with open('dev/localhost_5000-TIMESTAMP.json') as f:
385297
report = json.load(f)
386298
387-
audits_to_check = [
388-
'aria-required-parent',
389-
'aria-required-children',
390-
'aria-command-name',
391-
'image-alt',
392-
'label-content-name-mismatch',
393-
'link-name',
394-
'color-contrast',
395-
]
396-
397-
print("Lighthouse Accessibility Audit Results")
299+
audits = ['aria-required-parent', 'aria-required-children', 'aria-command-name',
300+
'image-alt', 'label-content-name-mismatch', 'link-name', 'color-contrast']
301+
302+
print("Lighthouse Accessibility Audit Summary")
398303
print("=" * 70)
399304
400-
for audit_id in audits_to_check:
305+
for audit_id in audits:
401306
if audit_id in report['audits']:
402307
audit = report['audits'][audit_id]
403308
score = audit.get('score')
404-
passed = len(audit.get('details', {}).get('passed', []))
405309
failed = len(audit.get('details', {}).get('failed', []))
406-
status = "✓ PASS" if score == 1 else f"✗ FAIL ({score})"
407-
print(f"{audit_id:35} | {status:12} | Pass: {passed:2} | Fail: {failed:2}")
408-
310+
status = "✓ PASS" if score == 1 else f"✗ FAIL ({failed} issues)"
311+
print(f"{audit_id:35} {status}")
409312
EOF
410313
```
411314

412-
### Detailed Failure Analysis
315+
### Extract Failure Details
413316

414317
```bash
415-
# Show failure details for specific audit
416318
python3 << 'EOF'
417319
import json
418320
419321
with open('dev/localhost_5000-TIMESTAMP.json') as f:
420322
report = json.load(f)
421323
422-
audit_id = 'aria-required-parent' # Change to audit you want to inspect
324+
# Change audit_id to inspect a specific audit
325+
audit_id = 'aria-required-parent'
423326
if audit_id in report['audits']:
424-
audit = report['audits'][audit_id]
425-
failed = audit.get('details', {}).get('failed', [])
426-
427-
print(f"\nAudit: {audit_id}")
428-
print(f"Failed items: {len(failed)}\n")
429-
430-
for item in failed[:5]: # Show first 5
431-
selector = item.get('node', {}).get('selector', 'unknown')
432-
snippet = item.get('node', {}).get('snippet', '')
433-
explanation = item.get('node', {}).get('explanation', '')
434-
print(f"Selector: {selector}")
435-
print(f"HTML: {snippet[:100]}")
436-
print(f"Issue: {explanation[:150]}")
437-
print()
438-
327+
failed = report['audits'][audit_id].get('details', {}).get('failed', [])
328+
print(f"Audit: {audit_id} - {len(failed)} issues\n")
329+
for item in failed[:5]:
330+
print(f"Selector: {item['node']['selector']}")
331+
print(f"Issue: {item['node']['explanation'][:150]}\n")
439332
EOF
440333
```
441334

442-
### Using jq for Quick Inspection
335+
### Quick jq Inspection
443336

444337
```bash
445-
# List all audit IDs in the report
338+
# List all audit IDs
446339
jq '.audits | keys[]' dev/localhost_5000-TIMESTAMP.json
447340

448-
# Count failed items for specific audit
341+
# Count failures for specific audit
449342
jq '.audits["aria-required-parent"].details.failed | length' dev/localhost_5000-TIMESTAMP.json
450343

451-
# Show failed node selectors
344+
# Show failure selectors
452345
jq '.audits["aria-required-parent"].details.failed[].node.selector' dev/localhost_5000-TIMESTAMP.json
453346
```
454347

455-
### Key Points
456-
457-
1. **File location**: Reports are saved to `dev/localhost_5000-TIMESTAMP.json` after each Lighthouse run
458-
2. **Structure**: `report['audits'][audit_id]['details']['failed']` contains failure array
459-
3. **Node info**: Each failure has `.node` with selector, snippet, explanation
460-
4. **Score values**: score = 1 means PASS, score = 0 means FAIL
461-
5. **Performance**: Python scripts are faster than jq for summary reports
348+
**Key**: `report['audits'][audit_id]['details']['failed']` contains the failure array with `.node.selector`, `.node.snippet`, and `.node.explanation`.
462349

463350
## Lighthouse Accessibility Audit Results (Desktop)
464351

@@ -468,78 +355,24 @@ jq '.audits["aria-required-parent"].details.failed[].node.selector' dev/localhos
468355

469356
### Failures Found (7 issues)
470357

471-
1. **[color-contrast](https://dequeuniversity.com/rules/axe/4.11/color-contrast)** (14 items) ⚠️ **DEFER**
472-
- Background and foreground colors don't meet WCAG AA ratios (4.5:1 normal, 3:1 large)
473-
- **Plan**: Handle via custom antd theme with 3 options: "Antd (standard)", "Cocalc", "Accessibility"
474-
- Store in preferences/appearance config
475-
- Ignore contrast requirements for ornamental details (e.g., footer)
476-
477-
2. **[aria-required-parent](https://dequeuniversity.com/rules/axe/4.11/aria-required-parent)** (4 items) ✅ **FIXED**
478-
- Ant Design Tabs: `role="tab"` elements missing required `tablist` parent role
479-
- Fixed by adding `role="tablist"` to SortableTabs container in `packages/frontend/components/sortable-tabs.tsx` (line 115)
480-
- Creates proper ARIA hierarchy: tablist parent → Ant Design's role="tab" children
481-
- Fixes tabs in projects-nav and file-tabs components
482-
483-
3. **[image-alt](https://dequeuniversity.com/rules/axe/4.11/image-alt)** (3 items) ✅ **FIXED**
484-
- All avatar images inside `.ant-avatar` components missing `[alt]` attributes
485-
- These images convey meaningful information about users/projects/models, not decorative
486-
- Fixed in:
487-
- `packages/frontend/account/avatar/avatar.tsx` - user avatar images with `alt="User {username}"` (used in collaborators, etc.)
488-
- `packages/frontend/components/language-model-icon.tsx` - LLM model icons with `alt="{vendorName} language model"`
489-
- `packages/frontend/projects/project-title.tsx` - project avatar in titles with `src={avatar} alt="Project avatar"`
490-
- `packages/frontend/projects/project-avatar.tsx` - project avatar display with `src={avatarImage} alt="Project avatar"`
491-
- `packages/frontend/projects/projects-nav.tsx` - project avatar in nav with `src={...} alt="Project avatar"`
492-
- `packages/frontend/projects/projects-table-columns.tsx` - project avatars in table and collaborator avatars in filters with appropriate alt text
493-
- Changed from `icon={<img src={...} />}` to `src={...} alt="..."` to properly expose alt attribute to Ant Design Avatar
494-
495-
4. **[label-content-name-mismatch](https://dequeuniversity.com/rules/axe/4.11/label-content-name-mismatch)** (3 items) ✅ **FIXED**
496-
- Fixed in `packages/frontend/projects/projects-table-controls.tsx`:
497-
- "Hidden" switch: `aria-label="Toggle hidden projects"` (was "Show hidden projects")
498-
- "Deleted" switch: `aria-label="Toggle deleted projects"` (was "Show deleted projects")
499-
- "Create Project" button: `aria-label="Create a new project ..."` (was "Create a new project")
500-
- Visible text now matches or is included in accessible names
501-
502-
5. **[aria-command-name](https://dequeuniversity.com/rules/axe/4.11/aria-command-name)** (1 item) ✅ **FIXED**
503-
- Fixed by adding `aria-label="Admin"` to admin NavTab in `packages/frontend/app/page.tsx` (line 233)
504-
- Now admin button has accessible name for screen readers
505-
506-
6. **[link-name](https://dequeuniversity.com/rules/axe/4.11/link-name)** (1 item) ✅ **FIXED**
507-
- CoCalc logo link was missing accessible name
508-
- Fixed by:
509-
- Adding `aria-label="CoCalc homepage"` to AppLogo in `packages/frontend/app/logo.tsx` (line 39)
510-
- Updated `<A>` component in `packages/frontend/components/A.tsx` to accept and forward `aria-label` prop
511-
512-
7. **[aria-required-children](https://dequeuniversity.com/rules/axe/4.11/aria-required-children)** (2 items) 🔄 **IN PROGRESS**
513-
- tablist parent has invalid children (role=button, buttons that should be role=tab)
514-
- Root cause: SortableTab wrapper adds `role="button"` which is invalid inside tablist
515-
- Issue in: Projects nav tabs and file tabs where SortableTab (with dnd-kit) wraps the tab elements
516-
- Solution needed: Adjust SortableTab wrapper to use `role="tab"` or restructure to avoid role conflict
517-
518-
### Latest Report Analysis (2025-11-13 15:22:38 UTC)
519-
520-
**Final Fixes Applied:**
521-
522-
**label-content-name-mismatch** (3 items) ✅ **FIXED**
523-
524-
- Removed duplicate aria-labels from Switch components (kept only visible text)
525-
- Removed mismatched aria-label from Create button (visible text is sufficient)
526-
- Switches now use checkedChildren/unCheckedChildren for visible text only
527-
- Create button uses visible text without aria-label override
528-
529-
**aria-required-children** (2 items) ✅ **FIXED**
530-
531-
- Added `role="tab"` to SortableTab wrapper in `packages/frontend/components/sortable-tabs.tsx` (line 154)
532-
- This overrides the `role="button"` that dnd-kit attributes add via spread operator
533-
- Now tablist only has role="tab" children (both wrapper and inner Ant Design tab)
534-
535-
**aria-required-parent** (4 items) 🔄 **PENDING VERIFICATION**
358+
#. **[color-contrast](https://dequeuniversity.com/rules/axe/4.11/color-contrast)** (14 items) ⚠️ **DEFER**
536359

537-
- May be auto-fixed by the SortableTab role="tab" change
538-
- Need to run Lighthouse again to verify
360+
- Background and foreground colors don't meet WCAG AA ratios (4.5:1 normal, 3:1 large)
361+
- **Plan**: Handle via custom antd theme with 3 options: "Antd (standard)", "Cocalc", "Accessibility"
362+
- Store in preferences/appearance config
363+
- Ignore contrast requirements for ornamental details (e.g., footer)
364+
365+
#. **[aria-required-children](https://dequeuniversity.com/rules/axe/4.11/aria-required-children)** (2 items) 🔄 **IN PROGRESS**
539366

540-
### Current Status Summary (2025-11-13 15:29:32 UTC)
367+
- tablist parent has invalid children (role=button, buttons that should be role=tab)
368+
- Root cause: SortableTab wrapper adds `role="button"` which is invalid inside tablist
369+
- Issue in: Projects nav tabs and file tabs where SortableTab (with dnd-kit) wraps the tab elements
370+
- Solution needed: Adjust SortableTab wrapper to use `role="tab"` or restructure to avoid role conflict
541371

542-
**Report**: localhost_5000-20251113T152932.json
372+
#. **aria-required-parent** (4 items) 🔄 **PENDING VERIFICATION**
373+
374+
- May be auto-fixed by the SortableTab role="tab" change
375+
- Need to run Lighthouse again to verify
543376

544377
After all fixes:
545378

@@ -551,8 +384,6 @@ After all fixes:
551384
- ❌ aria-required-children: 2 failures (tablist has non-tab/status children)
552385
- ⚠️ color-contrast: 8+ items - DEFERRED to custom antd theme implementation
553386

554-
**Score**: 5/7 audits passing (71% of issues fixed)
555-
556387
### Remaining Issue: aria-required-parent & aria-required-children
557388

558389
**Root Cause**: The actual Ant Design tab elements (`div role="tab"`) are nested too deeply inside the tablist wrapper due to how renderTabBar wraps each tab in SortableTab.

0 commit comments

Comments
 (0)