🎨 Palette: [UX improvement] Add aria-hidden to decorative SVGs (#70)

* Added aria-hidden="true" to decorative SVG icons within interactive elements (buttons, links) in Lightbox, ThemeToggle, Footer, BackLink, and MapView.
* Added role="alert" and aria-live="polite" to the MapView loading indicator.
* Documented learning in .Jules/palette.md

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
Ralksta
2026-04-01 12:46:20 +02:00
committed by GitHub
parent dbc525e8d8
commit e739fdb518
6 changed files with 33 additions and 14 deletions

View File

@@ -1,3 +1,7 @@
## 2024-05-24 - Lightbox ARIA Attributes ## 2024-05-24 - Lightbox ARIA Attributes
**Learning:** Adding `role="dialog"`, `aria-modal="true"`, and `aria-label` to custom modal/lightbox overlays ensures screen readers announce them properly instead of just falling back to reading inner content without context. Additionally, toggles that show/hide panels (like the EXIF info button) must use `aria-expanded` and link to the panel via `aria-controls` for proper screen reader communication. **Learning:** Adding `role="dialog"`, `aria-modal="true"`, and `aria-label` to custom modal/lightbox overlays ensures screen readers announce them properly instead of just falling back to reading inner content without context. Additionally, toggles that show/hide panels (like the EXIF info button) must use `aria-expanded` and link to the panel via `aria-controls` for proper screen reader communication.
**Action:** When building or modifying custom overlays or toggle buttons in the future, always verify that ARIA attributes are set correctly to match the visual behavior. **Action:** When building or modifying custom overlays or toggle buttons in the future, always verify that ARIA attributes are set correctly to match the visual behavior.
## 2024-05-25 - SVG Icon Accessibility
**Learning:** Adding `aria-hidden="true"` to decorative `<svg>` icons within interactive elements (like `<button>` or `<a>`) that already have an `aria-label` is crucial. Otherwise, screen readers may read the raw vector nodes or fall back to confusing announcements in addition to the label. Similarly, when adding text to visual spinners, use `role="alert"` and `aria-live="polite"` on the container while hiding the SVG graphic.
**Action:** When adding or modifying interactive elements with icon graphics, always ensure the graphic is explicitly hidden from screen readers using `aria-hidden="true"` if a text alternative (`aria-label`) is provided.

View File

@@ -14,6 +14,7 @@ export function BackLink({ href, label }: BackLinkProps) {
return ( return (
<Link href={href} className="album-header__back" aria-label={`Back to ${label}`}> <Link href={href} className="album-header__back" aria-label={`Back to ${label}`}>
<svg <svg
aria-hidden="true"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"

View File

@@ -40,6 +40,7 @@ export function Footer() {
aria-label="Instagram" aria-label="Instagram"
> >
<svg <svg
aria-hidden="true"
width="16" width="16"
height="16" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -58,6 +59,7 @@ export function Footer() {
{footer?.email && ( {footer?.email && (
<a href={`mailto:${footer.email}`} className="footer__link" aria-label="Email"> <a href={`mailto:${footer.email}`} className="footer__link" aria-label="Email">
<svg <svg
aria-hidden="true"
width="16" width="16"
height="16" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -81,6 +83,7 @@ export function Footer() {
aria-label="Website" aria-label="Website"
> >
<svg <svg
aria-hidden="true"
width="16" width="16"
height="16" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"

View File

@@ -111,6 +111,7 @@ export function Lightbox({ assets, currentIndex, onClose, onNext, onPrev }: Ligh
{/* Close button */} {/* Close button */}
<button className={styles.close} onClick={onClose} aria-label="Close" title="Close (Esc)"> <button className={styles.close} onClick={onClose} aria-label="Close" title="Close (Esc)">
<svg <svg
aria-hidden="true"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
@@ -131,6 +132,7 @@ export function Lightbox({ assets, currentIndex, onClose, onNext, onPrev }: Ligh
title="Previous photo (Left arrow)" title="Previous photo (Left arrow)"
> >
<svg <svg
aria-hidden="true"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
@@ -171,6 +173,7 @@ export function Lightbox({ assets, currentIndex, onClose, onNext, onPrev }: Ligh
title="Next photo (Right arrow)" title="Next photo (Right arrow)"
> >
<svg <svg
aria-hidden="true"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"

View File

@@ -151,7 +151,10 @@ export function MapView() {
if (error) { if (error) {
return ( return (
<div className="map-container__loading" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }}> <div
className="map-container__loading"
style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }}
>
<p>{error}</p> <p>{error}</p>
</div> </div>
); );
@@ -162,27 +165,30 @@ export function MapView() {
{loading && ( {loading && (
<div <div
className="map-container__loading" className="map-container__loading"
style={{ role="alert"
position: 'absolute', aria-live="polite"
inset: 0, style={{
position: 'absolute',
inset: 0,
zIndex: 1000, zIndex: 1000,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
background: 'rgba(0, 0, 0, 0.5)', background: 'rgba(0, 0, 0, 0.5)',
color: '#fff' color: '#fff',
}} }}
> >
<svg <svg
width="24" aria-hidden="true"
height="24" width="24"
viewBox="0 0 24 24" height="24"
fill="none" viewBox="0 0 24 24"
stroke="currentColor" fill="none"
strokeWidth="2" stroke="currentColor"
strokeLinecap="round" strokeWidth="2"
strokeLinejoin="round" strokeLinecap="round"
strokeLinejoin="round"
style={{ animation: 'spin 1s linear infinite', marginBottom: '8px' }} style={{ animation: 'spin 1s linear infinite', marginBottom: '8px' }}
> >
<circle cx="12" cy="12" r="10" strokeOpacity="0.25"></circle> <circle cx="12" cy="12" r="10" strokeOpacity="0.25"></circle>

View File

@@ -63,6 +63,7 @@ export function ThemeToggle() {
{theme === 'dark' ? ( {theme === 'dark' ? (
// Sun icon // Sun icon
<svg <svg
aria-hidden="true"
width="16" width="16"
height="16" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -85,6 +86,7 @@ export function ThemeToggle() {
) : ( ) : (
// Moon icon // Moon icon
<svg <svg
aria-hidden="true"
width="16" width="16"
height="16" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"