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-data a small object scoped to that element and its children.

  • @click and other @event handlers change the state.

  • x-show / :class / x-text react 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.

Thanks — your message has been sent.