icon

Morainev0.5.0

Button
elementsbutton

Button

Button component with polymorphic rendering and automatic loading state.

Import#

import { Button } from 'moraine'

Slot Structure#

Trigger element with optional leading and trailing icon slots.

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

Examples#

Variants#

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

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.

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.

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.

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.

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.

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#

Slotroot0 attributes
Interactive button element, or the polymorphic element provided through `as`.
No attribute metadata for this slot.

Props#

PropTypeDefaultDescription
as"button" | undefinedbutton
Element or component to render as.
childrenComponentOrElement<{ loading: boolean; }> | undefined
Children of the button. Supports render function form.
classClassValue
Class applied to the component root or trigger element.
classesButtonT.Classes | undefined
disabledboolean | undefined
Disabled state, including for non-button polymorphic roots.
leadingIconT.Name
Leading visual content, usually an icon.
loadingboolean | undefinedfalse
Controlled loading state.
loadingAutoboolean | undefinedfalse
Auto toggles loading while async click handlers are pending.
loadingIconIconT.Nameicon-loading
Optional icon shown when `loading` is active.
onClickJSX.EventHandlerUnion<ElementFor<"button">, MouseEvent> | undefined
onContextMenuJSX.EventHandlerUnion<ElementFor<"button">, MouseEvent> | undefined
onKeyDownJSX.EventHandlerUnion<ElementFor<"button">, KeyboardEvent> | undefined
onPointerCancelJSX.EventHandlerUnion<ElementFor<"button">, PointerEvent> | undefined
onPointerDownJSX.EventHandlerUnion<ElementFor<"button">, PointerEvent> | undefined
onPointerLeaveJSX.EventHandlerUnion<ElementFor<"button">, PointerEvent> | undefined
onPointerUpJSX.EventHandlerUnion<ElementFor<"button">, PointerEvent> | undefined
refJSX.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
slotNamestring | undefined
Root `data-slot` name
styleJSX.CSSProperties | undefined
stylesButtonT.Styles | undefined
trailingIconT.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