Interactivity
Carousels, tabs, modals: whatever you can think up.
Alpine.js is a lightweight JavaScript framework that's included on your site. Use it to trigger animations and toggle state. It can do most of the things that React can do at a block level.
Animating things in? Entrance animations (fade up, slide in, staggered reveals) have their own tool: the animation partial. See Animation.
State with Alpine
Use x-data to manage the state of your block. Use @click to adjust it and toggle things with x-show. You can do a lot with just these three tools.
x-dataa small object scoped to that element and its children.@clickand other@eventhandlers change the state.x-show/:class/x-textreact to the state.
<div x-data="{ open: false }">
<button @click="open = !open">
Toggle Details
</button>
<div x-show="open" x-transition>
{{details}}
</div>
</div>
Tabs
<div x-data="{ tab: 'features' }">
<div class="flex gap-4">
<button
@click="tab = 'features'"
:class="tab === 'features' ? 'border-b-2' : ''">
Features
</button>
<button
@click="tab = 'pricing'"
:class="tab === 'pricing' ? 'border-b-2' : ''">
Pricing
</button>
</div>
<div x-show="tab === 'features'">
{{features-content}}
</div>
<div x-show="tab === 'pricing'">
{{pricing-content}}
</div>
</div>
Accordion / disclosure
{{#each faqs}}
<div x-data="{ open: false }" class="border-b border-border">
<button @click="open = !open" class="flex w-full justify-between py-4">
<span class="heading-sm">{{question}}</span>
<span x-text="open ? '–' : '+'"></span>
</button>
<div x-show="open" x-transition>
<p class="body-md text-fg-muted pb-4">{{answer}}</p>
</div>
</div>
{{/each}}
Each item gets its own x-data, so they open and close independently.
Modal / dropdown
<div x-data="{ open: false }" @keydown.escape.window="open = false">
<button @click="open = true">Open menu</button>
<div
x-show="open"
@click.outside="open = false"
x-transition
class="absolute mt-2 bg-base border border-border rounded-lg">
<!-- menu contents -->
</div>
</div>
@click.outside and @keydown.escape.window are Alpine modifiers.
Key Directives
x-data="{ key: value }" <!-- Initialize state -->
x-show="condition" <!-- Show/hide (keeps in DOM) -->
x-if="condition" <!-- Conditional; only on <template> elements -->
x-text="expression" <!-- Set text content -->
x-html="expression" <!-- Set HTML content -->
x-model="variable" <!-- Two-way form binding -->
@click="handler" <!-- Event listener (@submit, @keydown, …) -->
:class="expression" <!-- Dynamic classes -->
x-transition <!-- Smooth show/hide transition -->
Tips
Start static, add interactivity later. Get your block working with plain HTML and Handlebars first. Add Alpine only where you need the page to respond.
Watch x-data inside loops. When you put x-data inside an {{#each}}, every item gets its own independent state, which is usually exactly what you want for accordions and cards. Put x-data outside the loop when items should share one state (like the tabs example).
x-show vs x-if. x-show toggles visibility but keeps the element in the DOM (cheaper to flip, good for things shown often). x-if removes it entirely (good for heavy content you rarely show), and only works on a <template> element.