---
title: Button
description: Button component with polymorphic rendering and automatic loading state.
sidebar:
  order: 0
search:
  tags: [action, click, submit, loading]
---

# Button

> Button component with polymorphic rendering and automatic loading state.

## Import

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

## Slot Structure

Trigger element with optional leading and trailing icon slots.

```text
root
├── leading (Icon, optional)
├── label (optional)
└── trailing (Icon, optional)
```

## Examples

### Variants

Visual variants for primary, secondary, subtle, and destructive actions.

```tsx
function Variants() {
  const VARIANTS: ButtonT.Variant['variant'][] = [
    'default',
    'secondary',
    'outline',
    'ghost',
    'link',
    'destructive',
  ]

  return (
    <div class="flex flex-wrap gap-3">
      <For each={VARIANTS}>{(variant) => <Button variant={variant}>{variant}</Button>}</For>
    </div>
  )
}
```

### Sizes

Text button sizes with a leading icon to preview label and icon spacing.

```tsx
function Sizes() {
  const SIZES: ButtonT.Variant['size'][] = ['xs', 'sm', 'md', 'lg', 'xl']

  return (
    <div class="flex flex-wrap gap-3 items-center">
      <For each={SIZES}>
        {(size) => (
          <Button size={size} variant="outline" leading="i-lucide:plus">
            Add item ({size})
          </Button>
        )}
      </For>
    </div>
  )
}
```

### Loading States

Controlled loading and async auto-loading from click handlers.

```tsx
function LoadingStates() {
  const [controlledLoading, setControlledLoading] = createSignal(false)
  const [customLoading, setCustomLoading] = createSignal(false)
  const [autoRuns, setAutoRuns] = createSignal(0)

  const runControlledLoading = async () => {
    setControlledLoading(true)
    await wait(1000)
    setControlledLoading(false)
  }

  const runCustomLoading = async () => {
    setCustomLoading(true)
    await wait(1200)
    setCustomLoading(false)
  }

  return (
    <div class="flex flex-wrap gap-3 items-center">
      <Button
        loading={controlledLoading()}
        onClick={runControlledLoading}
        leading="i-lucide:download"
      >
        {controlledLoading() ? 'Downloading...' : 'Download report'}
      </Button>

      <Button
        loading={customLoading()}
        loadingIcon="i-lucide:loader-circle"
        variant="outline"
        onClick={runCustomLoading}
      >
        {customLoading() ? 'Syncing...' : 'Sync workspace'}
      </Button>

      <Button
        loadingAuto
        variant="outline"
        leading="i-lucide:send"
        onClick={() => {
          return wait(2000).then(() => {
            setAutoRuns((value) => value + 1)
          })
        }}
      >
        Send invite ({autoRuns()})
      </Button>

      <Button disabled variant="ghost">
        Archive project
      </Button>
    </div>
  )
}
```

#### Loading Placement

When loading, the icon replaces `leading` first. If no `leading` is set, it replaces `trailing`.

```tsx
function LoadingPlacement() {
  return (
    <div class="flex flex-wrap gap-3 items-center">
      <Button loading>Creating project</Button>
      <Button loading trailing="i-lucide:timer">
        Scheduling publish
      </Button>
      <Button loading leading="i-lucide:download" trailing="i-lucide:arrow-right">
        Preparing download
      </Button>
    </div>
  )
}
```

### Icon Buttons

Icon-only sizes using decrease and increase actions. Always provide an accessible label when a button has no visible text.

```tsx
function IconButtons() {
  const ICON_SIZES: NonNullable<ButtonT.Variant['size']>[] = [
    'icon-xs',
    'icon-sm',
    'icon-md',
    'icon-lg',
    'icon-xl',
  ]

  return (
    <div class="flex flex-wrap gap-4 items-center">
      <For each={ICON_SIZES}>
        {(size) => (
          <div class="flex gap-1.5 items-center">
            <Button size={size} variant="outline" aria-label={`Decrease, ${size} button`}>
              <Icon name="i-lucide:minus" />
            </Button>
            <Button size={size} variant="outline" aria-label={`Increase, ${size} button`}>
              <Icon name="i-lucide:plus" />
            </Button>
          </div>
        )}
      </For>
    </div>
  )
}
```

### Polymorphic

Anchor rendering support via the polymorphic as prop.

```tsx
function Polymorphic() {
  return (
    <div class="flex flex-wrap gap-3 items-center">
      <Button as="a" href="https://www.solidjs.com" target="_blank" rel="noreferrer" variant="link">
        SolidJS docs
      </Button>
      <Button
        as="a"
        href="https://kobalte.dev"
        target="_blank"
        rel="noreferrer"
        variant="secondary"
      >
        Kobalte
      </Button>
    </div>
  )
}
```

## API Reference

### Attributes

#### `root`

Interactive button element, or the polymorphic element provided through `as`.

#### `loading`

Loading icon shown while the button is busy.

#### `leading`

Icon region before the button label.

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| aria-hidden | boolean \| string \| undefined | Hides decorative content from assistive technology. |

#### `label`

Button content region after render-prop resolution.

#### `trailing`

Icon region after the button label.

### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| as | "button" \| undefined | button | Element or component to render as. |
| children | ComponentOrElement<{ loading: boolean; }> \| undefined | — | Children of the button. Supports render function form. |
| class | ClassValue | — | Class applied to the component root or trigger element. |
| classes | ButtonT.Classes \| undefined | — | — |
| disabled | boolean \| undefined | — | Disabled state, including for non-button polymorphic roots. |
| leading | IconT.Name | — | 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 | icon-loading | Optional icon shown when `loading` is active. |
| onClick | JSX.EventHandlerUnion<ElementFor<"button">, MouseEvent> \| undefined | — | — |
| onContextMenu | JSX.EventHandlerUnion<ElementFor<"button">, MouseEvent> \| undefined | — | — |
| onKeyDown | JSX.EventHandlerUnion<ElementFor<"button">, KeyboardEvent> \| undefined | — | — |
| onPointerCancel | JSX.EventHandlerUnion<ElementFor<"button">, PointerEvent> \| undefined | — | — |
| onPointerDown | JSX.EventHandlerUnion<ElementFor<"button">, PointerEvent> \| undefined | — | — |
| onPointerLeave | JSX.EventHandlerUnion<ElementFor<"button">, PointerEvent> \| undefined | — | — |
| onPointerUp | JSX.EventHandlerUnion<ElementFor<"button">, PointerEvent> \| undefined | — | — |
| ref | JSX.HTMLElementTags["button"] extends { ref?: infer Ref; } ? Ref : never \| undefined | — | — |
| size | "xs" \| "sm" \| "md" \| "lg" \| "xl" \| "icon-xs" \| "icon-sm" \| "icon-md" \| "icon-lg" \| "icon-xl" \| undefined | — | — |
| slotName | string \| undefined | — | Root `data-slot` name |
| style | JSX.CSSProperties \| undefined | — | — |
| styles | ButtonT.Styles \| undefined | — | — |
| trailing | IconT.Name | — | Trailing visual content, usually an icon. |
| type | "button" extends "a" ? JSX.AnchorHTMLAttributes<HTMLAnchorElement>["type"] : "button" extends "button" ? JSX.ButtonHTMLAttributes<HTMLButtonElement>["type"] : "button" extends "input" ? JSX.InputHTMLAttributes<HTMLInputElement>["type"] : never \| undefined | — | Native type attribute for supported native roots. |
| variant | "link" \| "default" \| "destructive" \| "outline" \| "secondary" \| "ghost" \| undefined | — | — |

### ARIA

Accessibility attributes and roles emitted by the component markup.

| Attribute | Type | Description |
| --- | --- | --- |
| aria-busy | boolean \| string \| undefined | Accessibility attribute forwarded by the rendered component. |
| aria-disabled | boolean \| string \| undefined | Indicates that the control is disabled. |
| aria-hidden | boolean \| string \| undefined | Hides decorative content from assistive technology. |
| role | string | Defines the semantic role exposed to assistive technology. |

### Data Attributes

State and slot attributes exposed for styling hooks and selectors.

| Attribute | Type | Description |
| --- | --- | --- |
| data-disabled | string \| undefined | Present when the component or item is disabled. |
| data-loading | string \| undefined | Present when the component is loading. |
| data-size | string | Stores the resolved size variant. |
| data-slot | string | Identifies the rendered slot for styling hooks and selectors. |
| data-variant | string | Stores the resolved visual variant. |
