SakaniUI

Button

The primary action primitive: 5 variants × 3 sizes, all token-bound.

Maps 1:1 to the Figma Button component set. States (hover, focus, disabled) are handled entirely with CSS pseudo-classes, not extra props: what looks like a variant in Figma is a real interactive state in code.

Variants

Sizes

With icons

Loading

Props

PropTypeDefaultDescription
variant'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive''primary'Visual style of the button.
size'sm' | 'md' | 'lg''md'Height and padding scale.
leftIconReactNodeIcon rendered before the label.
rightIconReactNodeIcon rendered after the label.
loadingbooleanfalseShows a spinner and disables interaction.
disabledbooleanfalseNative disabled state.
...restButtonHTMLAttributes<HTMLButtonElement>Every native <button> attribute (type, onClick, form…).

Why it works this way

Why

  • Five variants mirror the Figma set one-to-one, so a designer's choice maps to a prop with no translation.
  • Hover, focus-visible and disabled are CSS states, not props: they are visual in Figma and semantic in code, and a prop would let them drift from the real interaction.
  • A single filled primary marks the one action a view most wants. Two filled buttons compete, and neither reads as primary.

Don't

  • Don't use it for navigation. A link goes somewhere, a button does something, and mixing them breaks keyboard and assistive-tech expectations. Use Link.
  • Don't hand-style a <button> to look like one. You will miss hover, press, focus and loading behaviour, and every future improvement.
  • Don't use it icon-only without a label. Use IconButton, which requires an aria-label.