Initial commit
This commit is contained in:
467
skills/frontend-designer/scripts/audit_accessibility.sh
Executable file
467
skills/frontend-designer/scripts/audit_accessibility.sh
Executable file
@@ -0,0 +1,467 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user