Documentation

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.

SubmitButton.wh
import $lib.components.ui.Button

<Button onClick={save}>Save</Button>
Kotlin waiting
Scroll into view to compile.

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

PropTypeDescription
onClickFunctionClick handler; a suspend function is launched for you
disabledBooleanBare means true; no enabled prop
variantStringdefault · secondary · destructive · outline · ghost · link
sizeStringdefault · xs · sm · lg · icon · icon-xs · icon-sm · icon-lg
classStringTailwind-style classes for width, color, radius, spacing

See also

  • Inputs — TextField and Checkbox for forms
  • Classes — the Tailwind-style class table