Kern GitHub

Button

Polymorphic button with intent and size variants.

Intents

1{{ partial:components/primitives/button label="Primary" /}}
2{{ partial:components/primitives/button label="Secondary" intent="secondary" /}}
3{{ partial:components/primitives/button label="Destructive" intent="destructive" /}}
4{{ partial:components/primitives/button label="Ghost" intent="ghost" /}}
5{{ partial:components/primitives/button label="Link" intent="link" /}}

Sizes

1{{ partial:components/primitives/button label="Small" size="sm" /}}
2{{ partial:components/primitives/button label="Default" /}}
3{{ partial:components/primitives/button label="Large" size="lg" /}}
4{{ partial:components/primitives/button icon_before="star" size="icon" attrs="aria-label=Favorite" /}}

States

Disabled Link
1{{ partial:components/primitives/button label="Disabled" disabled="true" /}}
2{{ partial:components/primitives/button label="Disabled Ghost" intent="ghost" disabled="true" /}}
3{{ partial:components/primitives/button label="Loading" loading="true" /}}
4{{ partial:components/primitives/button label="Disabled Link" href="#" disabled="true" /}}
5{{ partial:components/primitives/button label="Disabled Button" attrs="disabled" /}}

Icons

1{{ partial:components/primitives/button label="Icon before" icon_before="star" intent="secondary" /}}
2{{ partial:components/primitives/button label="Icon after" icon_after="arrow-forward" intent="ghost" /}}
3{{ partial:components/primitives/button label="Both icons" icon_before="star" icon_after="arrow-forward" /}}

Slots & Polymorphism

As Link As Span
1{{ partial:components/primitives/button intent="secondary" }}
2 {{ svg src="icons/star" class="size-4 shrink-0" aria-hidden="true" }}
3 Rich slot content
4{{ /partial:components/primitives/button }}
5{{ partial:components/primitives/button intent="ghost" }}
6 {{ slot:attrs }}
7 aria-label="Button with attrs slot" data-test-id="button-slot-attrs"
8 {{ /slot:attrs }}
9 With attrs slot
10{{ /partial:components/primitives/button }}
11{{ partial:components/primitives/button label="As Link" href="/" /}}
12{{ partial:components/primitives/button label="As Span" as="span" class="border border-border" /}}

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 button element
intent string primary Visual intent: primary|secondary|destructive|ghost|link
size string default Size variant: sm|default|lg|icon
disabled boolean Disabled state
loading boolean Loading state with spinner indicator
icon_before string Icon name rendered before the label (uses {{ svg }})
icon_after string Icon name rendered after the label (uses {{ svg }})
class string Additional classes merged via tw_merge (root element only)
attrs string Raw root attributes, simple payloads (e.g. attrs="disabled"); use slot:attrs when the payload needs nested quotes

Slots

Name Fallback / Default Description
attrs Raw root attributes for payloads a tag param cannot hold: nested quotes, multi-line Alpine bindings

Source

1{{#
2 @name Button
3 @desc Polymorphic button with intent and size variants.
4 @param label string - Button label text (falls back to default slot)
5 @param as string [button] - HTML element when href is not provided
6 @param href string - If present, renders as an anchor
7 @param target string - Anchor target attribute (_self, _blank, ...)
8 @param type string [button] - Button type attribute when rendering as button element
9 @param intent string [primary] - Visual intent: primary|secondary|destructive|ghost|link
10 @param size string [default] - Size variant: sm|default|lg|icon
11 @param disabled boolean [false] - Disabled state
12 @param loading boolean [false] - Loading state with spinner indicator
13 @param icon_before string - Icon name rendered before the label (uses {{ svg }})
14 @param icon_after string - Icon name rendered after the label (uses {{ svg }})
15 @param class string - Additional classes merged via tw_merge (root element only)
16 @param attrs string - Raw root attributes, simple payloads (e.g. attrs="disabled"); use slot:attrs when the payload needs nested quotes
17 @slot attrs - Raw root attributes for payloads a tag param cannot hold: nested quotes, multi-line Alpine bindings
18#}}
19{{# format-ignore-start #}}
20{{ _el = href ? 'a' : (as ?? 'button') }}
21
22{{ _class_loading = loading ?= 'cursor-wait' }}
23{{ _base = '
24 inline-flex shrink-0 items-center justify-center gap-2
25 cursor-pointer border-2 border-transparent
26 font-medium whitespace-nowrap transition-colors
27 focus-visible:outline-focus focus-visible:border-focus-text focus-visible:outline focus-visible:outline-4
28 active:bg-focus active:text-focus-text active:border-focus
29 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50
30 aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50
31' }}
32
33{{ _intents = [
34 'primary' => 'bg-primary text-primary-foreground border-primary rounded-full hover:opacity-85',
35 'secondary' => 'bg-secondary text-secondary-foreground border-secondary rounded-full hover:opacity-85',
36 'destructive' => 'bg-destructive text-destructive-foreground border-destructive rounded-full hover:opacity-85',
37 'ghost' => 'text-foreground rounded-full border-transparent hover:border-current',
38 'link' => 'text-primary focus-visible:bg-focus focus-visible:text-focus-text border-transparent underline decoration-1 underline-offset-[0.15em] hover:decoration-[3px] focus-visible:border-transparent focus-visible:no-underline focus-visible:[box-shadow:0_4px_var(--color-focus-text)] focus-visible:outline-none',
39] }}
40
41{{ _sizes = [
42 'sm' => 'h-8 px-3 text-sm',
43 'default' => 'h-10 px-4 text-sm',
44 'lg' => 'h-12 px-6 text-base',
45 'icon' => 'size-10 p-0',
46] }}
47
48{{ _class_intent = _intents[intent] ?? _intents['primary'] }}
49{{ _class_size = _sizes[size] ?? _sizes['default'] }}
50
51{{# format-ignore-end #}}
52<{{ _el }}
53 class="{{ '{_base} {_class_intent} {_class_size} {_class_loading} {class}' | tw_merge }}"
54 {{ if _el == 'a' }}
55 href="{{ href }}"
56 {{ if target }}target="{{ target }}"{{ /if }}
57 {{ if target == '_blank' }}rel="noopener noreferrer"{{ /if }}
58 {{ if disabled }}
59 aria-disabled="true"
60 tabindex="-1"
61 {{ /if }}
62 {{ elseif _el == 'button' }}
63 type="{{ type ?? 'button' }}"
64 {{ if disabled }}disabled{{ /if }}
65 {{ else }}
66 {{ if disabled }}aria-disabled="true"{{ /if }}
67 {{ /if }}
68 {{ if loading }}aria-busy="true"{{ /if }}
69 {{ attrs }}
70 {{ slot:attrs }}
71>
72 {{ if loading }}
73 <span class="animate-flip size-4 bg-current" aria-hidden="true"></span>
74 {{ elseif icon_before }}
75 {{ svg src="icons/{icon_before}" class="size-4 shrink-0" aria-hidden="true" }}
76 {{ /if }}
77 {{ label ?? slot }}
78 {{ if icon_after && !loading }}
79 {{ svg src="icons/{icon_after}" class="size-4 shrink-0" aria-hidden="true" }}
80 {{ /if }}
81</{{ _el }}>

Dependencies

Packages

1composer require marcorieser/tailwind-merge-statamic