# Semantics Before ARIA

**MUST** · **ID:** `content-semantics-first` · **Category:** content
**Source:** [Vercel](https://github.com/vercel-labs/agent-skills/blob/main/skills/web-design-guidelines/SKILL.md)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/content-semantics-first

> MUST: Prefer native semantics (`button`, `a`, `label`, `table`) before ARIA

Prefer native elements before ARIA attributes

Native HTML elements have built-in accessibility, keyboard support, and expected behaviors. Use <button> instead of <div role="button">, <a> instead of <span role="link">. Only add ARIA when native elements don't provide the semantics you need.

## Bad — do not do this

`content-semantics-first-bad`

```tsx
export function SemanticsFirstBad() {
  return (
    <div className="w-full max-w-sm">
      <div className="bg-card border border-border rounded-lg p-4">
        <div className="space-y-3">
          <div
            role="button"
            tabIndex={0}
            className="px-4 py-2 bg-primary text-primary-foreground rounded-lg cursor-pointer hover:bg-primary/90"
          >
            Submit Form
          </div>
          <div
            role="link"
            tabIndex={0}
            className="text-primary underline cursor-pointer hover:text-primary/80"
          >
            View Documentation
          </div>
        </div>
        <div className="mt-3 bg-error/10 border border-error/20 rounded-lg p-3">
          <code className="text-xs text-error font-mono block whitespace-pre">
{`<div role="button">...</div>
<div role="link">...</div>`}
          </code>
        </div>
        <p className="mt-2 text-xs text-muted-foreground">
          Divs with roles don't get native keyboard events, form submission, or middle-click behavior.
        </p>
      </div>
      <p className="text-xs text-error mt-4">
        ARIA roles on divs - missing native behaviors
      </p>
    </div>
  );
}
```

## Good — do this

`content-semantics-first-good`

```tsx
export function SemanticsFirstGood() {
  return (
    <div className="w-full max-w-sm">
      <div className="bg-card border border-border rounded-lg p-4">
        <div className="space-y-3">
          <button
            type="submit"
            className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
          >
            Submit Form
          </button>
          <a
            href="#docs"
            className="inline-block text-primary underline hover:text-primary/80 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
          >
            View Documentation
          </a>
        </div>
        <div className="mt-3 bg-success/10 border border-success/20 rounded-lg p-3">
          <code className="text-xs text-success font-mono block whitespace-pre">
{`<button type="submit">...</button>
<a href="#docs">...</a>`}
          </code>
        </div>
        <p className="mt-2 text-xs text-muted-foreground">
          Native elements have keyboard support, form integration, and expected browser behaviors built-in.
        </p>
      </div>
      <p className="text-xs text-success mt-4">
        Native elements - full accessibility and behavior for free
      </p>
    </div>
  );
}
```

## References

- [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/)
- [No ARIA is Better Than Bad ARIA](https://www.w3.org/WAI/ARIA/apg/practices/read-me-first/)
