Button

import { Button, buttonVariants } from '@hanzo/ui'

Variants

Every visual variant, from the filled default to the bare link.

export function Variants() {
return (
<XStack flexWrap="wrap" gap="$3" items="center">
<Button>Default</Button>
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="link">Link</Button>
</XStack>
)
}

Sizes

Three text sizes and a square icon size for each of them.

export function Sizes() {
return (
<XStack flexWrap="wrap" gap="$3" items="center">
<Button size="sm">Small</Button>
<Button>Default</Button>
<Button size="lg">Large</Button>
<Button size="icon-sm" aria-label="Small icon">
+
</Button>
<Button size="icon" aria-label="Icon">
+
</Button>
<Button size="icon-lg" aria-label="Large icon">
+
</Button>
</XStack>
)
}

States

A button that is loading shows a spinner and takes no clicks; a disabled one takes none and says so.

export function States() {
return (
<XStack flexWrap="wrap" gap="$3" items="center">
<Button isLoading>Saving</Button>
<Button disabled>Disabled</Button>
<Button variant="outline" disabled>
Disabled outline
</Button>
</XStack>
)
}

As a link

`asChild` styles the element you pass instead of rendering its own button.

export function AsChild() {
return (
<Button asChild variant="outline">
<a href="https://github.com/hanzoai/ui">Source on GitHub</a>
</Button>
)
}

Types

export type ButtonVariant =
| 'default'
| 'destructive'
| 'outline'
| 'secondary'
| 'ghost'
| 'link'
| 'primary'
| 'linkFG'
| 'linkMuted'
export type ButtonSize = 'default' | 'sm' | 'lg' | 'icon' | 'icon-sm' | 'icon-lg'
export type ButtonProps = Omit<
ComponentProps<typeof Frame>,
'variant' | 'size' | 'children' | 'onClick'
> & {
variant?: ButtonVariant | null
size?: ButtonSize | null
isLoading?: boolean
children?: ReactNode
/**
* The DOM click. Frame types this from the cross-platform stack, where the
* event carries `currentTarget: number | ReactNativeElement` — so a handler
* written the way every web caller writes it, `(e: React.MouseEvent) => …`,
* is a type error on a prop that works perfectly.
*
* On web the handler IS a DOM handler: props spread onto Frame, which renders
* a real <button>, and React hands it a MouseEvent. Declaring what actually
* arrives is the same trade as `title` below, and for the same reason — the
* alternative is call sites re-declaring the component as `any`, which hides
* every real error in the file along with this one.
*/
onClick?: MouseEventHandler<HTMLElement>
/**
* The DOM tooltip. Already reached the element -- every unrecognised prop is
* spread onto Frame, which forwards it -- but the Frame's props come from the
* cross-platform stack and name no DOM attribute, so passing it did not type.
* Callers either dropped the tooltip or re-declared the whole component as
* `any`, and the second hides every real error in the file.
*
* Declared here because it is true on web and harmless elsewhere: native
* ignores an attribute it does not know. Widening the type to match the
* behaviour beats asking ~60 call sites to work around it.
*/
title?: string
/**
* The DOM button type. Not cosmetic: inside a form the default is "submit",
* so a control meant to do something else submits the form instead. Callers
* write type="button" to stop that, and dropping it would be a real bug
* rather than a lost attribute. Reaches the element the same way title does.
*/
type?: 'button' | 'submit' | 'reset'
/**
* How the button identifies itself to the form it submits.
*
* A submitter contributes its own `name`/`value` pair to the form data, which
* is how one form tells "save" from "save and close" — so dropping these is
* losing which button was pressed, not losing an attribute. They reach the
* element the same way `type` does.
*/
name?: string
value?: string
} & Microdata

Exports

ButtonbuttonVariantstype ButtonPropstype ButtonSizetype ButtonVariant