Sprig Logo Sprig Cookbook

Copy-paste recipes for Craft sites.

Sprig allows you to create reactive components from Twig templates. Below are some recipes to get you up and running with Sprig. 

View the full Sprig docs »

This component refreshes the results whenever the input field detects a keyup that changes its value. 

🌿 Ingredients: s‑trigger, s‑target, s‑swap.

🌱 Tip: start by typing s”.

{% set query = query ?? '' %}

{% if sprig.include %}
  <input sprig s-trigger="keyup changed" s-target="#results" s-swap="outerHTML" 
    type="text" name="query" value="{{ query }}" placeholder="Search">
{% endif %}

<div id="results">
  {% if query %}
    {% set entries = craft.entries.search(query).all() %}
    {% if entries|length %}
      {% for entry in entries %}
        <a href="{{ entry.url }}">{{ entry.title }}</a>
      {% endfor %}
    {% else %}
        No results
    {% endif %}
  {% endif %}
</div>

Load More #

This component loads another entry each time the button is clicked.

🌿 Ingredients: s‑target, s‑swap.

🌱 Tip: can you figure out what will happen when all entries are loaded?

Blitz

{% set offset = offset ?? 0 %}
{% set limit = limit ?? 1 %}

{% set entries = craft.entries.offset(offset).limit(limit).all() %}

{% for entry in entries %}
  <h5>{{ entry.title }}</h5>
{% endfor %}

{% if entries %}
  <div id="replace">
    <input type="hidden" name="offset" value="{{ offset + 1 }}">
    <button sprig s-target="#replace" s-swap="outerHTML">
      Load another
    </button>
  </div>
{% endif %}

Pagination #

This component loads the previous or next entry depending on which button is clicked. It keeps the query string in the URL updated and even remembers the page number when the browser window is refreshed.

🌿 Ingredients: s‑vars, s‑push-url.

🌱 Tip: bring your own Twig 🎉.

Blitz
Campaign
Sherlock

Showing 1-3 of 15 entries.
Page 1 of 5 pages.

1 2 3 4 5

{% set limit = limit ?? 3 %}
{% set page = page ?? 1 %}
{% set offset = (page - 1) * limit %}

{% set query = craft.entries.section('plugins').offset(offset).limit(limit) %}
{% set entries = query.all() %}
{% set totalEntries = query.count() %}
{% set totalPages = (totalEntries / limit)|round(0, 'ceil') %}

{% for entry in entries %}
  <h6>{{ entry.title }}</h6>
{% endfor %}

{% if entries %}
  {% if page > 1 %}
    <button sprig s-vars="page: {{ page - 1 }}" s-push-url="?page={{ page - 1 }}">
      Previous
    </button>
  {% endif %}
  {% if page < totalPages %}
    <button sprig s-vars="page: {{ page + 1 }}" s-push-url="?page={{ page + 1 }}">
      Next
    </button>
  {% endif %}
  <p>
    <em>
      Showing {{ offset + 1 }}-{{ offset + entries|length }} 
      of {{ totalEntries }} entries.
    </em><br>
    <em>Page {{ page }} of {{ totalPages }} pages.</em><br>
    {% for i in 1..totalPages %}
      {% if i == page %}
        {{ i }}
      {% else %}
        <a sprig s-vars="page: {{ i }}" s-push-url="?page={{ i }}">{{ i }}</a>
      {% endif %}
    {% endfor %}
  </p>
{% endif %}

Polling #

This component polls for the current time every 3 seconds or when the timezone is changed (the default trigger for a select element).

🌿 Ingredients: s‑trigger.

🌱 Tip: explore all the available triggers.

The time is 05:24:49 CEST


{% set timezone = timezone ?? 'Europe/Vienna' %}

<div sprig s-trigger="every 3s" class="pulse btn-blue">
    The time is {{ now|time('long', timezone=timezone) }}
</div>

<select sprig name="timezone">
    <option value="US/Pacific" {{ timezone == 'US/Pacific' ? 'selected' }}>US/Pacific</option>
    <option value="America/Buenos_Aires" {{ timezone == 'America/Buenos_Aires' ? 'selected' }}>America/Buenos_Aires</option>
    <option value="Europe/Vienna" {{ timezone == 'Europe/Vienna' ? 'selected' }}>Europe/Vienna</option>
    <option value="Asia/Hong_Kong" {{ timezone == 'Asia/Hong_Kong' ? 'selected' }}>Asia/Hong_Kong</option>
    <option value="Australia/Sydney" {{ timezone == 'Australia/Sydney' ? 'selected' }}>Australia/Sydney</option>
</select>

Protected Variables #

This component outputs the entries in one of a protected set of sections. Since the _allowedSections variable begins with an underscore, it cannot be tampered with.

🌿 Ingredients: s‑vars.

🌱 Tip: inspect the component source code (in your browser’s developer tools) to see how protected variables are hashed to prevent tampering. 

A Technical Rundown of How Project Config Works
Add-on Support Visualised
All Add-on Income Donated On 13.12.11
Amazon SES
Another Year, Another Redesign

{{ sprig('_components/section-entries', {
  _allowedSections: 'plugins, articles'
}) }}
{% set section = section ?? _allowedSections %}

<button sprig 
  class="{{ section == _allowedSections ? 'active' }}">All</button>
<button sprig s-vars="section: 'plugins'" 
  class="{{ section == 'plugins' ? 'active' }}">Plugins</button>
<button sprig s-vars="section: 'articles'" 
  class="{{ section == 'articles' ? 'active' }}">Articles</button>
<button sprig s-vars="section: 'secrets'" 
  class="{{ section == 'secrets' ? 'active' }}">Secrets</button>

{% if section in _allowedSections %}

    {% set entries = craft.entries.section(section).limit(5).all() %}

    {% for entry in entries %}
        <h6>{{ entry.title }}</h6>
    {% endfor %}

{% else %}

    <p class="error">The section “{{ section }}” is not in the allowed sections.</p>

{% endif %}

Entry Form #

This component submits an entry form with back-end validation.

🌿 Ingredients: s‑method, s‑action.

🌱 Tip: the entries controller returns a JSON response which is loaded into the component.

{% set title = title ?? '' %}

{% if success is defined %}

  <p>The entry "{{ title }}" was successfully created!</p>
  <button sprig>Start Over</button>

{% else %}

  {% if errors is defined %}
    {% for error in errors %}
      <p class="error">{{ error|join(', ') }}</p>
    {% endfor %}
  {% endif %}

  <form sprig s-method="post" s-action="entries/save-entry">
      <input type="hidden" name="sectionId" value="1">
      <input type="text" name="title" value="{{ title }}" placeholder="Title">
      <button type="submit">Create Entry</button>
  </form>

{% endif %}

Contact Form #

This component submits a contact form with back-end validation.

🌿 Ingredients: s‑method, s‑action.

🌱 Tip: the send controller returns a JSON response which is loaded into the component.

{% set name = name ?? '' %}
{% set email = email ?? '' %}
{% set message = message ?? '' %}

{% if success is defined %}

  <p>Your message was successfully sent!</p>

{% else %}

  {% if errors is defined %}
    {% for error in errors %}
      <p class="error">{{ error|join(', ') }}</p>
    {% endfor %}
  {% endif %}

  <form sprig s-method="post" s-action="contact-form/send">
      <input type="text" name="name" value="{{ name }}" placeholder="Name">
      <input type="email" name="email" value="{{ email }}" placeholder="Email">
      <textarea name="message" placeholder="Message">{{ message }}</textarea>
      <button type="submit">Send Message</button>
  </form>

{% endif %}