Button

One control for every click action - six variants, four sizes, and a loading state that never shifts layout.

Button playground

Press it to watch the loading state - it holds for two seconds, then returns. The label stays mounted but invisible, so the button never changes width.

Install Button

1. New project? One command installs the package and wires everything - see Installation:

bash
pnpm dlx zyncat-ui init

2. Import the component - it loads its own stylesheet:

tsx
import { Button } from '@zyncat/ui/button';

Usage

tsx
import { Button } from '@zyncat/ui/button';
 
<Button variant="primary" onClick={schedulePost}>Schedule post</Button>

Compose Button in your application. No Tailwind or external styling library is required; every value resolves from Zyncat UI's token vocabulary.

Button props

Button

PropTypeDefaultDescription
...htmlAttributesButtonHTMLAttributes—Standard <button> attributes (onClick, name, form, aria-*, data-*, ...) forwarded verbatim. Bare `<button>` attributes also pass through directly; `htmlProps` wins on conflict.
variantButtonVariant'primary'Visual weight / intent.
refRef<HTMLButtonElement>—Forwarded to the underlying <button> (React 19 ref-as-prop).
sizeButtonSize'md'Control height. sm 28px - md 32px (default) - lg 37px.
type'button' | 'submit' | 'reset''button'`submit` / `reset` / `button`.
disabledboolean—Disable the control (also implied by `loading`).
loadingboolean—Loading - swaps the label for a spinner and makes the button inert. The label stays mounted but invisible, so the button holds its width.
fullWidthboolean—Stretch to fill the container width.
classNamestring—Extra class(es) merged onto the button.
styleCSSProperties—Inline styles merged onto the button.
childrenReactNode—Button label.

Frequently asked questions

Five: primary, secondary, ghost, danger and link. For chrome without a skin, call buttonClass() with no variant - it returns sizing, focus ring and layout only, which is how Alert's own action button is built.

Set loading - the label fades to 0 opacity so the button keeps its width, a centered spinner overlays it, aria-busy is set, and the button becomes disabled so it can't be clicked mid-request.

Use size="icon" - it renders a fixed square button, sized to match the small control height, with no horizontal padding. There's no iconLeft/iconRight prop; pass the icon directly as children, and add your own aria-label since there's no visible label text.

Yes - ref is a plain prop forwarded straight to the underlying <button> (React 19's ref-as-prop), and standard attributes like onClick, name, form or aria-* pass through directly or via htmlProps, which wins if the same attribute is set both ways.

Set in Geist & Newsreader — animated by the house engineZyncat UI · Rev 0.11 · MIT · Built by Tabsir Ahammed · Source on GitHub