Button
Polymorphic button with intent and size variants.
Examples
Intents
<div class="flex flex-wrap gap-3">
{{ partial:components/primitives/button label="Primary" intent="primary" }}
{{ partial:components/primitives/button label="Secondary" intent="secondary" }}
{{ partial:components/primitives/button label="Destructive" intent="destructive" }}
{{ partial:components/primitives/button label="Ghost" intent="ghost" }}
{{ partial:components/primitives/button label="Link" intent="link" }}
</div>
<div class="flex flex-wrap gap-3">
{{ partial:components/primitives/button label="Primary" intent="primary" }}
{{ partial:components/primitives/button label="Secondary" intent="secondary" }}
{{ partial:components/primitives/button label="Destructive" intent="destructive" }}
{{ partial:components/primitives/button label="Ghost" intent="ghost" }}
{{ partial:components/primitives/button label="Link" intent="link" }}
</div>
Sizes
<div class="flex flex-wrap items-center gap-3">
{{ partial:components/primitives/button label="Small" size="sm" }}
{{ partial:components/primitives/button label="Default" size="default" }}
{{ partial:components/primitives/button label="Large" size="lg" }}
{{ partial:components/primitives/button label="★" size="icon" class="text-lg" attributes="aria-label=icon-button" }}
</div>
<div class="flex flex-wrap items-center gap-3">
{{ partial:components/primitives/button label="Small" size="sm" }}
{{ partial:components/primitives/button label="Default" size="default" }}
{{ partial:components/primitives/button label="Large" size="lg" }}
{{ partial:components/primitives/button label="★" size="icon" class="text-lg" attributes="aria-label=icon-button" }}
</div>
States
<div class="flex flex-wrap items-center gap-3">
{{ partial:components/primitives/button label="Disabled" disabled="true" }}
{{ partial:components/primitives/button label="Disabled Ghost" intent="ghost" disabled="true" }}
{{ partial:components/primitives/button label="Loading" loading="true" }}
{{ partial:components/primitives/button label="Disabled Link" href="#" disabled="true" }}
</div>
<div class="flex flex-wrap items-center gap-3">
{{ partial:components/primitives/button label="Disabled" disabled="true" }}
{{ partial:components/primitives/button label="Disabled Ghost" intent="ghost" disabled="true" }}
{{ partial:components/primitives/button label="Loading" loading="true" }}
{{ partial:components/primitives/button label="Disabled Link" href="#" disabled="true" }}
</div>
Slots
As Link
As Span
<div class="flex flex-wrap items-center gap-3">
{{ partial:components/primitives/button label="With before slot" intent="secondary" }}
{{ slot:before }}
<span aria-hidden="true">★</span>
{{ /slot:before }}
{{ /partial:components/primitives/button }}
{{ partial:components/primitives/button label="With after slot" intent="ghost" }}
{{ slot:after }}
<span aria-hidden="true">→</span>
{{ /slot:after }}
{{ /partial:components/primitives/button }}
{{ partial:components/primitives/button label="As Link" href="/" target="_self" }}
{{ partial:components/primitives/button label="As Span" as="span" class="border border-border" }}
</div>
<div class="flex flex-wrap items-center gap-3">
{{ partial:components/primitives/button label="With before slot" intent="secondary" }}
{{ slot:before }}
<span aria-hidden="true">★</span>
{{ /slot:before }}
{{ /partial:components/primitives/button }}
{{ partial:components/primitives/button label="With after slot" intent="ghost" }}
{{ slot:after }}
<span aria-hidden="true">→</span>
{{ /slot:after }}
{{ /partial:components/primitives/button }}
{{ partial:components/primitives/button label="As Link" href="/" target="_self" }}
{{ partial:components/primitives/button label="As Span" as="span" class="border border-border" }}
</div>
Props
| Name | Type | Default | Description |
|---|---|---|---|
label
|
string
|
|
Button label text (falls back to default slot) |
as
|
string
|
button
|
HTML element when href is not provided |
href
|
string
|
|
If present, renders as an anchor |
target
|
string
|
|
Anchor target attribute (_self, _blank, ...) |
type
|
string
|
button
|
Button type attribute when rendering as |
intent
|
string
|
primary
|
Visual intent: primary|secondary|destructive|ghost|link |
size
|
string
|
default
|
Size variant: sm|default|lg|icon |
disabled
|
boolean
|
false
|
Disabled state |
loading
|
boolean
|
false
|
Loading state with spinner indicator |
class
|
string
|
|
Additional classes merged via tw_merge (root element only) |
attributes
|
string
|
|
Additional raw HTML attributes passed to the root element |
Source
{{#
@name Button
@desc Polymorphic button with intent and size variants.
@param label string - Button label text (falls back to default slot)
@param as string [button] - HTML element when href is not provided
@param href string - If present, renders as an anchor
@param target string - Anchor target attribute (_self, _blank, ...)
@param type string [button] - Button type attribute when rendering as <button>
@param intent string [primary] - Visual intent: primary|secondary|destructive|ghost|link
@param size string [default] - Size variant: sm|default|lg|icon
@param disabled boolean [false] - Disabled state
@param loading boolean [false] - Loading state with spinner indicator
@param class string - Additional classes merged via tw_merge (root element only)
@param attributes string - Additional raw HTML attributes passed to the root element
#}}
{{ _element = as ?? 'button' }}
{{ if href }}
{{ _element = 'a' }}
{{ /if }}
{{ _intent_key = intent ?? 'primary' }}
{{ _size_key = size ?? 'default' }}
{{ _intents = ['primary' => 'bg-primary text-primary-foreground hover:bg-primary/90', 'secondary' => 'bg-secondary text-secondary-foreground hover:bg-secondary/80', 'destructive' => 'bg-destructive text-destructive-foreground hover:bg-destructive/90', 'ghost' => 'text-foreground hover:bg-secondary', 'link' => 'text-primary underline-offset-4 hover:text-primary/80 hover:underline', ] }}
{{ _sizes = ['sm' => 'h-8 px-3 text-sm', 'default' => 'h-10 px-4 text-sm', 'lg' => 'h-12 px-6 text-base', 'icon' => 'size-10 p-0', ] }}
{{ _base_classes = 'inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-default font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50' }}
{{ _intent_class = _intents[_intent_key] ?? _intents['primary'] }}
{{ _size_class = _sizes[_size_key] ?? _sizes['default'] }}
{{ _loading_class = '' }}
{{ if loading }}
{{ _loading_class = 'cursor-wait' }}
{{ /if }}
{{ _classes = '{_base_classes} {_intent_class} {_size_class} {_loading_class} {class}'
| tw_merge }}
{{ _label = label ?? slot }}
<{{ _element }}
class="{{ _classes }}"
{{ if _element == 'a' }}
href="{{ href }}"
{{ if target }}target="{{ target }}"{{ /if }}
{{ if target == '_blank' }}rel="noopener noreferrer"{{ /if }}
{{ if disabled }}
aria-disabled="true"
tabindex="-1"
{{ /if }}
{{ /if }}
{{ if _element == 'button' }}
type="{{ type ?? 'button' }}"
{{ if disabled }}disabled{{ /if }}
{{ /if }}
{{ if _element != 'a' }}
{{ if _element != 'button' }}
{{ if disabled }}aria-disabled="true"{{ /if }}
{{ /if }}
{{ /if }}
{{ if loading }}aria-busy="true"{{ /if }}
{{ attributes }}
>
{{ if loading }}
<span
class="size-4 animate-spin rounded-full border-2 border-current border-r-transparent"
aria-hidden="true"
></span>
{{ else }}
{{ if slot:before }}
{{ slot:before }}
{{ /if }}
{{ /if }}
{{ if _label }}
<span>{{ _label }}</span>
{{ /if }}
{{ if loading }}
{{ else }}
{{ if slot:after }}
{{ slot:after }}
{{ /if }}
{{ /if }}
</{{ _element }}>
{{#
@name Button
@desc Polymorphic button with intent and size variants.
@param label string - Button label text (falls back to default slot)
@param as string [button] - HTML element when href is not provided
@param href string - If present, renders as an anchor
@param target string - Anchor target attribute (_self, _blank, ...)
@param type string [button] - Button type attribute when rendering as <button>
@param intent string [primary] - Visual intent: primary|secondary|destructive|ghost|link
@param size string [default] - Size variant: sm|default|lg|icon
@param disabled boolean [false] - Disabled state
@param loading boolean [false] - Loading state with spinner indicator
@param class string - Additional classes merged via tw_merge (root element only)
@param attributes string - Additional raw HTML attributes passed to the root element
#}}
{{ _element = as ?? 'button' }}
{{ if href }}
{{ _element = 'a' }}
{{ /if }}
{{ _intent_key = intent ?? 'primary' }}
{{ _size_key = size ?? 'default' }}
{{ _intents = ['primary' => 'bg-primary text-primary-foreground hover:bg-primary/90', 'secondary' => 'bg-secondary text-secondary-foreground hover:bg-secondary/80', 'destructive' => 'bg-destructive text-destructive-foreground hover:bg-destructive/90', 'ghost' => 'text-foreground hover:bg-secondary', 'link' => 'text-primary underline-offset-4 hover:text-primary/80 hover:underline', ] }}
{{ _sizes = ['sm' => 'h-8 px-3 text-sm', 'default' => 'h-10 px-4 text-sm', 'lg' => 'h-12 px-6 text-base', 'icon' => 'size-10 p-0', ] }}
{{ _base_classes = 'inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-default font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50' }}
{{ _intent_class = _intents[_intent_key] ?? _intents['primary'] }}
{{ _size_class = _sizes[_size_key] ?? _sizes['default'] }}
{{ _loading_class = '' }}
{{ if loading }}
{{ _loading_class = 'cursor-wait' }}
{{ /if }}
{{ _classes = '{_base_classes} {_intent_class} {_size_class} {_loading_class} {class}'
| tw_merge }}
{{ _label = label ?? slot }}
<{{ _element }}
class="{{ _classes }}"
{{ if _element == 'a' }}
href="{{ href }}"
{{ if target }}target="{{ target }}"{{ /if }}
{{ if target == '_blank' }}rel="noopener noreferrer"{{ /if }}
{{ if disabled }}
aria-disabled="true"
tabindex="-1"
{{ /if }}
{{ /if }}
{{ if _element == 'button' }}
type="{{ type ?? 'button' }}"
{{ if disabled }}disabled{{ /if }}
{{ /if }}
{{ if _element != 'a' }}
{{ if _element != 'button' }}
{{ if disabled }}aria-disabled="true"{{ /if }}
{{ /if }}
{{ /if }}
{{ if loading }}aria-busy="true"{{ /if }}
{{ attributes }}
>
{{ if loading }}
<span
class="size-4 animate-spin rounded-full border-2 border-current border-r-transparent"
aria-hidden="true"
></span>
{{ else }}
{{ if slot:before }}
{{ slot:before }}
{{ /if }}
{{ /if }}
{{ if _label }}
<span>{{ _label }}</span>
{{ /if }}
{{ if loading }}
{{ else }}
{{ if slot:after }}
{{ slot:after }}
{{ /if }}
{{ /if }}
</{{ _element }}>
Dependencies
Packages
marcorieser/tailwind-merge-statamic
composer require marcorieser/tailwind-merge-statamic
composer require marcorieser/tailwind-merge-statamic