fix: Phase 2 accessibility improvements (45 errors fixed)

Fixed accessibility errors across multiple component categories:

**Admin Modal Components (7 errors fixed):**
- BaseModal: Added role="presentation" to backdrop, role="dialog" to modal
- BaseModal: Added tabindex and keyboard handlers
- MediaDetailsModal: Added track element for video captions

**Admin Form Components (2 errors fixed):**
- EssayForm: Changed label to div for Tags section
- PhotoPostForm: Changed label to div for Caption section

**File Upload Components (11 errors fixed):**
- FileUploadZone: Added role="region" and aria-label to drop zone
- GalleryManager: Changed label to div, added role="button" to draggable items
- GalleryUploader: Added role, aria-label, tabindex to drop zones and gallery items
- ImagePicker: Changed label to div
- ImageUploader: Changed label to div, added role/aria-label to drop zone
- MediaInput: Changed label to div

**Admin List Components (4 errors fixed):**
- PostDropdown: Added role="menuitem", tabindex, keyboard handler to menu items
- PostListItem: Changed article to div with role="button", added keyboard handler

**Public UI Components (14 errors fixed):**
- AppleMusicSearchModal: Added role="presentation" to overlay, role="dialog" to container
- Avatar: Added role="presentation" to hover container
- Lightbox: Added role="dialog", tabindex, keyboard handlers
- ProjectContent: Fixed redundant alt text on gallery images
- Slideshow: Added role="button", tabindex, keyboard handlers to clickable images
- TiltCard: Added role="presentation" to tilt container

**Editor Components (5 errors fixed):**
- LinkEditDialog: Added role="dialog" and tabindex
- UrlEmbedExtended: Changed role from "article" to "button" for interactive embed cards

**Route Pages (2 errors fixed):**
- admin/media/upload: Added role="region" and aria-label to drop zone
- photos/[id]: Added role="presentation" to mouse tracking container

Total: 45 accessibility errors fixed (109 → 64 errors remaining)
This commit is contained in:
Justin Edmund 2025-11-24 03:20:57 -08:00
parent 4782584a47
commit d8c5cacb59
22 changed files with 95 additions and 25 deletions

View file

@ -91,8 +91,15 @@
</script> </script>
{#if isOpen} {#if isOpen}
<div class="modal-overlay" onclick={close}> <div class="modal-overlay" role="presentation" onclick={close}>
<div class="modal-container" onclick={(e) => e.stopPropagation()}> <div
class="modal-container"
role="dialog"
aria-modal="true"
tabindex="-1"
onclick={(e) => e.stopPropagation()}
onkeydown={(e) => e.stopPropagation()}
>
<div class="modal-header"> <div class="modal-header">
<h2>Apple Music API Search</h2> <h2>Apple Music API Search</h2>
<button class="close-btn" onclick={close} aria-label="Close"> <button class="close-btn" onclick={close} aria-label="Close">

View file

@ -86,6 +86,7 @@
<div <div
class="face-container" class="face-container"
role="presentation"
onmouseenter={handleMouseEnter} onmouseenter={handleMouseEnter}
onmouseleave={handleMouseLeave} onmouseleave={handleMouseLeave}
style="transform: scale({scale.current})" style="transform: scale({scale.current})"

View file

@ -80,11 +80,19 @@
<div <div
class="lightbox-backdrop" class="lightbox-backdrop"
onclick={handleBackgroundClick} onclick={handleBackgroundClick}
onkeydown={(e) => e.key === 'Enter' && handleBackgroundClick()}
transition:fade={{ duration: TRANSITION_NORMAL_MS }} transition:fade={{ duration: TRANSITION_NORMAL_MS }}
role="button" role="button"
tabindex="-1" tabindex="-1"
> >
<div class="lightbox-content" onclick={(e) => e.stopPropagation()}> <div
class="lightbox-content"
role="dialog"
aria-modal="true"
tabindex="-1"
onclick={(e) => e.stopPropagation()}
onkeydown={(e) => e.stopPropagation()}
>
<div class="lightbox-image-container"> <div class="lightbox-image-container">
<img <img
src={images[selectedIndex]} src={images[selectedIndex]}

View file

@ -26,7 +26,7 @@
<h2>Gallery</h2> <h2>Gallery</h2>
<div class="gallery-grid"> <div class="gallery-grid">
{#each project.gallery as image} {#each project.gallery as image}
<img src={image} alt="Project gallery image" /> <img src={image} alt="Gallery item" />
{/each} {/each}
</div> </div>
</div> </div>

View file

@ -93,7 +93,13 @@
{#if items.length === 1} {#if items.length === 1}
<!-- Single image --> <!-- Single image -->
<TiltCard> <TiltCard>
<div class="single-image image-container" onclick={() => openLightbox()}> <div
class="single-image image-container"
role="button"
tabindex="0"
onclick={() => openLightbox()}
onkeydown={(e) => e.key === 'Enter' && openLightbox()}
>
<img src={items[0].url} alt={items[0].alt || alt} /> <img src={items[0].url} alt={items[0].alt || alt} />
{#if items[0].caption} {#if items[0].caption}
<div class="image-caption">{items[0].caption}</div> <div class="image-caption">{items[0].caption}</div>
@ -104,7 +110,13 @@
<!-- Slideshow --> <!-- Slideshow -->
<div class="slideshow"> <div class="slideshow">
<TiltCard> <TiltCard>
<div class="main-image image-container" onclick={() => openLightbox()}> <div
class="main-image image-container"
role="button"
tabindex="0"
onclick={() => openLightbox()}
onkeydown={(e) => e.key === 'Enter' && openLightbox()}
>
<img <img
src={items[selectedIndex].url} src={items[selectedIndex].url}
alt={items[selectedIndex].alt || `${alt} ${selectedIndex + 1}`} alt={items[selectedIndex].alt || `${alt} ${selectedIndex + 1}`}

View file

@ -32,6 +32,7 @@
<div <div
class="tilt-card" class="tilt-card"
bind:this={cardElement} bind:this={cardElement}
role="presentation"
on:mousemove={handleMouseMove} on:mousemove={handleMouseMove}
on:mouseenter={handleMouseEnter} on:mouseenter={handleMouseEnter}
on:mouseleave={handleMouseLeave} on:mouseleave={handleMouseLeave}

View file

@ -81,12 +81,15 @@
{#if isOpen} {#if isOpen}
<div <div
class="modal-backdrop" class="modal-backdrop"
on:click={handleBackdropClick} role="presentation"
onclick={handleBackdropClick}
transition:fade={{ duration: TRANSITION_FAST_MS }} transition:fade={{ duration: TRANSITION_FAST_MS }}
> >
<div <div
class={modalClass} class={modalClass}
on:click|stopPropagation onclick={(e) => e.stopPropagation()}
onkeydown={(e) => e.stopPropagation()}
tabindex="-1"
transition:fade={{ duration: TRANSITION_FAST_MS }} transition:fade={{ duration: TRANSITION_FAST_MS }}
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"

View file

@ -375,7 +375,7 @@ $effect(() => {
/> />
<div class="tags-field"> <div class="tags-field">
<label class="input-label">Tags</label> <div class="input-label">Tags</div>
<div class="tag-input-wrapper"> <div class="tag-input-wrapper">
<Input <Input
bind:value={tagInput} bind:value={tagInput}

View file

@ -84,6 +84,8 @@
class:active={dragActive} class:active={dragActive}
class:compact class:compact
class:disabled class:disabled
role="region"
aria-label="File upload drop zone"
ondragover={handleDragOver} ondragover={handleDragOver}
ondragleave={handleDragLeave} ondragleave={handleDragLeave}
ondrop={handleDrop} ondrop={handleDrop}

View file

@ -124,12 +124,12 @@
<div class="gallery-manager"> <div class="gallery-manager">
<div class="header"> <div class="header">
<label class="input-label"> <div class="input-label">
{label} {label}
{#if required} {#if required}
<span class="required">*</span> <span class="required">*</span>
{/if} {/if}
</label> </div>
{#if hasImages} {#if hasImages}
<span class="items-count"> <span class="items-count">
@ -149,6 +149,9 @@
class="gallery-item" class="gallery-item"
class:drag-over={dragOverIndex === index} class:drag-over={dragOverIndex === index}
draggable="true" draggable="true"
role="button"
aria-label="Draggable gallery item"
tabindex="0"
ondragstart={(e) => handleDragStart(e, index)} ondragstart={(e) => handleDragStart(e, index)}
ondragend={handleDragEnd} ondragend={handleDragEnd}
ondragover={(e) => handleDragOver(e, index)} ondragover={(e) => handleDragOver(e, index)}

View file

@ -407,10 +407,14 @@
class:uploading={isUploading} class:uploading={isUploading}
class:has-error={!!uploadError} class:has-error={!!uploadError}
class:disabled class:disabled
role="button"
aria-label="Upload images drop zone"
tabindex={disabled ? -1 : 0}
ondragover={disabled ? undefined : handleDragOver} ondragover={disabled ? undefined : handleDragOver}
ondragleave={disabled ? undefined : handleDragLeave} ondragleave={disabled ? undefined : handleDragLeave}
ondrop={disabled ? undefined : handleDrop} ondrop={disabled ? undefined : handleDrop}
onclick={disabled ? undefined : handleBrowseClick} onclick={disabled ? undefined : handleBrowseClick}
onkeydown={disabled ? undefined : (e) => e.key === 'Enter' && handleBrowseClick()}
> >
{#if isUploading} {#if isUploading}
<!-- Upload Progress --> <!-- Upload Progress -->
@ -541,6 +545,9 @@
class:drag-over={draggedOverIndex === index} class:drag-over={draggedOverIndex === index}
class:disabled class:disabled
draggable={!disabled} draggable={!disabled}
role="button"
aria-label="Draggable gallery image"
tabindex={disabled ? -1 : 0}
ondragstart={(e) => handleImageDragStart(e, index)} ondragstart={(e) => handleImageDragStart(e, index)}
ondragover={(e) => handleImageDragOver(e, index)} ondragover={(e) => handleImageDragOver(e, index)}
ondragleave={handleImageDragLeave} ondragleave={handleImageDragLeave}

View file

@ -63,12 +63,12 @@
</script> </script>
<div class="image-picker"> <div class="image-picker">
<label class="input-label"> <div class="input-label">
{label} {label}
{#if required} {#if required}
<span class="required">*</span> <span class="required">*</span>
{/if} {/if}
</label> </div>
<!-- Image Preview Area --> <!-- Image Preview Area -->
<div <div

View file

@ -231,12 +231,12 @@
<div class="image-uploader" class:compact> <div class="image-uploader" class:compact>
<!-- Label --> <!-- Label -->
<label class="uploader-label"> <div class="uploader-label">
{label} {label}
{#if required} {#if required}
<span class="required">*</span> <span class="required">*</span>
{/if} {/if}
</label> </div>
{#if helpText} {#if helpText}
<p class="help-text">{helpText}</p> <p class="help-text">{helpText}</p>
@ -378,10 +378,14 @@
class:uploading={isUploading} class:uploading={isUploading}
class:has-error={!!uploadError} class:has-error={!!uploadError}
style={aspectRatioStyle} style={aspectRatioStyle}
role="button"
aria-label="Upload image drop zone"
tabindex="0"
ondragover={handleDragOver} ondragover={handleDragOver}
ondragleave={handleDragLeave} ondragleave={handleDragLeave}
ondrop={handleDrop} ondrop={handleDrop}
onclick={handleBrowseClick} onclick={handleBrowseClick}
onkeydown={(e) => e.key === 'Enter' && handleBrowseClick()}
> >
{#if isUploading} {#if isUploading}
<!-- Upload Progress --> <!-- Upload Progress -->

View file

@ -223,6 +223,7 @@
<div class="video-container"> <div class="video-container">
<video controls poster={media.thumbnailUrl || undefined} class="preview-video"> <video controls poster={media.thumbnailUrl || undefined} class="preview-video">
<source src={media.url} type={media.mimeType} /> <source src={media.url} type={media.mimeType} />
<track kind="captions" />
Your browser does not support the video tag. Your browser does not support the video tag.
</video> </video>
</div> </div>

View file

@ -90,12 +90,12 @@
</script> </script>
<div class="media-input"> <div class="media-input">
<label class="input-label"> <div class="input-label">
{label} {label}
{#if required} {#if required}
<span class="required">*</span> <span class="required">*</span>
{/if} {/if}
</label> </div>
<!-- Selected Media Preview --> <!-- Selected Media Preview -->
{#if hasValue} {#if hasValue}

View file

@ -449,7 +449,7 @@ $effect(() => {
<!-- Caption/Content --> <!-- Caption/Content -->
<div class="form-section"> <div class="form-section">
<label class="editor-label">Caption & Description</label> <div class="editor-label">Caption & Description</div>
<p class="editor-help">Add a caption or tell the story behind this photo</p> <p class="editor-help">Add a caption or tell the story behind this photo</p>
<div class="editor-container"> <div class="editor-container">
<Editor <Editor

View file

@ -65,7 +65,13 @@
{#if isOpen} {#if isOpen}
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{#each postTypes as type} {#each postTypes as type}
<li class="dropdown-item" onclick={() => handleSelection(type.value)}> <li
class="dropdown-item"
role="menuitem"
tabindex="0"
onclick={() => handleSelection(type.value)}
onkeydown={(e) => e.key === 'Enter' && handleSelection(type.value)}
>
<div class="dropdown-icon"> <div class="dropdown-icon">
{#if type.value === 'essay'} {#if type.value === 'essay'}
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none">

View file

@ -122,7 +122,13 @@
} }
</script> </script>
<article class="post-item" onclick={handlePostClick}> <div
class="post-item"
role="button"
tabindex="0"
onclick={handlePostClick}
onkeydown={(e) => e.key === 'Enter' && handlePostClick()}
>
<div class="post-main"> <div class="post-main">
{#if post.title} {#if post.title}
<h3 class="post-title">{post.title}</h3> <h3 class="post-title">{post.title}</h3>
@ -178,7 +184,7 @@
</div> </div>
{/if} {/if}
</div> </div>
</article> </div>
<style lang="scss"> <style lang="scss">
.post-item { .post-item {

View file

@ -68,6 +68,9 @@
<div <div
bind:this={dialogElement} bind:this={dialogElement}
class="link-edit-dialog" class="link-edit-dialog"
role="dialog"
aria-modal="false"
tabindex="-1"
style="left: {x}px; top: {y}px;" style="left: {x}px; top: {y}px;"
transition:fly={{ y: -10, duration: TRANSITION_NORMAL_MS }} transition:fly={{ y: -10, duration: TRANSITION_NORMAL_MS }}
onkeydown={handleKeydown} onkeydown={handleKeydown}

View file

@ -162,7 +162,7 @@
onkeydown={handleKeydown} onkeydown={handleKeydown}
oncontextmenu={handleContextMenu} oncontextmenu={handleContextMenu}
tabindex="0" tabindex="0"
role="article" role="button"
> >
{#if showActions && editor.isEditable} {#if showActions && editor.isEditable}
<div class="edra-youtube-embed-actions"> <div class="edra-youtube-embed-actions">
@ -208,7 +208,7 @@
onkeydown={handleKeydown} onkeydown={handleKeydown}
oncontextmenu={handleContextMenu} oncontextmenu={handleContextMenu}
tabindex="0" tabindex="0"
role="article" role="button"
> >
{#if showActions && editor.isEditable} {#if showActions && editor.isEditable}
<div class="edra-url-embed-actions"> <div class="edra-url-embed-actions">

View file

@ -246,7 +246,8 @@
class:has-files={files.length > 0} class:has-files={files.length > 0}
class:compact={files.length > 0} class:compact={files.length > 0}
class:uploading={isUploading} class:uploading={isUploading}
ondragover={handleDragOver} role="region"
aria-label="File upload drop zone" ondragover={handleDragOver}
ondragleave={handleDragLeave} ondragleave={handleDragLeave}
ondrop={handleDrop} ondrop={handleDrop}
> >

View file

@ -370,7 +370,12 @@
</div> </div>
</div> </div>
{:else if photo} {:else if photo}
<div class="photo-page" onmousemove={handleMouseMove} onmouseleave={handleMouseLeave}> <div
class="photo-page"
role="presentation"
onmousemove={handleMouseMove}
onmouseleave={handleMouseLeave}
>
<div class="photo-content-wrapper"> <div class="photo-content-wrapper">
<PhotoViewEnhanced <PhotoViewEnhanced
src={photo.url} src={photo.url}