---
title: Button
description: Render actions and links with loading, icons, and semantic roots.
package: moraine
version: 0.6.0
repository: https://github.com/subframe7536/moraine
---

# Button

> Render actions and links with loading, icons, and semantic roots.

Use Button for actions and explicit navigation. Choose the underlying element with `as` according to the action: a button for commands and an anchor for destinations.

## Basic usage

```tsx
import { Button } from 'moraine'

export function Example() {
  return <Button>Save</Button>
}
```

## Anatomy

```text
Button [component; slot=root; <button>]
├── leading [slot]
├── label [slot]
└── trailing [slot]
```

## Usage

### Loading

Use `loading` when application state owns the pending operation. `loadingAuto` is opt-in: when enabled, a promise returned by `onClick` drives the visual loading state. Loading suppresses additional activation without applying native `disabled`, so a focused button keeps its keyboard position. Explicit `disabled` still uses native disabled semantics where available.

```tsx
import { Button } from 'moraine'
import { createSignal } from 'solid-js'

export function LoadingUsage() {
  const [loading, setLoading] = createSignal(false)

  const handleManual = () => {
    setLoading(true)
    setTimeout(() => setLoading(false), 1200)
  }

  return (
    <div class="flex flex-wrap gap-3 items-center">
      <Button loading={loading()} onClick={handleManual}>
        Controlled Loading
      </Button>
      <Button
        loadingAuto
        variant="outline"
        onClick={() => new Promise((resolve) => setTimeout(resolve, 1500))}
      >
        Auto Promise Loading
      </Button>
    </div>
  )
}
```

### Icons and polymorphism

Leading and trailing content can be icons or custom elements. Icon-only buttons need `aria-label` or another accessible name. Polymorphic roots without native link semantics receive button role and keyboard activation; anchors with `href` keep native link behavior. When a custom component forwards its root ref, Moraine resolves native button semantics from the rendered DOM element.

```tsx
import { Button } from 'moraine'

export function IconsPolymorphism() {
  return (
    <div class="flex flex-wrap gap-3 items-center">
      <Button leading="i-lucide:plus">Create Project</Button>
      <Button variant="outline" trailing="i-lucide:external-link" as="a" href="#polymorphic">
        Documentation Link
      </Button>
      <Button variant="ghost" size="xs" aria-label="Settings" leading="i-lucide:settings" />
    </div>
  )
}
```

## Examples

### Icon action with a hint

```tsx
import { Button, Icon, Tooltip } from 'moraine'
import { createSignal } from 'solid-js'

export function IconButtons() {
  const [bookmarked, setBookmarked] = createSignal(false)

  return (
    <div class="flex gap-3 items-center">
      <span class="text-sm">Release checklist</span>
      <Tooltip>
        <Tooltip.Trigger
          as={Button}
          variant="outline"
          size="icon-sm"
          aria-label={bookmarked() ? 'Remove bookmark' : 'Bookmark checklist'}
          onClick={() => setBookmarked((value) => !value)}
        >
          <Icon name={bookmarked() ? 'i-lucide:bookmark-check' : 'i-lucide:bookmark'} />
        </Tooltip.Trigger>
        <Tooltip.Content text={bookmarked() ? 'Remove bookmark' : 'Bookmark checklist'} />
      </Tooltip>
    </div>
  )
}
```

## Attributes

| Attributes | Slot | Description |
| --- | --- | --- |
| `data-disabled` | `button` | Present when the component, slot, or item is disabled. |
| `data-loading` | `button` | Present when the component or async operation is loading. |

## Props

Props for the Button component.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| disabled | boolean \| undefined | — | Disabled state, including for non-button polymorphic roots. |
| leading | IconT.Name \| undefined | — | Leading visual content, usually an icon. |
| loading | boolean \| undefined | false | Controlled loading state. |
| loadingAuto | boolean \| undefined | false | Auto toggles loading while async click handlers are pending. |
| loadingIcon | IconT.Name \| undefined | 'icon-loading' | Optional icon shown when `loading` is active. |
| size | 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg' \| 'icon-xl' \| undefined | 'md' | Visual size of the component. |
| slotName | string \| undefined | — | Root `data-slot` name |
| trailing | IconT.Name \| undefined | — | Trailing visual content, usually an icon. |
| variant | 'default' \| 'secondary' \| 'outline' \| 'ghost' \| 'link' \| 'destructive' \| undefined | 'default' | Visual treatment of the component. |
| as | T \| undefined | 'button' | Element or component to render as. |
| children | JSX.Element \| ((props: { loading: boolean }) => JSX.Element) \| undefined | — | Content or render function receiving the loading state. |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| classes | Classes \| undefined | — | Family slot class defaults for this instance. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |
| styles | Styles \| undefined | — | Family slot style defaults for this instance. |
