primitives/@yashiel/mihcm-ui·v0.1.0·stable

Button

Primary action affordance. Six variants, four sizes. Universal across web and native.

WCAG 2.1 AA — release blocker

Button must satisfy these on every render before it ships.

Web (React DOM)

  • Renders a real <button> element with type="button" by default.
  • aria-busy set automatically when isLoading is true.
  • Focus state is a 2px ring using --color-ring. Never outline: none without a replacement.
  • Color contrast on every variant verified ≥ 4.5:1 against its background.
  • Keyboard: Tab to focus · Enter / Space to activate. Standard browser behavior; no JS handling needed.

React Native

  • Renders Pressable with:
    • accessibilityRole="button"
    • accessibilityState={{ disabled, busy: isLoading }}
  • Touch target: lg size = 44px minimum (Apple HIG / Android Material).

Reduced motion

Transitions limited to duration-short (200ms). When the user has prefers-reduced-motion, transitions are honored by the user agent — no special handling needed.

Screen-reader checks

  • The button's text content is its accessible name. If you use only an icon, you must pass an aria-label (web) or accessibilityLabel (native).
  • For isLoading states, prefer keeping the original label rather than swapping to "Loading…" — aria-busy carries the state to assistive tech without a label change.

Color is never the only signal

  • Destructive buttons never rely on red alone — pair with the word "Delete" or an icon.
  • Disabled state has reduced opacity AND pointer-events: none AND the underlying semantic state.

Audit checklist

  • Tab-able and visibly focused
  • Enter/Space activates
  • 4.5:1 contrast on every variant in light AND dark
  • 44px touch target on lg and icon
  • Loading state announces via aria-busy (web) or accessibilityState.busy (native)
  • Icon-only variant has explicit label