← kothry

Button

Four types (primary / outline / ghost / link), four sizes (sm / md / lg / xl), a destructive tone and an iconOnly mode — mirroring the Figma variant properties 1:1. Hover and press any example to see its interactive states.

Variants

variant controls the visual type. Default is primary.

<Button>Button</Button>
<Button variant="outline">Button</Button>
<Button variant="ghost">Button</Button>
<Button variant="link">Button</Button>

Sizes

sm 24px / md 32px (default) / lg 40px / xl 48px tall. Link ignores box sizing and scales its text only.

primary
outline
ghost
link
<Button size="sm">Button</Button>
<Button size="md">Button</Button>
<Button size="lg">Button</Button>
<Button size="xl">Button</Button>

Destructive

destructive switches every variant to the error palette without changing its shape.

primary
outline
ghost
link
<Button destructive>Delete</Button>
<Button destructive variant="outline">Delete</Button>
<Button destructive variant="ghost">Delete</Button>
<Button destructive variant="link">Delete</Button>

Disabled

Native disabled attribute; every variant collapses to the grey disabled palette.

regular
destr.
<Button disabled>Button</Button>
<Button disabled variant="outline">Button</Button>
<Button disabled destructive>Delete</Button>

With icons

Any svg child is auto-sized per button size (16px on sm/md, 20px on lg/xl).

<Button><Plus /> New project</Button>
<Button variant="outline">Export <ChevronDown /></Button>
<Button variant="ghost"><Search /> Search</Button>
<Button variant="link">Learn more <ArrowRight /></Button>
<Button destructive><Trash2 /> Delete</Button>

Icon only

iconOnly makes the button square (24/32/40/48px). The link variant has no icon-only form in the design. Always provide aria-label.

primary
outline
ghost
<Button iconOnly aria-label="Settings"><Settings /></Button>
<Button iconOnly variant="outline" size="lg" aria-label="Add"><Plus /></Button>
<Button iconOnly destructive variant="ghost" aria-label="Delete"><Trash2 /></Button>

Loading

loading overlays a centered spinner without shifting width and blocks re-triggering (aria-busy). It is not the disabled look — the user is waiting on their own action.

<Button loading={pending} onClick={submit}>Recalculate score</Button>
<Button variant="outline" loading>Saving</Button>

Composition

className merges via tailwind-merge; buttonVariants() exposes the class generator for non-React targets.

<Button className="w-full" size="lg">Continue</Button>

import { buttonVariants } from '@kothry/ui';
<a className={buttonVariants({ variant: 'outline', size: 'sm' })}>Anchor</a>

Button API

Extends React.ButtonHTMLAttributes<HTMLButtonElement>. Any extra props are forwarded to the underlying <button> (or the asChild element).

Prop / methodTypeDefaultDescription
variant'primary' | 'outline' | 'ghost' | 'link''primary'Visual type. link renders as bare text with no box height, padding or radius.
size'sm' | 'md' | 'lg' | 'xl''md'Height and metrics: sm 24px / md 32px / lg 40px / xl 48px. Also sizes auto-injected svg icons.
destructivebooleanfalseSwitches every variant to the error palette without changing its shape.
iconOnlybooleanfalseMakes the button square (24/32/40/48px) with no horizontal padding. Provide an aria-label.
asChildbooleanfalseMerges button styling onto the single child element (Radix Slot) — use for anchors or Next.js <Link>. The loading spinner is skipped.
loadingbooleanfalseNON-SPEC: overlays a centered spinner over invisible content (width stays fixed) and blocks re-triggering via aria-busy/aria-disabled. Not the disabled look.
classNamestringAdditional classes merged via tailwind-merge onto the root element.
...propsReact.ButtonHTMLAttributes<HTMLButtonElement>All other native button props (disabled, onClick, type, etc.) are forwarded.