My Single Directory Components Workflow

Florida Drupal Camp 2025

https://bit.ly/sdc-workflow

Look at Meeeee!

brianperry.dev

Twitter Bluesky
@brianperry.dev

Company Slide Goes Here

(a sappy aside about the community)

I ❤️ JS Frameworks

But for some projects they are overkill.

Which leads me to...

My First Drupal Theme in ~3 Years

Photo by Tonik on Unsplash
  • How has Drupal theming changed?
  • Will modern web standards make my life easier?
  • Will I miss my fancy frameworks?

    also...

Will Single Directory Components solve all of my problems?

What exactly are Single Directory Components (SDCs)?

SDCs are Drupal Core's FE Component Primitive

The one true way

SDCs can have:

  • A YML file, which may define a schema
  • A Twig template
  • A CSS file (optional)
  • A JavaScript file (optional)

SDCs Simplify Components in Drupal

  • A defined standard
  • All files in one place
  • Loads CSS/JS assets automatically
  • No hunting for hooks and preprocess functions
  • SDC Contrib Ecosystem

My Workflow

A quick distinction

SDC things

vs

Brian things

Photo by Todd Quackenbush on Unsplash

The Brief: Spice up Drupal CMS Cards

Prototyping

High Level Prompts

  • Create a Card using only html, css, and js
  • Give it a retro style
  • Use Drupal's brand colors
  • Add a JavaScript-based interaction

A Brian Thing™️: I prototype outside of Drupal when possible

Astro is current tool of choice

Step 1: Add Libraries You'll Need

  • For this example: borrowed Olivero base css
  • I currently turn to Pico and Open Props
  • For many others, this might be Tailwind

Step 2: Add some basic markup

A Brian Thing™️: Scope with a custom element

  • <retro-card> is valid html
  • Makes scoped CSS easy
  • Can later upgrade to a web component for interactivity

Step 3: Refine Your Props and Slots

Slots - unstructured data - markup, other components

Props - key value pairs (think attributes)

<retro-card>
  <div class="card">
    <div class="card-image">
      <slot name="image" />
    </div>
    <div class="card-content">
      <div class="card-meta">
        <span class="date">{date}</span>
        <div class="tags">
          <span class="tag">{tag}</span>
        </div>
      </div>
      <h2 class="card-title">{title}</h2>
      <div class="card-text">
        <slot name="body" />
      </div>
      <a href={url} class="read-more">Jack In →</a>
    </div>
  </div>
</retro-card>
---
import Card from '../components/Card.astro';
import Layout from '../layouts/Layout.astro';
---

<Layout>
    <Card
    date="March 14, 2024"
    tag="Retro"
    title="Synthwave Dreams"
    url="/synthwave-dreams"
  >
    <img slot="image" src="https://picsum.photos/400/250" alt="Card image" />
    <p slot="body">Welcome to the future that never was. A nostalgic journey through neon-lit streets and digital horizons. Where synthwave meets design in a perfect harmony.</p>
  </Card>
</Layout>
---
interface Props {
  date: string;
  tag: string;
  title: string;
  url: string;
}

const { title, date, tag, url } = Astro.props;
---

Step 4: Add Styles

Step 5: Add JavaScript for interactivity

<script>
  // Add glitch effect to image on click
const cardImage = document.querySelector('.card-image');
cardImage?.addEventListener('click', () => {
  cardImage.classList.add('glitch');
  setTimeout(() => {
    cardImage.classList.remove('glitch');
  }, 300);
});

// Add flicker effect to tags on click
const tags = document.querySelectorAll('.tag');
tags.forEach(tag => {
  tag.addEventListener('click', () => {
    tag.classList.add('flicker');
    setTimeout(() => {
      tag.classList.remove('flicker');
    }, 300);
  });
});
</script>

A Brian Thing™️: Upgrade to a web component

Why bother?

  • Scoping - isolated to this specific instance of a <retro-card>
  • Progressive enhancement - with JS off, it's still a card

Creating a component in Drupal

Spoiler: this should feel pretty similar

Step 1: Create a single directory for your single directory component

components/card subdirectory in a module or theme

Step 2: Create your card.component.yml file

bare minimum version:

name: Card
props:
  type: object
  properties: {}

Step 3: Create a Twig Template

card.twig:

<retro-card>Card component</retro-card>

Note that the extension is .twig not .html.twig

Step 4: Add Basic CSS

card.css

retro-card {
  color: green;
}

We'll add more css/markup along the way...

Step 5: Use Your Component

In our drupal_cms_olivero_sdc theme

node--card.html.twig:

{% embed 'drupal_cms_olivero_sdc:card' %}{% endembed %}

Mostly useless, but it exists 🚀

Step 6: Populate Slots

Slots in our prototype looked like:

<div class="card-text">
  <slot name="body" />
</div>

For Drupal, we first define the slot in yml

slots:
  body:
    title: Body
    description: Body markup

Next, we define it as a block in our twig template

<div class="card-text">
  {% block body %}{% endblock %}
</div>

And populate the block in our Twig embed

{% embed 'drupal_cms_olivero_sdc:card' %}
  {% block body %}
    {{ content|without('field_featured_image', 'links') }}
  {% endblock %}
{% endembed %}

Step 7: Populate Props

Slots in our prototype looked like:

<Card
   date="March 14, 2024"
   tag="Retro"
   title="Synthwave Dreams"
   url="/synthwave-dreams"
 >

For Drupal, we first update our yml to add a date prop:

name: Card
props:
  type: object
  properties:
    date:
      type: string
      title: date
      description: "The date content was created"

Next we use the date prop in our twig template:

{% if display_submitted %}
  <span class="date">{{date}}</span>
{% endif %}

And pass the prop in our Twig embed

{% embed 'drupal_cms_olivero_sdc:card' with { date: date} %}

Similar to TypeScript, you can enforce your schema

---
interface Props {
  date: string;
  tag: string;
  title: string;
  url: string;
}

const { title, date, tag, url } = Astro.props;
---

For theme components:

enforce_prop_schemas: true

Step 8: Add JavaScript

No modifications required in this case.

Too many steps?

drush generate sdc

A clearly defined schema gives you superpowers

Easy as...

{% embed 'drupal_cms_olivero_sdc:card' %}

vs

{% embed 'drupal_cms_olivero_sdc:business_card' %}

Where did my complex build process go?

Indispensable contrib tools

nomarkup module

twig_tweak module

  • SDC Styleguide
  • SDC Block
  • Radix Theme
  • UI Patterns 2.x

Gotchas

  • Fighting Drupal's markup
    • Should your prop be a slot?
  • More complicated build processes
  • Twig Embed / Mapping Data Is Still Hard
  • Web Components in CK Editor (try Embedded Content module)

Dreams for the future of Drupal Theming

  • Visual Component Mapping
    • Experience Builder?
  • 'Franken-Framework'
    • Semi-coupled themes
    • Islands
    • Inertia

Thanks! / Questions?