Skip to main content
CodeRocket
Accessibilitymediumcontent

Fix empty and broken links

rule · empty-links

Every link must have an accessible name that describes its destination or purpose.

Code Example

HTML
<!-- ❌ Bad: Completely empty link -->
<a href="/home"></a>
<!-- Screen reader: "link" (no context) -->
 
<!-- ❌ Bad: Image link with no alt -->
<a href="/profile">
  <img src="avatar.jpg">
</a>
<!-- Screen reader: "link, image" (no context) -->
 
<!-- ❌ Bad: Icon link with no label -->
<a href="/settings">
  <svg>...</svg>
</a>
<!-- Screen reader: "link" (no context) -->

Why It Matters

Screen readers announce empty links as just 'link' with no context—users have no idea where they lead or what they do, making navigation impossible.

Add Visible Text

HTML
<!-- ✅ Good: Visible link text -->
<a href="/home">Go to homepage</a>
 
<!-- ✅ Good: Image with descriptive alt -->
<a href="/profile">
  <img src="avatar.jpg" alt="View your profile">
</a>
HTML
<!-- ✅ Good: Icon link with aria-label -->
<a href="/settings" aria-label="Account settings">
  <svg aria-hidden="true">...</svg>
</a>
 
<!-- ✅ Good: Social media icons -->
<a href="https://twitter.com/company" aria-label="Follow us on Twitter">
  <svg aria-hidden="true"><!-- Twitter icon --></svg>
</a>

Visually Hidden Text

HTML
<!-- ✅ Good: Visually hidden but screen reader accessible -->
<a href="/cart">
  <svg aria-hidden="true"><!-- Cart icon --></svg>
  <span class="sr-only">Shopping cart (3 items)</span>
</a>
CSS
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

React Component

TSX
interface IconLinkProps {
  href: string
  label: string
  icon: React.ReactNode
  isExternal?: boolean
}
 
function IconLink({ href, label, icon, isExternal }: IconLinkProps) {
  return (
    <a
      href={href}
      aria-label={label}
      target={isExternal ? '_blank' : undefined}
      rel={isExternal ? 'noopener noreferrer' : undefined}
    >
      <span aria-hidden="true">{icon}</span>
      {isExternal && <span className="sr-only">(opens in new tab)</span>}
    </a>
  )
}
 
// Usage
<IconLink
  href="/settings"
  label="Account settings"
  icon={<SettingsIcon />}
/>
JavaScript
// Browser console check
document.querySelectorAll('a').forEach(link => {
  const text = link.textContent?.trim()
  const ariaLabel = link.getAttribute('aria-label')
  const imgAlt = link.querySelector('img')?.getAttribute('alt')
 
  if (!text && !ariaLabel && !imgAlt) {
    console.warn('Empty link found:', link)
  }
})

Exceptions

  • Evaluate the rendered experience before treating a static-code smell as a blocker; interaction timing, browser behavior, and assistive technology output often determine severity.
  • Not every secondary accessibility issue deserves equal weight; prioritize the issue that most directly blocks perception, operation, or understanding.
  • Avoid adding redundant markup or ARIA solely to satisfy a rule when a simpler semantic implementation would eliminate the issue entirely.

Verification

Automated Checks

  • Use axe DevTools or Lighthouse to find empty links

Manual Checks

  • Navigate with screen reader—every link should announce its purpose
  • Check that image links have alt text or aria-label
  • Verify icon-only links have accessible names