---
name: drupal-paragraphs
description: 'Build and theme Paragraphs in Drupal 11. Use when: creating paragraph types via config, writing paragraph Twig templates, implementing preprocess for paragraphs, building component stories for paragraph-based layouts, handling nested paragraphs, dealing with paragraph field naming conventions, paragraph display modes, and paragraph-to-component mapping.'
argument-hint: 'Paragraph type machine name and a description of its fields and intended display'
---

# Drupal Paragraphs — Square360 Conventions

> **Also load:** [drupal-theme](../drupal-theme/SKILL.md) for the full s360-base-theme file structure, toolkit functions, and the component-driven Twig pattern.
> **Also load:** [drupal-11-coding-standards](../drupal-11-coding-standards/SKILL.md) for field naming conventions.

## When to Use
- Creating or theming a new Paragraph type
- Writing `paragraph--{type}.html.twig` templates
- Mapping paragraph fields to component Twig variables
- Nesting paragraphs (e.g. a "Section" paragraph containing multiple "Card" sub-paragraphs)
- Registering paragraph libraries
- Writing Storybook stories for paragraph-based components

---

## Paragraph Type Configuration

Paragraph types live in `config/install/` or exported to `config/sync/`.

Machine name convention: `paragraph_type.{type_name}` (e.g. `paragraph_type.text_block`)

Field naming follows the project-wide **Field Machine Name Conventions**:
- `field_erp_*` — Entity reference to another paragraph (nested)
- `field_ern_*` — Entity reference to a node
- `field_erm_*` — Entity reference to a media entity
- `field_ert_*` — Entity reference to a taxonomy term
- `field_{usage}` — Scalar/text fields
- `field_is_{usage}` — Boolean fields

---

## Template Suggestion Hierarchy

Drupal resolves paragraph templates from most to least specific:

```
paragraph--{bundle}--{view-mode}.html.twig
paragraph--{bundle}.html.twig
paragraph--{view-mode}.html.twig
paragraph.html.twig
```

**Location in theme:** `web/themes/custom/{theme}/templates/paragraph/`

Naming example for a `text_block` paragraph in the `full` view mode:
```
templates/paragraph/paragraph--text-block--full.html.twig
```

---

## Component-Driven Twig Pattern

The paragraph template acts as **bridge** between Drupal data and the component Twig.

**Step 1 — Component Twig** (`ui/src/paragraphs/text-block/text-block.twig`):
```twig
{# Component owns the markup and BEM class structure #}
<div {{ attributes.addClass('text-block') }}>
  {% if title %}
    <h2 class="text-block__title">{{ title }}</h2>
  {% endif %}
  {% if body %}
    <div class="text-block__body">{{ body }}</div>
  {% endif %}
</div>
```

**Step 2 — Drupal template** (`templates/paragraph/paragraph--text-block.html.twig`):
```twig
{# Drupal template maps field values to component variables #}
{% set title = content.field_title[0]['#context']['value'] ?? '' %}
{% set body = content.field_body|render %}

{% include '@{theme}/paragraphs/text-block/text-block.twig' with {
  attributes: attributes,
  title: title,
  body: body,
} only %}
```

---

## Preprocess for Paragraphs

In `{theme}.theme`:

```php
/**
 * Implements template_preprocess_paragraph().
 */
function mytheme_preprocess_paragraph(array &$variables): void {
  $paragraph = $variables['paragraph'];
  $bundle = $paragraph->bundle();

  // Example: pass a computed value to the template.
  if ($bundle === 'text_block') {
    $variables['show_title'] = !$paragraph->get('field_title')->isEmpty();
  }
}

/**
 * Implements template_preprocess_paragraph__BUNDLE().
 *
 * Per-bundle preprocess (uses double underscore in hook name).
 */
function mytheme_preprocess_paragraph__text_block(array &$variables): void {
  // Only runs for text_block paragraph type.
  $paragraph = $variables['paragraph'];
  $variables['bg_color'] = $paragraph->get('field_bg_color')->value ?? 'white';
}
```

---

## Nested Paragraphs

When a paragraph contains an entity reference field to other paragraphs (`field_erp_*`):

```twig
{# Parent paragraph template #}
{% for item in content.field_erp_items %}
  {{ item }}
{% endfor %}
```

Drupal renders the child paragraphs automatically. Each child uses its own template.

For custom rendering order or display modes, use the `content` array:
```twig
{{ content.field_erp_items }}
```

---

## Libraries for Paragraphs

Register in `libraries/paragraphs.libraries.yml` (the split libraries file for paragraph assets):

```yaml
text-block:
  css:
    component:
      ../ui/dist/css/paragraphs/text-block/text-block.css: {}
  js:
    ../ui/dist/js/paragraphs/text-block/text-block.js: {}
  dependencies:
    - core/drupal
```

Attach in the template:
```twig
{{ attach_library('{theme}/text-block') }}
```

---

## Storybook Story for a Paragraph Component

Stories for paragraph components follow the same pattern as other components but use `Paragraphs/` in the title:

```js
// text-block.stories.js
import TextBlock from './text-block.twig';
import defaultData from './text-block.yml';

export default {
  title: 'Paragraphs/Text Block',
  argTypes: {
    title: { control: 'text' },
    body: { control: 'text' },
  },
};

export const Default = (args) => TextBlock(args);
Default.args = { ...defaultData };

export const NoTitle = (args) => TextBlock(args);
NoTitle.args = { ...defaultData, title: '' };
```

---

## Square360 Conventions

> **TODO: Lead developer to complete this section.**

Document here:
- Which paragraph types are available on this project
- Any shared "layout" paragraph types (Section, Grid, etc.)
- Display modes used for paragraphs (e.g. `full`, `summary`, `card`)
- Any global preprocess functions applied to all paragraphs
