Primitives over raw elements

Button, Input and Select ship focus, disabled and invalid states.

From shadcn (Vercel plugin), Anti-patterns: raw elements where primitives exist.

Tab through the same form

Click Email, type abc, press Tab, then submit.

Raw elementsBad
Email
Subscribe

  • Label focuses input
  • Visible focus
  • Button reachable by Tab
  • Invalid state shown
Label + Input + ButtonGood

  • Label focuses input
  • Visible focus
  • Button reachable by Tab
  • Invalid state shown
<form onSubmit={onSubmit} noValidate>
  <Label htmlFor="email">Email</Label>
  <Input id="email" type="email" aria-invalid={!!error} aria-describedby="email-error" />
  <p id="email-error">{error}</p>
  <Button type="submit">Subscribe</Button>
</form>

Disabled has to mean disabled

Click each Save fast, then force Disabled and click again.

Force a state on both buttons
<div onClick>Bad
Save changes

onClick: 0

className={disabled ? "opacity-50" : ""}
<Button>Good

onClick: 0

<Button disabled={pending}>
<Button disabled={pending} onClick={save}>
  {pending && <Loader2 className="animate-spin" />}
  Save changes
</Button>

Open a dropdown by keyboard

Tab to each trigger; press ↓, Enter, Esc or m.

div dropdownBad
Currency
USD · US dollar
<div onClick>
<Select>Good
Currency
    <Select>
    <Select value={currency} onValueChange={setCurrency}>
      <SelectTrigger aria-label="Currency"><SelectValue /></SelectTrigger>
      <SelectContent>
        <SelectItem value="USD">USD · US dollar</SelectItem>
        <SelectItem value="MXN">MXN · Mexican peso</SelectItem>
      </SelectContent>
    </Select>

    Why it matters, pros and cons

    Why it matters

    A raw element starts with none of the states users rely on.

    Pros

    • States live in the component, not call sites.
    • Roles and ARIA come from Radix.
    • One token change restyles every ring.

    Cons

    • Heavier than native; native select suits mobile-heavy pages.
    • Some need providers, like TooltipProvider.
    • Radix and Base UI APIs differ; pick one.