---
task_id: sprint_4_story_1_task_6
story_id: sprint_4_story_1
sprint_id: sprint_4
status: not_started
type: frontend
assignee: Frontend Developer 1
created_date: 2025-11-05
estimated_hours: 3
---

# Task 6: Implement Responsive Design and Accessibility

## Description

Ensure the Story detail page works flawlessly on all screen sizes (mobile, tablet, desktop) and meets WCAG 2.1 Level AA accessibility standards. This includes responsive layout adjustments, keyboard navigation, ARIA labels, and focus management.

## What to Do

1. Test and fix responsive layout on all breakpoints
2. Implement mobile-specific UI adjustments (sidebar to tabs)
3. Add keyboard navigation support (Tab, Enter, ESC)
4. Add ARIA labels to all interactive elements
5. Implement focus management (dialog open/close)
6. Ensure proper heading hierarchy (h1 → h2 → h3)
7. Test color contrast ratios (WCAG AA: 4.5:1 minimum)
8. Test with screen reader (NVDA/JAWS)
9. Add skip links for keyboard users
10. Run Lighthouse accessibility audit

## Files to Modify

- `app/(dashboard)/stories/[id]/page.tsx` (modify, responsive tweaks)
- `components/projects/story-header.tsx` (modify, accessibility)
- `components/projects/story-metadata-sidebar.tsx` (modify, responsive)
- All related components (add ARIA labels)

## Implementation Details

### Responsive Layout Breakpoints

```typescript
// app/(dashboard)/stories/[id]/page.tsx
// Adjust grid layout for different screens

<div className="grid grid-cols-1 lg:grid-cols-[1fr_320px] gap-6">
  {/* Main content - full width on mobile, 70% on desktop */}
  <div className="space-y-6 order-2 lg:order-1">
    {/* Story description, acceptance criteria, tasks */}
  </div>

  {/* Sidebar - full width on mobile (at top), fixed width on desktop */}
  <aside className="order-1 lg:order-2">
    {/* Metadata sidebar */}
  </aside>
</div>
```

### Mobile-Specific Adjustments

```typescript
// Convert sidebar sections to tabs on mobile
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';

function MobileStoryMetadata({ story, parentEpic }: Props) {
  return (
    <Tabs defaultValue="details" className="lg:hidden">
      <TabsList className="w-full">
        <TabsTrigger value="details">Details</TabsTrigger>
        <TabsTrigger value="time">Time</TabsTrigger>
        <TabsTrigger value="epic">Epic</TabsTrigger>
      </TabsList>

      <TabsContent value="details">
        {/* Status, Priority, Assignee */}
      </TabsContent>

      <TabsContent value="time">
        {/* Time tracking, Dates */}
      </TabsContent>

      <TabsContent value="epic">
        {/* Parent Epic card */}
      </TabsContent>
    </Tabs>
  );
}
```

### ARIA Labels and Roles

```typescript
// Story header with accessibility
<header role="banner" aria-labelledby="story-title">
  <h1 id="story-title" className="text-3xl font-bold">
    {story.title}
  </h1>

  <div role="group" aria-label="Story metadata">
    <Badge aria-label={`Status: ${story.status}`}>
      {story.status}
    </Badge>

    <Badge aria-label={`Priority: ${story.priority}`}>
      {story.priority}
    </Badge>
  </div>

  <div role="group" aria-label="Story actions">
    <Button
      onClick={onEdit}
      aria-label="Edit story details"
      aria-describedby="edit-help"
    >
      <Edit className="h-4 w-4" aria-hidden="true" />
      Edit
    </Button>
    <span id="edit-help" className="sr-only">
      Opens a dialog to edit story title, description, and metadata
    </span>

    <Button
      onClick={onDelete}
      aria-label="Delete story permanently"
      aria-describedby="delete-help"
    >
      <Trash2 className="h-4 w-4" aria-hidden="true" />
      Delete
    </Button>
    <span id="delete-help" className="sr-only">
      Permanently deletes this story and all associated tasks
    </span>
  </div>
</header>

// Main content sections
<main role="main" aria-labelledby="story-title">
  <section aria-labelledby="description-heading">
    <h2 id="description-heading">Description</h2>
    <div role="document" aria-label="Story description">
      {story.description}
    </div>
  </section>

  <section aria-labelledby="criteria-heading">
    <h2 id="criteria-heading">Acceptance Criteria</h2>
    {/* Criteria list */}
  </section>

  <section aria-labelledby="tasks-heading">
    <h2 id="tasks-heading">Tasks</h2>
    {/* Tasks list */}
  </section>
</main>

// Sidebar
<aside role="complementary" aria-label="Story metadata">
  <h2 className="sr-only">Story Details</h2>
  {/* Metadata cards */}
</aside>
```

### Keyboard Navigation

```typescript
// Add keyboard shortcuts
useEffect(() => {
  const handleKeyDown = (e: KeyboardEvent) => {
    // ESC - Go back to Epic
    if (e.key === 'Escape' && !isEditDialogOpen && !isDeleteDialogOpen) {
      router.push(`/epics/${story.epicId}`);
    }

    // Cmd/Ctrl + E - Edit Story
    if ((e.metaKey || e.ctrlKey) && e.key === 'e') {
      e.preventDefault();
      setIsEditDialogOpen(true);
    }

    // Cmd/Ctrl + Backspace - Delete Story
    if ((e.metaKey || e.ctrlKey) && e.key === 'Backspace') {
      e.preventDefault();
      setIsDeleteDialogOpen(true);
    }
  };

  window.addEventListener('keydown', handleKeyDown);
  return () => window.removeEventListener('keydown', handleKeyDown);
}, [isEditDialogOpen, isDeleteDialogOpen]);

// Add keyboard shortcut hints
<footer className="mt-8 text-xs text-muted-foreground">
  <p>Keyboard shortcuts:</p>
  <ul className="list-disc list-inside">
    <li>ESC - Back to Epic</li>
    <li>Cmd/Ctrl + E - Edit Story</li>
    <li>Cmd/Ctrl + Backspace - Delete Story</li>
  </ul>
</footer>
```

### Focus Management

```typescript
// Trap focus in dialogs
import { useFocusTrap } from '@/lib/hooks/use-focus-trap';

function EditStoryDialog({ isOpen, onClose }: Props) {
  const dialogRef = useRef<HTMLDivElement>(null);
  useFocusTrap(dialogRef, isOpen);

  // Auto-focus first field when dialog opens
  useEffect(() => {
    if (isOpen) {
      const firstInput = dialogRef.current?.querySelector('input');
      firstInput?.focus();
    }
  }, [isOpen]);

  // Return focus to trigger element when dialog closes
  const triggerRef = useRef<HTMLButtonElement | null>(null);

  const handleOpen = () => {
    triggerRef.current = document.activeElement as HTMLButtonElement;
    setIsOpen(true);
  };

  const handleClose = () => {
    setIsOpen(false);
    triggerRef.current?.focus();
  };

  return (
    <Dialog open={isOpen} onOpenChange={handleClose}>
      <DialogContent ref={dialogRef}>
        {/* Dialog content */}
      </DialogContent>
    </Dialog>
  );
}
```

### Skip Links

```typescript
// Add skip link for keyboard users
<a
  href="#main-content"
  className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-primary focus:text-primary-foreground focus:rounded-md"
>
  Skip to main content
</a>

<main id="main-content" tabIndex={-1}>
  {/* Main content */}
</main>
```

## Acceptance Criteria

### Responsive Design
- [ ] Desktop (> 1024px): Two-column layout (content + sidebar)
- [ ] Tablet (640px - 1024px): Two-column layout with narrower sidebar
- [ ] Mobile (< 640px): Single-column layout, sidebar moves to top or tabs
- [ ] All buttons and controls accessible on touch devices
- [ ] No horizontal scrolling on any screen size
- [ ] Text remains readable on all screen sizes (minimum 14px)

### Accessibility (WCAG 2.1 Level AA)
- [ ] All interactive elements keyboard accessible (Tab navigation)
- [ ] Focus indicators visible (2px outline, high contrast)
- [ ] ARIA labels on all buttons, links, and form controls
- [ ] Proper heading hierarchy (h1 → h2 → h3)
- [ ] Color contrast ratio >= 4.5:1 for all text
- [ ] Large text (18px+) contrast ratio >= 3:1
- [ ] Screen reader announces all content correctly
- [ ] Focus trapped in dialogs (cannot Tab outside)
- [ ] Focus returns to trigger element when dialog closes
- [ ] Skip links available for keyboard users
- [ ] Icons have `aria-hidden="true"` (text labels present)
- [ ] Loading states announced to screen readers (`role="status"`)
- [ ] Error messages announced (`role="alert"`)

### Keyboard Navigation
- [ ] Tab - Navigate through interactive elements
- [ ] Enter - Activate buttons and links
- [ ] Space - Toggle checkboxes and buttons
- [ ] ESC - Close dialogs and navigate back
- [ ] Cmd/Ctrl + E - Edit Story (custom shortcut)
- [ ] Cmd/Ctrl + Backspace - Delete Story (custom shortcut)

## Testing

### Manual Testing

**Responsive Design**:
1. Open Story detail page on desktop → Verify two-column layout
2. Resize to tablet width (768px) → Verify layout adjusts
3. Resize to mobile width (375px) → Verify single-column layout
4. Verify sidebar moves to top on mobile
5. Test all buttons clickable on touch device
6. Verify no horizontal scrolling on any screen
7. Test landscape and portrait orientations

**Keyboard Navigation**:
1. Navigate to Story page using only keyboard
2. Press Tab → Verify focus moves through all interactive elements
3. Verify focus indicator visible (blue outline)
4. Press ESC → Verify navigates back to Epic
5. Press Cmd/Ctrl + E → Verify Edit dialog opens
6. Press Tab in dialog → Verify focus trapped
7. Press ESC in dialog → Verify dialog closes and focus returns

**Screen Reader** (NVDA/JAWS):
1. Enable screen reader
2. Navigate to Story page
3. Verify page structure announced (heading hierarchy)
4. Verify all buttons have labels
5. Verify badges announced with context ("Status: In Progress")
6. Verify loading states announced
7. Verify error messages announced
8. Open Edit dialog → Verify form fields labeled correctly

### Automated Testing

**Lighthouse Audit**:
```bash
# Run Lighthouse accessibility audit
npm run lighthouse

# Expected results:
# Accessibility: >= 90 (ideally 100)
# Best Practices: >= 90
# SEO: >= 90
# Performance: >= 80 (acceptable for dynamic content)
```

**axe DevTools**:
```bash
# Install axe DevTools browser extension
# Open Story page
# Run axe scan
# Expected: 0 violations
```

### Color Contrast Testing

```bash
# Test all text colors
# Use browser DevTools > Accessibility > Color contrast

# Required ratios (WCAG AA):
# Normal text (< 18px): 4.5:1
# Large text (>= 18px): 3:1
# UI components: 3:1

# Test combinations:
Story Title (32px):     Should pass (large text)
Description (16px):     Should pass 4.5:1
Metadata labels (14px): Should pass 4.5:1
Badges:                 Should pass 3:1 (UI components)
Buttons:                Should pass 3:1 (UI components)
```

## Dependencies

**Prerequisites**:
- Task 1, 2, 3, 4, 5 (all components must exist)
- ✅ shadcn/ui components with built-in accessibility
- ✅ Tailwind CSS responsive utilities
- ✅ ARIA attributes support

**Blocks**:
- None (final polish task)

## Estimated Time

3 hours

## Notes

**Testing Tools**:
- Chrome DevTools > Lighthouse (accessibility audit)
- axe DevTools browser extension (automated WCAG testing)
- NVDA/JAWS screen reader (manual testing)
- WAVE browser extension (visual accessibility check)
- Contrast Checker (color contrast ratios)

**Common Accessibility Issues to Avoid**:
- Missing alt text on images
- Insufficient color contrast
- Missing ARIA labels on icon-only buttons
- Broken keyboard navigation (focus trap)
- Missing heading hierarchy (h1 → h3, skip h2)
- Form inputs without labels
- Loading states not announced to screen readers

**Responsive Design Checklist**:
- Test on real devices (iPhone, Android, tablet)
- Test different orientations (portrait, landscape)
- Test different zoom levels (100%, 150%, 200%)
- Verify touch targets >= 44x44px
- No horizontal scrolling on any screen
