Files
gh-jamesrochabrun-skills/skills/frontend-designer/scripts/audit_accessibility.sh
2025-11-29 18:48:55 +08:00

468 lines
14 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# Frontend Designer - Accessibility Audit
# Comprehensive WCAG 2.1 AA compliance checker
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
NC='\033[0m' # No Color
# Counters
PASS_COUNT=0
FAIL_COUNT=0
WARNING_COUNT=0
# Helper functions
print_success() {
echo -e "${GREEN}✓ PASS${NC} $1"
((PASS_COUNT++))
}
print_error() {
echo -e "${RED}✗ FAIL${NC} $1"
((FAIL_COUNT++))
}
print_warning() {
echo -e "${YELLOW}⚠ WARN${NC} $1"
((WARNING_COUNT++))
}
print_info() {
echo -e "${BLUE} INFO${NC} $1"
}
print_section() {
echo ""
echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${MAGENTA}$1${NC}"
echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
# Banner
echo ""
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ Frontend Designer - Accessibility Audit ║"
echo "║ WCAG 2.1 AA Compliance ║"
echo "║ ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
# Get target
if [ -z "$1" ]; then
print_info "Usage: $0 <file.html|directory>"
print_info "Example: $0 index.html"
print_info "Example: $0 src/components/"
exit 1
fi
TARGET="$1"
# Check if target exists
if [ ! -e "$TARGET" ]; then
print_error "Target not found: $TARGET"
exit 1
fi
# Section 1: HTML Structure
print_section "1. HTML STRUCTURE & SEMANTICS"
check_html_lang() {
if grep -q '<html[^>]*\slang=' "$1"; then
print_success "HTML lang attribute present"
else
print_error "Missing lang attribute on <html>"
echo " Fix: <html lang=\"en\">"
fi
}
check_page_title() {
if grep -q '<title>' "$1"; then
print_success "Page title present"
else
print_error "Missing <title> element"
echo " Fix: Add <title>Page Title</title>"
fi
}
check_main_landmark() {
if grep -q '<main' "$1" || grep -q 'role="main"' "$1"; then
print_success "Main landmark present"
else
print_warning "No <main> landmark found"
echo " Tip: Use <main> for primary content"
fi
}
check_heading_structure() {
if grep -q '<h1' "$1"; then
print_success "H1 heading found"
# Check for heading hierarchy
local h1_count=$(grep -o '<h1' "$1" | wc -l)
if [ "$h1_count" -eq 1 ]; then
print_success "Single H1 (recommended)"
else
print_warning "Multiple H1 headings found ($h1_count)"
echo " Tip: Use single H1 per page"
fi
else
print_error "No H1 heading found"
echo " Fix: Add <h1> for main page heading"
fi
}
check_semantic_html() {
local semantic_tags=("nav" "header" "footer" "article" "section" "aside")
local found=false
for tag in "${semantic_tags[@]}"; do
if grep -q "<$tag" "$1"; then
found=true
break
fi
done
if [ "$found" = true ]; then
print_success "Semantic HTML elements used"
else
print_warning "Consider using semantic HTML (nav, header, footer, etc.)"
fi
}
# Section 2: Images & Media
print_section "2. IMAGES & MEDIA"
check_img_alt() {
local img_count=$(grep -o '<img' "$1" | wc -l)
if [ "$img_count" -eq 0 ]; then
print_info "No images found"
else
local alt_count=$(grep '<img' "$1" | grep -c 'alt=')
if [ "$alt_count" -eq "$img_count" ]; then
print_success "All images have alt attributes ($img_count/$img_count)"
else
print_error "Images missing alt attributes ($alt_count/$img_count)"
echo " Fix: Add alt=\"description\" to all images"
echo " For decorative images use alt=\"\""
fi
fi
}
check_video_captions() {
if grep -q '<video' "$1"; then
if grep -q '<track' "$1"; then
print_success "Video has captions/subtitles"
else
print_error "Video missing captions"
echo " Fix: Add <track kind=\"captions\" src=\"captions.vtt\">"
fi
fi
}
# Section 3: Forms
print_section "3. FORMS & INPUTS"
check_form_labels() {
local input_count=$(grep -o '<input' "$1" | wc -l)
if [ "$input_count" -eq 0 ]; then
print_info "No form inputs found"
else
# Check for labels
local label_count=$(grep -o '<label' "$1" | wc -l)
local aria_label_count=$(grep '<input' "$1" | grep -c 'aria-label')
local aria_labelledby_count=$(grep '<input' "$1" | grep -c 'aria-labelledby')
local labeled_count=$((label_count + aria_label_count + aria_labelledby_count))
if [ "$labeled_count" -ge "$input_count" ]; then
print_success "All inputs have labels"
else
print_error "Some inputs missing labels"
echo " Fix: Use <label for=\"id\"> or aria-label"
fi
# Check for required fields
if grep -q 'required' "$1"; then
if grep -q 'aria-required="true"' "$1"; then
print_success "Required fields marked with aria-required"
else
print_warning "Consider adding aria-required=\"true\" to required fields"
fi
fi
fi
}
check_error_messages() {
if grep -q 'aria-describedby' "$1"; then
print_success "Error messages linked with aria-describedby"
elif grep -q 'error' "$1"; then
print_warning "Error handling present, verify aria-describedby usage"
fi
}
# Section 4: Interactive Elements
print_section "4. INTERACTIVE ELEMENTS"
check_button_text() {
# Check for empty buttons
if grep -q '<button[^>]*></button>' "$1"; then
print_error "Empty button found"
echo " Fix: Add text or aria-label to button"
else
print_success "No empty buttons found"
fi
}
check_link_text() {
# Check for generic link text
if grep -qi 'click here\|read more\|more' "$1"; then
print_warning "Generic link text found (click here, read more)"
echo " Tip: Use descriptive link text"
else
print_success "No generic link text detected"
fi
}
check_skip_links() {
if grep -q 'skip.*content\|skip.*navigation' "$1"; then
print_success "Skip navigation link present"
else
print_warning "No skip navigation link found"
echo " Tip: Add skip link for keyboard users"
echo " <a href=\"#main\" class=\"skip-link\">Skip to content</a>"
fi
}
# Section 5: ARIA
print_section "5. ARIA ATTRIBUTES"
check_aria_roles() {
if grep -q 'role=' "$1"; then
print_success "ARIA roles found"
# Check for button roles on non-button elements
if grep -q '<div[^>]*role="button"' "$1" || grep -q '<span[^>]*role="button"' "$1"; then
if grep -q 'tabindex=' "$1"; then
print_success "Custom buttons have tabindex"
else
print_error "role=\"button\" without tabindex"
echo " Fix: Add tabindex=\"0\" to custom buttons"
fi
fi
fi
}
check_aria_labels() {
if grep -q 'aria-label=' "$1"; then
print_success "ARIA labels used for context"
fi
# Check for redundant aria-label
if grep -q '<button[^>]*aria-label.*>[^<]*</button>' "$1"; then
print_warning "Possible redundant aria-label on button with text"
echo " Tip: Use aria-label when button has no visible text"
fi
}
check_aria_live() {
if grep -q 'aria-live' "$1"; then
print_success "Live regions defined"
fi
}
# Section 6: Keyboard Navigation
print_section "6. KEYBOARD NAVIGATION"
check_tabindex() {
# Check for positive tabindex
if grep -q 'tabindex="[1-9]' "$1"; then
print_error "Positive tabindex values found"
echo " Fix: Use tabindex=\"0\" or \"-1\" only"
echo " Positive values disrupt natural tab order"
else
print_success "No positive tabindex values (good)"
fi
}
check_focus_indicators() {
# This would need CSS analysis
print_info "Manual check: Verify focus indicators are visible"
echo " Test: Tab through page, ensure focus is visible"
echo " CSS: :focus-visible { outline: 2px solid; }"
}
# Section 7: Color & Contrast
print_section "7. COLOR & CONTRAST"
print_info "Manual checks required for color/contrast:"
echo ""
echo " Required contrast ratios (WCAG AA):"
echo " ✓ Normal text: 4.5:1"
echo " ✓ Large text (18pt+): 3:1"
echo " ✓ UI components: 3:1"
echo ""
echo " Tools for testing:"
echo " - Chrome DevTools (Lighthouse)"
echo " - WebAIM Contrast Checker"
echo " - axe DevTools"
echo ""
check_color_only() {
if grep -qi 'color:.*red\|color:.*green' "$1"; then
print_warning "Color usage detected - ensure not used as only indicator"
echo " Tip: Don't rely on color alone (add icons, text, patterns)"
fi
}
# Section 8: Responsive & Mobile
print_section "8. RESPONSIVE & MOBILE"
check_viewport() {
if grep -q 'viewport' "$1"; then
print_success "Viewport meta tag present"
else
print_error "Missing viewport meta tag"
echo " Fix: <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
fi
}
check_touch_targets() {
print_info "Manual check: Touch targets minimum 44x44px"
echo " Test: Verify buttons/links meet minimum size"
echo " CSS: min-height: 44px; min-width: 44px;"
}
# Section 9: Content
print_section "9. CONTENT & READABILITY"
check_lang_changes() {
if grep -q '\slang=' "$1"; then
local lang_count=$(grep -o '\slang=' "$1" | wc -l)
if [ "$lang_count" -gt 1 ]; then
print_success "Language changes marked ($lang_count instances)"
fi
fi
}
check_abbreviations() {
if grep -q '<abbr' "$1"; then
print_success "Abbreviations use <abbr> element"
fi
}
# Section 10: Motion & Animation
print_section "10. MOTION & ANIMATIONS"
print_info "Manual check: Respect prefers-reduced-motion"
echo ""
echo " CSS:"
echo " @media (prefers-reduced-motion: reduce) {"
echo " * { animation: none !important; }"
echo " }"
echo ""
# Run checks on files
if [ -f "$TARGET" ]; then
# Single file
check_html_lang "$TARGET"
check_page_title "$TARGET"
check_main_landmark "$TARGET"
check_heading_structure "$TARGET"
check_semantic_html "$TARGET"
check_img_alt "$TARGET"
check_video_captions "$TARGET"
check_form_labels "$TARGET"
check_error_messages "$TARGET"
check_button_text "$TARGET"
check_link_text "$TARGET"
check_skip_links "$TARGET"
check_aria_roles "$TARGET"
check_aria_labels "$TARGET"
check_aria_live "$TARGET"
check_tabindex "$TARGET"
check_focus_indicators "$TARGET"
check_color_only "$TARGET"
check_viewport "$TARGET"
check_touch_targets "$TARGET"
check_lang_changes "$TARGET"
check_abbreviations "$TARGET"
elif [ -d "$TARGET" ]; then
# Directory - find HTML files
html_files=$(find "$TARGET" -name "*.html" -o -name "*.htm")
if [ -z "$html_files" ]; then
print_error "No HTML files found in $TARGET"
exit 1
fi
for file in $html_files; do
print_info "Checking: $file"
check_html_lang "$file"
check_page_title "$file"
check_main_landmark "$file"
check_heading_structure "$file"
check_img_alt "$file"
check_form_labels "$file"
echo ""
done
fi
# Summary
echo ""
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ Audit Summary ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
echo -e "${GREEN}✓ Passed: $PASS_COUNT${NC}"
echo -e "${RED}✗ Failed: $FAIL_COUNT${NC}"
echo -e "${YELLOW}⚠ Warnings: $WARNING_COUNT${NC}"
echo ""
# Calculate score
TOTAL=$((PASS_COUNT + FAIL_COUNT))
if [ $TOTAL -gt 0 ]; then
SCORE=$(( (PASS_COUNT * 100) / TOTAL ))
echo "Score: $SCORE%"
echo ""
if [ $SCORE -ge 90 ]; then
echo -e "${GREEN}Excellent! Your site is highly accessible.${NC}"
elif [ $SCORE -ge 70 ]; then
echo -e "${YELLOW}Good, but needs improvements.${NC}"
else
echo -e "${RED}Needs significant accessibility improvements.${NC}"
fi
fi
echo ""
print_info "Additional Testing Recommended:"
echo " 1. Screen reader testing (NVDA, JAWS, VoiceOver)"
echo " 2. Keyboard-only navigation"
echo " 3. Automated tools (axe, Lighthouse, WAVE)"
echo " 4. Color contrast analyzer"
echo " 5. Real user testing with assistive technologies"
echo ""
print_info "Resources:"
echo " - WCAG 2.1: https://www.w3.org/WAI/WCAG21/quickref/"
echo " - WebAIM: https://webaim.org/"
echo " - a11y Project: https://www.a11yproject.com/"
echo ""
# Exit code based on failures
if [ $FAIL_COUNT -gt 0 ]; then
exit 1
else
exit 0
fi