Button
shadcn's button, with the same variants and sizes.
Basic usage
<Button onClick={handleClick}>Click me</Button> Label
The label is a child, not a prop:
<Button onClick={save}>Save</Button> A bare text child auto-wraps in a <Text>. Write one explicitly to style it:
<Button onClick={save}>
<Text class="font-bold">Save</Text>
</Button> Button with an icon
<Button onClick={add}>
<Icon name="lucide:plus" />
Add item
</Button>
<Button onClick={save}>
Save
<Icon name="lucide:check" />
</Button> Icon-only button
Pair an icon-only size with a single child so there is no label to lay out beside it:
<Button size="icon" onClick={handleClick}>
<Icon name="lucide:heart" />
</Button> Disabled state
var email = $state("")
var password = $state("")
val isValid = $derived(email.isNotEmpty() && password.length >= 8)
<Button disabled={!isValid} onClick={submit}>Sign In</Button> Loading state
var isLoading = $state(false)
suspend fun submit() {
isLoading = true
$fetch.post(url = "...", body = { email: email })
isLoading = false
}
<Button onClick={submit} disabled={isLoading}>
@if (isLoading) {
<Spinner size={16} />
} else {
Submit
}
</Button> See it happen
Switch between these to see how the same tag picks up a disabled expression and an async loading state — compiled live in your browser.
import $lib.components.ui.Button
<Button onClick={save}>Save</Button>A Button takes its label as a child, the way every other element does. onClick takes a function.
Variants
variant is shadcn's set: default, secondary, destructive, outline, ghost, link.
<Button>Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button> Sizes
Four text sizes and four icon-only counterparts: default, xs, sm, lg, icon, icon-xs, icon-sm, icon-lg.
<Button size="sm">Small</Button>
<Button size="lg">Large</Button>
<Button size="icon">
<Icon name="lucide:heart" />
</Button> Styling with class
A Button is styled like any other element, with Tailwind-style classes:
<Button class="w-full">Continue</Button>
<Button class="rounded-full">Pill</Button>
<Button class="bg-primary">Primary</Button> Common patterns
Primary/secondary actions
<Row class="gap-12">
<Button variant="secondary" onClick={cancel}>Cancel</Button>
<Button onClick={save}>Save</Button>
</Row> Bottom sheet actions
<Column class="w-full h-full">
<Column class="flex-1" scrollable>
Content
</Column>
<Row class="safe-bottom gap-12 p-16">
<Button variant="secondary" class="flex-1" onClick={cancel}>Cancel</Button>
<Button class="flex-1" onClick={confirm}>Confirm</Button>
</Row>
</Column> Chip-style toggle
<FlowRow class="gap-8">
@for (tag in tags) {
<Button
variant={if (selectedTags.contains(tag)) "default" else "outline"}
size="sm"
onClick={() => selectTag(tag)}
>
{tag}
</Button>
}
</FlowRow> Universal onClick
onClick works on any element, not just Button. It makes images, text, cards,
and containers clickable:
<Image src={poster} onClick={() => $navigate("/detail")} />
<Text color="primary" onClick={handleClick}>
Click me
</Text>
<Card onClick={() => $navigate("/product/${id}")}>
<Image src={image} />
{name}
</Card> Async actions
A Button's onClick can be a suspend function; the compiler launches it:
suspend fun loadData() {
items = $fetch("https://api.example.com/data")
}
<Button onClick={loadData}>Load data</Button> Navigation
A Button that navigates is still a Button. An element that only navigates is better written with href:
<Button onClick={() => $navigate("/profile")}>View profile</Button>
<Row href="/product/${productId}">Details</Row> Prop reference
| Prop | Type | Description |
|---|---|---|
onClick | Function | Click handler; a suspend function is launched for you |
disabled | Boolean | Bare means true; no enabled prop |
variant | String | default · secondary · destructive · outline · ghost · link |
size | String | default · xs · sm · lg · icon · icon-xs · icon-sm · icon-lg |
class | String | Tailwind-style classes for width, color, radius, spacing |