Using Components in Tailpine: A Practical Example
Component Implementation Guide
This documentation demonstrates how to implement and use the Tailpine Tabs Component in your Drupal site. Follow this step-by-step example to understand how components work in practice.
Component Files Overview
File 1: Component Configuration (tabscontent.component.yml)
Purpose: Defines the component's structure, properties, and behavior to Drupal
name: Tailpine Tabs description: Single tab item with dual render mode status: stable props: type: object properties: tabButtons: type: string description: "Tab display title (e.g., 'Home')" tabStyle: type: string description: "Visual style treatment" enum: - underline - default_fill - outline - primary_fill render_mode: type: string enum: ["tabsHeader", "content"] description: "Defines rendering mode" slots: tabContent: title: Tab Content description: "Content displayed when tab is active"
What this file does:
Registers the component with Drupal's SDC system
Defines what data the component accepts (props)
Specifies content areas (slots) that can be filled
Documents the component for other developers
File 2: Component Template (tabscontent.twig)
Purpose: Contains the HTML markup and display logic for the component
{% if render_mode == 'tabsHeader' %} {# Tab Header Version #} <button @click="activeTab = '{{ tabid }}'" :class="{ 'bg-primary text-white': activeTab === '{{ tabid }}', 'text-body hover:text-primary': activeTab !== '{{ tabid }}' }" class="px-6 py-3 text-sm font-medium transition-all duration-200 rounded-t-lg" role="tab" :aria-selected="activeTab === '{{ tabid }}'" > {{ tabButtons }} </button> {% elseif render_mode == 'content' %} {# Tab Content Version #} <div x-show="activeTab === '{{ tabid }}'" class="prose prose-lg max-w-none" role="tabpanel" > {{ tabContent|raw }} </div> {% endif %}
What this file does:
Renders either tab headers OR content based on
render_modeUses Alpine.js for interactive tab switching
Applies Tailwind CSS classes for styling
Implements accessibility features (ARIA roles)
File 3: Paragraph Template (paragraph--tailpine-tabs-wrapper.html.twig)
Purpose: Wrapper that uses the component in a Drupal paragraph context
<div x-data="{ activeTab: '0' }" class="tabs-container"> {# Tab headers loop #} <div class="tab-headers"> {% for key, item in content.field_tabs %} {% include 'tailpine:tabscontent' with { tabButtons: item['#paragraph'].field_title[0].value, tabid: key, tabStyle: 'underline', render_mode: 'tabsHeader' } %} {% endfor %} </div> {# Tab content loop #} <div class="tab-content"> {% for key, item in content.field_tabs %} {% include 'tailpine:tabscontent' with { tabid: key, render_mode: 'content', tabContent: item['#paragraph'].field_description[0].value } %} {% endfor %} </div> </div>
What this file does:
Creates the main tabs container with Alpine.js state management
Loops through Drupal paragraph items to create multiple tabs
Coordinates between tab headers and their corresponding content
Passes Drupal field data to the component
What We're Building
A interactive tabs component with:
Multiple tab headers
Dynamic content switching
Multiple visual styles
Alpine.js interactivity
Accessible markup
How to Use This Component
Step 1: Include Component in Your Template
Here's how you use the tabs component in a paragraph template:
{# paragraph--tailpine-tabs-wrapper.html.twig #} <div x-data="{ activeTab: '0' }" class="tabs-container"> {# TAB HEADERS #} <div class="tab-headers flex border-b"> {% for key, item in content.field_tabs %} {% if key|first != '#' %} {% include 'tailpine:tabscontent' with { tabButtons: item['#paragraph'].field_title[0].value, tabid: key, tabStyle: 'underline', render_mode: 'tabsHeader' } %} {% endif %} {% endfor %} </div> {# TAB CONTENT #} <div class="tab-content"> {% for key, item in content.field_tabs %} {% if key|first != '#' %} {% include 'tailpine:tabscontent' with { tabid: key, render_mode: 'content', tabContent: item['#paragraph'].field_description[0].value } %} {% endif %} {% endfor %} </div> </div>
Step 2: Content Structure Setup
Create these fields in Drupal:
Paragraph Type: "Tab Item"
Field:
field_title(Text) - Tab headerField:
field_description(Long Text) - Tab content
Paragraph Type: "Tabs Wrapper"
Field:
field_tabs(Paragraph reference) - References "Tab Item" paragraphsField:
field_tab_style(Text) - Visual style selection
Step 3: Real-World Usage Example
{# Example: Product Features Tabs #} <div x-data="{ activeTab: '0' }" class="product-tabs"> {# Headers #} <div class="flex space-x-2 mb-6"> {% include 'tailpine:tabscontent' with { tabButtons: 'Description', tabid: '0', tabStyle: 'primary_fill', render_mode: 'tabsHeader' } %} {% include 'tailpine:tabscontent' with { tabButtons: 'Specifications', tabid: '1', tabStyle: 'primary_fill', render_mode: 'tabsHeader' } %} {% include 'tailpine:tabscontent' with { tabButtons: 'Reviews', tabid: '2', tabStyle: 'primary_fill', render_mode: 'tabsHeader' } %} </div> {# Content #} <div class="tab-content-area"> {% include 'tailpine:tabscontent' with { tabid: '0', render_mode: 'content', tabContent: '<p>Product description goes here...</p>' } %} {% include 'tailpine:tabscontent' with { tabid: '1', render_mode: 'content', tabContent: '<ul><li>Spec 1</li><li>Spec 2</li></ul>' } %} {% include 'tailpine:tabscontent' with { tabid: '2', render_mode: 'content', tabContent: '<div>Customer reviews content</div>' } %} </div> </div>
Available Tab Styles
The component supports multiple visual styles:
{# Different style examples #} {% include 'tailpine:tabscontent' with { tabButtons: 'Underline Style', tabStyle: 'underline', render_mode: 'tabsHeader' } %} {% include 'tailpine:tabscontent' with { tabButtons: 'Filled Style', tabStyle: 'default_fill', render_mode: 'tabsHeader' } %} {% include 'tailpine:tabscontent' with { tabButtons: 'Outline Style', tabStyle: 'outline', render_mode: 'tabsHeader' } %}
🔧 Advanced Usage Patterns
Dynamic Style Selection
{% set selectedStyle = content.field_style_selection.0['#markup'] %} {% include 'tailpine:tabscontent' with { tabButtons: item.title, tabStyle: selectedStyle, render_mode: 'tabsHeader' } %}
Conditional Content
{% include 'tailpine:tabscontent' with { tabid: key, render_mode: 'content', tabContent: item.content|default('No content available') } %}
With Additional Classes
{% include 'tailpine:tabscontent' with { tabButtons: item.title, tabStyle: 'underline', render_mode: 'tabsHeader', attributes: create_attribute().addClass('custom-tab-class') } %}
File Summary
| File | Purpose | Key Features |
|---|---|---|
tabscontent.component.yml | Component definition | Props, slots, render modes |
tabscontent.twig | Display logic | Alpine.js, conditional rendering |
paragraph--tailpine-tabs-wrapper.html.twig | Drupal integration | Field mapping, loops |
Benefits of This Approach
Reusable: Same component for headers and content
Maintainable: One file to update for both parts
Consistent: Uniform behavior across all tabs
Accessible: Proper ARIA roles and attributes
Performant: Alpine.js for lightweight interactivity