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

yaml
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

twig
{% 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_mode

  • Uses 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

twig
<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:

twig
{# 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:

  1. Paragraph Type: "Tab Item"

    • Field: field_title (Text) - Tab header

    • Field: field_description (Long Text) - Tab content

  2. Paragraph Type: "Tabs Wrapper"

    • Field: field_tabs (Paragraph reference) - References "Tab Item" paragraphs

    • Field: field_tab_style (Text) - Visual style selection

Step 3: Real-World Usage Example

twig
{# 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:

twig
{# 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

twig
{% set selectedStyle = content.field_style_selection.0['#markup'] %}

{% include 'tailpine:tabscontent' with {
    tabButtons: item.title,
    tabStyle: selectedStyle,
    render_mode: 'tabsHeader'
} %}

Conditional Content

twig
{% include 'tailpine:tabscontent' with {
    tabid: key,
    render_mode: 'content',
    tabContent: item.content|default('No content available')
} %}

With Additional Classes

twig
{% include 'tailpine:tabscontent' with {
    tabButtons: item.title,
    tabStyle: 'underline',
    render_mode: 'tabsHeader',
    attributes: create_attribute().addClass('custom-tab-class')
} %}

File Summary

 
 
FilePurposeKey Features
tabscontent.component.ymlComponent definitionProps, slots, render modes
tabscontent.twigDisplay logicAlpine.js, conditional rendering
paragraph--tailpine-tabs-wrapper.html.twigDrupal integrationField mapping, loops

Benefits of This Approach

  1. Reusable: Same component for headers and content

  2. Maintainable: One file to update for both parts

  3. Consistent: Uniform behavior across all tabs

  4. Accessible: Proper ARIA roles and attributes

  5. Performant: Alpine.js for lightweight interactivity