Accessibility First

Default to semantic HTML elements, ARIA roles only when needed, and keyboard operability for all interactive components.

Why?  Makes apps usable by everyone and meets WCAG standards.

Semantic markup lets browsers, screen-readers, and assistive tech understand your UI without extra hints. Sticking to native elements first—and adding ARIA only when absolutely required—delivers built-in keyboard support, predictable focus order, and superior SEO, while shrinking the surface for accessibility bugs.

Correct Example

// Accessible toggle component (TypeScript + Tailwind)
import { useState } from "react";

export function DarkModeToggle() {
  const [on, setOn] = useState(false);

  return (
    <button
      type="button"
      aria-pressed={on}
      onClick={() => setOn(!on)}
      className={`flex items-center gap-2 rounded-md px-3 py-2
        ${on ? "bg-zinc-900 text-white" : "bg-zinc-100"}`}
    >
      <span className="sr-only">Toggle dark mode</span>
      <svg
        aria-hidden // decorative icon
        className="h-5 w-5"
        focusable="false"
        viewBox="0 0 20 20"
      >
        {/* …sun/moon path… */}
      </svg>
      <span>{on ? "On" : "Off"}</span>
    </button>
  );
}

Why it’s good:

  • Uses the semantic <button> element (built-in keyboard + focus).
  • Conveys state with aria-pressed (standard, no custom role).
  • Icon is hidden from assistive tech via aria-hidden, while the visible label is duplicated for sighted users.

Incorrect Example

// ⚠️ Mouse-only “button” that breaks accessibility
export function DarkModeToggle() {
  const [on, setOn] = useState(false);

  return (
    <div
      onClick={() => setOn(!on)}
      className={`cursor-pointer select-none rounded-md px-3 py-2
        ${on ? "bg-zinc-900 text-white" : "bg-zinc-100"}`}
    >
      {/* No semantic role, no keyboard handlers, no aria state */}
      <svg className="h-5 w-5" viewBox="0 0 20 20">

      </svg>
      {on ? "On" : "Off"}
    </div>
  );
}

What’s wrong:

  • <div> with onClick has no keyboard or focus support.
  • Missing role="button" & tabIndex=0; still falls short of native behavior (spacebar activation, aria-pressed, etc.).
  • Screen-reader users get zero state feedback.

Key Takeaways

  • Prefer native elements (<button>, <a>, <input>) over generic <div>/<span> wrappers.
  • Add ARIA attributes only to fill gaps; never replace semantics you could get for free.
  • Every interactive element must be focusable and operable via keyboard (Tab, Enter, Space).
  • Use aria-hidden for purely decorative icons; supply off-screen text (.sr-only) for visual-only labels.
  • Test with a screen-reader + keyboard regularly—accessibility is a feature, not an afterthought.