---
name: drupal-media
description: 'Work with Drupal Media entities, image styles, and responsive images. Use when: creating media types, writing media Twig templates, implementing responsive image styles, configuring breakpoints.yml, using picture element output, handling file/image/video/document media, writing media preprocess functions, or attaching responsive image libraries.'
argument-hint: 'Media type (image, video, document, remote video) and context (listing, hero, card thumbnail, etc.)'
---

# Drupal Media — Square360 Conventions

> **Also load:** [drupal-theme](../drupal-theme/SKILL.md) for the full s360-base-theme file structure.
> **Also load:** [drupal-11-coding-standards](../drupal-11-coding-standards/SKILL.md) for field naming conventions (`field_erm_*` for media references).

## When to Use
- Creating or configuring Drupal Media types (image, video, document, remote video)
- Writing Twig templates for media entities
- Setting up responsive image styles with `breakpoints.yml`
- Generating `<picture>` element output in templates
- Handling image alt text accessibly
- Rendering media in paragraphs or content types

---

## Media Types

Default Drupal Media types and their field:
- **Image** — `field_media_image` (image field with alt/title)
- **Document** — `field_media_document` (file field)
- **Video (local)** — `field_media_video_file` (file field)
- **Remote Video** — `field_media_oembed_video` (text field / URL)
- **Audio** — `field_media_audio_file` (file field)

Custom media types follow project field naming: `field_erm_{media_type}` when referencing from a node/paragraph.

---

## Template Suggestion Hierarchy

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

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

---

## Rendering a Media Entity in a Template

**Avoid** rendering raw field arrays; use Drupal's image/responsive_image formatters via `content`:

```twig
{# Renders the image using its configured formatter (e.g. responsive_image_style) #}
{{ content.field_media_image }}
```

For more control, pass the render array in preprocess:

```php
// In preprocess_node or preprocess_paragraph:
$media = $node->get('field_erm_hero')->entity;
if ($media && $media->hasField('field_media_image')) {
  $variables['hero_image'] = [
    '#theme' => 'responsive_image',
    '#responsive_image_style_id' => 'hero',
    '#uri' => $media->get('field_media_image')->entity->getFileUri(),
    '#alt' => $media->get('field_media_image')->alt,
    '#title' => $media->get('field_media_image')->title,
  ];
}
```

Template:
```twig
{% if hero_image %}
  <div class="hero__image">{{ hero_image }}</div>
{% endif %}
```

---

## Responsive Images

### 1. Define breakpoints in `{theme}.breakpoints.yml`

```yaml
{theme}.mobile:
  label: Mobile
  mediaQuery: ''
  weight: 0
  multipliers:
    - 1x

{theme}.tablet:
  label: Tablet
  mediaQuery: 'all and (min-width: 768px)'
  weight: 1
  multipliers:
    - 1x

{theme}.desktop:
  label: Desktop
  mediaQuery: 'all and (min-width: 1200px)'
  weight: 2
  multipliers:
    - 1x
    - 2x
```

### 2. Create image styles

Image styles are configured at `/admin/config/media/image-styles`. Each breakpoint needs a dedicated style:
- `hero_mobile` — 768×auto crop
- `hero_tablet` — 1200×auto crop
- `hero_desktop` — 1920×auto crop

### 3. Create a responsive image style

At `/admin/config/media/responsive-image-style`. Map each breakpoint to an image style:
- Breakpoint `{theme}.mobile` → `hero_mobile`
- Breakpoint `{theme}.tablet` → `hero_tablet`
- Breakpoint `{theme}.desktop` → `hero_desktop`
- Fallback → `hero_desktop`

### 4. Use in a field formatter

Configure the Media image field to use the **Responsive image** formatter, selecting your responsive image style.

The output renders as a `<picture>` element with correct `<source>` tags per breakpoint.

---

## Alt Text

Alt text must always be set on image media. In templates:

```twig
{# Never suppress alt text unless it is a decorative image #}
<img src="{{ file_url }}" alt="{{ alt }}" />

{# Decorative images: empty alt, add role="presentation" to wrapper #}
<div class="decorative-image" role="presentation" aria-hidden="true">
  <img src="{{ file_url }}" alt="" />
</div>
```

When rendering via Drupal's `responsive_image` theme hook, alt text is automatically pulled from the media entity's image field. Ensure editors understand it is required.

---

## Remote Video (oEmbed)

For YouTube/Vimeo embeds, the Remote Video media type renders an iframe via the oEmbed formatter. To add a privacy-respecting wrapper or a play button overlay, override the template:

```
templates/media/media--remote-video.html.twig
```

The iframe is in `content.field_media_oembed_video`. Wrap it:

```twig
<div class="remote-video">
  <div class="remote-video__embed">
    {{ content.field_media_oembed_video }}
  </div>
</div>
```

---

## Square360 Conventions

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

Document here:
- Media types in use on this project
- Responsive image style names and their intended usage context
- Breakpoints defined in `{theme}.breakpoints.yml`
- Any lazy-loading approach (native `loading="lazy"`, a JS library, etc.)
- Where media is managed (content editors, developer-only, etc.)
