Emulating Components in Twig
Tags: craftcms, components, twig
Published: 2019-01-22 14:31:12

---
title: Emulating Components in Twig
published: true
description: When I work in Twig templates, not having access to bona fide components is definitely one of the things I miss most. Because of that, I've been looking for a solution to this problem for a while.
tags: craftcms, components, twig
---

Components are one of my favorite things about JavaScript frameworks like Vue.js. They allow you to follow DRY principles by removing repetition of code in your markup, and they provide a fantastic developer experience. When I work in Twig templates, not having access to bona fide components is definitely one of the things I miss most. Because of that, I've been looking for a solution to this problem for a while.

Before I talk about the solutions I tried out, and the one I've landed on, let me first give you my criteria for what features I need out of components, or a component replacement.

## Criteria

- Props: I should be able to pass named parameters (usually referred to as "props") into my component.
- Slots: I should be able to pass chunks of markup (HTML, text, whatever) into my component, and the component should determine what to do with that markup (i.e. where to render it).

If you have experience with Vue.js or another component-based framework, these concepts should be very familiar to you. If not, you'll have to trust me that once you've used these component features, trying to write markup without them is painful 😂

## Explored Solutions
There were two main Twig features that I thought I could use to achieve component-like functionality within Twig: Macros and Includes.

### Macros
So I started experimenting with Macros. If you're not familiar, Macros are the "functions" of Twig. Here's an example Macro (see more in the [Twig docs](https://twig.symfony.com/doc/2.x/tags/macro.html)):

```twig
{% import _self as components %}

{% macro button(props) %}
    <button>{{ props.content }}</button>
{% endmacro %}

<p>{{ components.button({ content: 'Click Me' }) }}</p>
```

Using Macros satisfied one criterion, passing props, but it didn't solve my problem of wanting to pass markup into my "component" via named slots.

### Includes
Next, I tried [Twig's Includes](https://twig.symfony.com/doc/2.x/tags/include.html). Includes are a way to break up your code into multiple files, and I often use them to break up my page templates so they're smaller and to make things easier to search and find. But, they have a built-in feature, "with", that allows you to pass context (i.e. variables) into the include which made me hopeful that I could hack them into working well as components!

But again, they only satisfied the first criteria. There was no (built-in) way to pass in chunks of markup to be rendered within the component's markup.

*Side note: Technically, for both Macros or Includes, I could do something hacky like assigning a variable to a bunch of markup and passing that variable in, but that felt very wrong and un-intuitive.*

## Final Solution
After giving up hope, I eventually stumbled upon Twig's Embeds. I read the documentation, and instantly got excited because Embeds seemed to satisfy both criteria right out of the box! I immediately coded up an example on TwigFiddle (twigfiddle.com) and I found that Embeds fully met both of my criteria: Using the "with" functionality and "blocks" I could pass props **and** chunks of markup into the embed.

Here's an example of embed declaration, and its use:
```twig
{% embed "page.twig" with {
    social: ['facebook', 'twitter', 'snapchat']
} %}
    {% block header %}
        <div>Header Content</div>
    {% endblock %}
    
    {% block main %}
        <div>Main Content</div>
    {% endblock %}
{% endembed %}
```

```twig
{# File: page.twig #}

<div class="page-component">
    <header>{% block header %}Default Header Content{% endblock %}</header>
    
    <main>{% block main %}Default Main Content{% endblock %}</main>
    
    <footer>{% block footer %}Default Footer Content{% endblock %}</footer>
    
    {% if social is defined %}
        <ul class="social">
            {% for item in social %}<li>{{ item }}</li>{% endfor %}
        </ul>
    {% endif %}
</div>
```

This method of emulating components in Twig isn't without its drawbacks, but I think it works well for *most* scenarios. Here are a few notes about the pros and cons of embeds:

### Pros
- You can pass in both props (variables) and slots (blocks).
- You can specify default content for each block.
- Using the "with only" feature, you can encapsulate your component so it won't implicitly receive any variables from the context in which it's used.

### Cons
- You have to specifically reference the embed's file path in every usage
- You do **have** to use the "only" keyword to make sure your parent template's variables don't leak into the component.

## Final Thoughts
I don't think Embeds are a fantastic long-term solution, and I'd love to have component functionality natively in Twig, but I was happy to find something that satisfies my needs and allows me to clean up my templates in the way that I'm comfortable with.

I'm also intrigued by [Twig Components Bundle](https://packagist.org/packages/olveneer/twig-components-bundle), a Symfony package that appears to add component functionality to Twig, based on the component system of Vue.js. But, I haven't tried it out yet. I'll make sure to report back if I do!

***

I work for an awesome company called Good Work. We are an expert team of web developers helping design teams at agencies, brands and startups build things for web and mobile.

If you're looking for someone to work with on your web projects, don't hesitate to [reach out](https://simplygoodwork.com/contact)!