Accordion
Purpose: Collapse and expand content sections on click, with two variants — a lightweight local-state accordion and a lazy-loading variant that fetches content from a view the first time it opens.
Why Accordions?
Long detail pages benefit from hiding secondary content behind a click. The Accordion system provides:
- A base
accordion.htmlwith Alpine.js-driven show/hide and a smooth CSS transition - An
accordion_child_toggleblock for cases where a button inside the header area drives the toggle, not the whole header row - A
view_glue_accordion.htmlthat lazily fetches HTML from a server URL on first open, then toggles locally on subsequent clicks - A shared
show_accordionstate variable available in all blocks so child templates can bind icons, classes, and text to open/closed state
Quick Start
1. Extend the Base Accordion
Create a template that extends accordion.html and fills the two required blocks:
{% extends 'django_spire/accordion/accordion.html' %}
{% block accordion_toggle %}
<div class="d-flex justify-content-between align-items-center px-3 py-2">
<span class="fw-semibold">Project Overview</span>
<i class="bi" :class="show_accordion ? 'bi-chevron-up' : 'bi-chevron-down'"></i>
</div>
{% endblock %}
{% block accordion_content %}
<div class="px-3 py-2 border-top">
Your accordion content goes here.
</div>
{% endblock %}
2. Include It in a Page
Clicking the toggle area expands and collapses the content with a smooth transition.
Core Concepts
accordion.html
The base accordion. Manages a single boolean show_accordion state with Alpine.js. The toggle area wraps a @click="toggle()" handler, and the content area uses x-show and x-transition.
Template path:
| Block | Required | Description |
|---|---|---|
accordion_toggle |
Yes | The always-visible header. The entire block is wrapped in a @click="toggle()" listener. |
accordion_child_toggle |
No | Use when a specific child element (e.g. a button) should call toggle() rather than the whole header row. |
accordion_content |
Yes | The content revealed when the accordion is open. Hidden by default via x-cloak. |
The show_accordion variable is available in all blocks — use it in :class bindings to rotate icons or change styles based on open state.
view_glue_accordion.html
A lazy-loading variant. On first click it calls render(), which instantiates a ViewGlue object pointed at the URL in view_url, fetches the HTML from the server, and injects it into the content div. On subsequent clicks it simply toggles visibility without re-fetching.
Template path:
| Block | Required | Description |
|---|---|---|
view_url |
Yes | A single-line URL. Used to construct the ViewGlue request on first open. |
accordion_toggle |
Yes | The clickable header. Must call render_or_toggle_show() — it handles both the initial render and subsequent toggles. |
accordion_content_class |
No | Extra CSS classes applied to the injected content container div. |
Alpine.js state available in blocks:
| Variable | Type | Description |
|---|---|---|
show |
bool |
Whether the content area is currently visible |
is_rendered |
bool |
Whether content has been fetched from the server at least once |
Examples
Basic Accordion
A single accordion section with a header and collapsible body. Click the header to toggle.
{% extends 'django_spire/accordion/accordion.html' %}
{% block accordion_toggle %}
<div class="d-flex justify-content-between align-items-center px-3 py-2">
<span class="fw-semibold">Project Overview</span>
<i class="bi" :class="show_accordion ? 'bi-chevron-up' : 'bi-chevron-down'"></i>
</div>
{% endblock %}
{% block accordion_content %}
<div class="px-3 py-2 border-top">
Your accordion content goes here.
</div>
{% endblock %}
Multiple Independent Accordions
Each accordion manages its own state — opening one does not affect the others.
{% extends 'django_spire/accordion/accordion.html' %}
{% block accordion_toggle %}
<div class="d-flex justify-content-between align-items-center px-3 py-2">
<span class="fw-semibold">Order #{{ order.pk }}</span>
<i class="bi" :class="show_accordion ? 'bi-chevron-up' : 'bi-chevron-down'"></i>
</div>
{% endblock %}
{% block accordion_content %}
<div class="px-3 py-2 border-top">
Status: <strong>{{ order.get_status_display }}</strong>
— {{ order.item_count }} items — ${{ order.total }}
</div>
{% endblock %}
Child Toggle
Use accordion_child_toggle when a specific element inside the accordion header — rather than the entire row — should control the toggle. The parent header area has no @click binding; the child calls toggle() directly.
{% extends 'django_spire/accordion/accordion.html' %}
{% block accordion_toggle %}
{# Empty — the toggle is triggered by a child element instead #}
{% endblock %}
{% block accordion_child_toggle %}
<button @click="toggle()" class="btn btn-outline-secondary btn-sm">
<span x-text="show_accordion ? 'Collapse' : 'Expand'"></span>
</button>
{% endblock %}
{% block accordion_content %}
<div class="px-3 py-2 border-top">
This content is toggled by the button above, not by clicking the header.
</div>
{% endblock %}
View Glue Accordion
The view glue accordion fetches its content from a Django view the first time it opens. Subsequent clicks toggle the already-loaded content without hitting the server again. The preview below simulates the async load with a short delay.
{% extends 'django_spire/accordion/view_glue_accordion.html' %}
{% block view_url %}{% url 'myapp:order:detail_content' pk=order.pk %}{% endblock %}
{% block accordion_toggle %}
<div
@click="render_or_toggle_show()"
class="d-flex justify-content-between align-items-center px-3 py-2"
style="cursor: pointer;"
>
<span class="fw-semibold">Order #{{ order.pk }}</span>
<i class="bi" :class="show ? 'bi-chevron-up' : 'bi-chevron-down'"></i>
</div>
{% endblock %}
The view_url block must resolve to a single-line URL. The view it points to should return a rendered HTML fragment — not a full page — since ViewGlue injects the response directly into the content div.