Front-End Parts Kits in Craft

Pattern library, style guide, UI library, parts kit; call them what you want, but these are essential to building websites of any scale. Typically, this is something you manually construct to demonstrate all the modules and pieces that you have constructed, but I have been investigating ways to make them a bit more dynamic when building sites with Craft.

The biggest downside of manually building a parts kit is maintenance. Any time you have to update a piece of the markup in the parts kit, you have to update the real version of the code too. On the last few Craft sites I have built, I have experiemented with using partials and mocking data to share code in the part kit.

Using Partials

In these recent projects, most of the patterns emerged within blocks in a Matrix field.

Craft matrix example

So I created a simple template to handle looping through Matrix blocks that I could include on entry pages:

_partials/content-blocks.html

{% for block in entry.contentBlocks %}
    {% include '_partials/blocks/' ~ block.type ignore missing %}
{% endfor %}

Then, I created a separate template for each of the block types. Here a few examples:

_partials/blocks/text.html

<div class="wrapper">
    {% if entry.icon is defined and entry.icon | length and loop.index == 1 %}
        <img src="{{ entry.icon.first().url }}" alt="" class="text__icon">
    {% endif %}
    <div class="text">
        {{ block.text}}
    </div>
</div>

_partials/blocks/quote.html

<div class="quote--{{ block.size }}">
    <blockquote>“{{ block.quote }}”</blockquote>
</div>

_partials/blocks/callOutList.html

<div class="call-out-list">
    <h2 class="horizontal-list__hdr">{{ block.headline }}</h2>
    <ul class="horizontal-list__items">
        {% for item in block.list %}
            <li>
                {% if block.icons[loop.index0] is defined %}
                    <div class="horizontal-list__icon">
                        <img src="{{ block.icons[loop.index0].url }}" alt="" class="text__icon">
                    </div>
                {% endif %}
                <h3 class="horizontal-list__name">{{ item.name }}</h3>
                <p>{{ item.description }}</p>
            </li>
        {% endfor %}
    </ul>
</div>

Anywhere I want to use those Matrix blocks, which was a lot of places, I could just include that template:

{% include '_partials/content-blocks.html' %}

So that takes care of the real templates, but what about the parts kit?

Mocking Data

Now that we've got a reusable partial in place, how do we provide data? When looking at Twig syntax, you will notice that everything is just an object. So I figured that I could just mock an entry object, include the partial, and then boom done.

First, I started by defining a couple of variables that I'll end up using throughout the parts kit:

{% set text %}
    <h2>Heading</h2>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
{% endset %}
{% set textFull %}
    <p class="highlight">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
    <h2>Heading 2</h2>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
    <h3>Heading 3</h3>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
    <h4>Heading 4</h4>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, <a href="http://google.com">sed do eiusmod tempor incididunt</a> ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
    <ul>
        <li>Here is a list item</li>
        <li>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</li>
        <li>Here is a list item</li>
    </ul>
    <ol>
        <li>Here is a list item</li>
        <li>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</li>
        <li>Here is a list item</li>
    </ol>
{% endset %}
{% set image = [
    {
        url: 'http://placehold.it/480x442',
        caption: 'Here is the awesome caption',
        width: 480
    }
] %}
{% set contentBlocksBase = {
    type: 'text',
    text: text,
    image: []
} %}

Now let’s take a look at how I handled that first text block example:

{% set entry = {
    contentBlocks: [contentBlocksBase | merge({ text: textFull })]
} %}
{% include '_partials/content-blocks' %}

Pretty simple. The quote example is straightforward as well:

{% set entry = {
    contentBlocks: [contentBlocksBase | merge({
        type: 'quote',
        quote: 'Suspendisse sagittis congue tincidunt. Ut consectetur purus nisi, sed sodales lorem tempor vitae. Quisque pellentesque augue eget.',
        size: 'small'
    })]
} %}
{% include '_partials/content-blocks' %}

The call out list is a bit more complex since we have to construct subarrays, but still very easy to construct the data:

{% set entry = {
    contentBlocks: [contentBlocksBase | merge({
        type: 'callOutList',
        headline: 'Here is the awesome headline',
        icons: [
            {
                url: 'http://placehold.it/150'
            },
            {
                url: 'http://placehold.it/150'
            },
            {
                url: 'http://placehold.it/150'
            }
        ],
        list: [
            {
                name: 'Here is the first item',
                description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor'
            },
            {
                name: 'Here is the second item',
                description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor'
            },
            {
                name: 'Here is the third item',
                description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor'
            }
        ]
    })]
} %}
{% include '_partials/content-blocks' %}

Things get a little trickier when you have to do more complex stuff in each of the partials. So there were a few cases where I had to create a flag to say whether or not the partial was being used on the parts kit or the real website.

Craft text with image Matrix example

_partials/blocks/textWithImage.html

{% set image = block.image[0] %}
<div class="text-with-image">
    <div class="wrapper--narrow contain">
        <figure class="text-with-image__figure--{{ block.imageAlignment }}">
            {% set thumb = {
                width: (image.width < 480) ? image.width : 480,
            } %}
            <img src="{{ (dummy is defined) ? image.url : image.url(thumb) }}" alt="">
            {% if image.caption %}
                <figcaption class="text-with-image__caption">{{ image.caption }}</figcaption>
            {% endif %}
        </figure>
        <div class="text-with-image__content">
            <div class="text">
                {{ block.text }}
            </div>
        </div>
    </div>
</div>

Since there is an image transform happening in there, and we need a real Craft asset in order to perform the transform, I've got that dummy flag in there. So when I want to include this block in the part kit, it looks like so:

{% set entry = {
    contentBlocks: [contentBlocksBase | merge({
        type: 'textWithImage',
        image: image,
        imageAlignment: 'left',
    })]
} %}
{% include '_partials/content-blocks' with { dummy: true } %}

For the most part, you can mock data to reuse templates between your parts kit and the actual website pretty easily, but there may be a few instances where you have to treat things a little differently since our fake Twig objects do not have the same methods as real Craft models. While doing this, I had the idea to maybe create a plugin where you could build Craft models on the fly with fake data, but I didn't have the time to fully explore the idea. That’s Future Trevor's problem.

Trevor is a senior front-end developer who specializes in writing bulletproof code for clients including the World Wildlife Fund and GoPole.

More posts by Trevor