Kern Component Library GitHub

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>

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>

States

Disabled Link
<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>

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 }}>

Dependencies

Packages

marcorieser/tailwind-merge-statamic

composer require marcorieser/tailwind-merge-statamic