Modularity With Craft

MY EPIPHANY

A few years ago, I had an epiphany about my development process. I was just starting a new job and my mentor asked me to read a book about CSS.

Read a book on CSS? Come on, I know CSS! I had already developed dozens of websites, which should be proof enough that I understand the concept.

Turns out that I had a lot to learn.

After reading a few pages, I knew that writing CSS using a scalable, modular method was something that would change the way I work completely. I discovered some flaws in my development process—mistakes I was making early on that would cause problems with my code going forward.

Since that day I’ve preached a modular approach to coding. And my mantra is SMACSS.

SMACSS VS BEM VS OOCSS

People who are familiar with a modular approach to writing CSS will probably have heard of several methodologies including BEM or OOCSS. Don’t bother with which one is better, just start with the one that feels most natural to you, as they all are centered on one concept—modules.

One of the major challenges in getting started with a modular CSS approach is that it will take some time to figure out how to define modules. When I started to work with SMACSS, I noticed I was constantly revising my code. Sometime I would define a module, then try to make a whole slew of styles to work within that module, only to realize that I really should have started a whole new module—or several. It will take some time to settle into your workflow, but with experience you’ll find that you’re not going back and revising your code nearly as often.

SOME ADVICE TO GET YOU STARTED

I encourage you to read more about SMACSS, but here are a few thoughts to get you started:

  1. If you need to override more than 25% of your code when submodeling a module, it’s probably better to create a new module.
  2. Try to avoid classes that reference content, such as “page-title-about”. It’s better to clarify the differences between a given module and base modules like “page-title-bigger” or “page-title-subtle”.
  3. Don’t tie layout classes to corresponding values. For example, on one project we used ‘l-mb-70’ class for elements with a bottom margin of 70px or ‘l-hp-50’ for horizontal padding of 50px. At first it seemed like a good idea. However, we ran into problems when writing responsive rules.
  4. Modularity is not about reducing CSS as much as you can, it’s about scoping rules to a module.
Modularity is not about reducing CSS as much as you can, it’s about scoping rules to a module.

MODULARITY WORKS WELL WITH PREPROCESSORS

Incorporating a modular approach in your styling is easy if you use some kind of CSS preprocessor like SASS or LESS, because you can structure your files according to module. For example, you could have “slider.scss” or “video.scss”, which really helps break one big CSS file into smaller, more manageable chunks. In this way, you can better focus on the styles within the scope of one particular module. This also makes it easier for team leaders to review.

JUST A TITLE ATTRIBUTE ON IMAGES…

You may think that using this type of approach means a lot of extra work up front. But let me demonstrate the benefits using a small task I worked on recently as an example.

To improve the technical SEO on of our projects, we wanted to add title attributes to all of the images on the site. We had one simple task—add a title value using a twig expression. This is something that should take about five minutes, right?

Not so fast...

Imagine how many times you use the image element across a site. You’d have to update every one of those instances and make sure that you didn’t break something when editing them.

But if you had used a component file for your image tags, you would only need to make the change in one file. Not only does that improve the speed of your workflow, but your change will be scoped to that one component. You can rest assured knowing you didn’t break something in another part of the site. If it works in one instance, it will work everywhere.

Imagine how much time we could save—not to mention our clients’ budget!—by using a modular approach.

WHAT MODULARITY BRINGS TO THE TABLE

Modularity is really important to every workflow. Separating our project into smaller parts makes it more maintainable, more stable and less prone to error. Inexperienced developers might argue that it is more time consuming and should only be considered for medium- to large-sized projects, but I strongly disagree.

When coding, you need to establish a strong foundation right from the start. If at some point down the road your little project turns into something much bigger, you don’t want to redo the work you’ve already done. Moreover, modules facilitate a more efficient, divide-and-conquer approach when working with a team. Modules are also reusable, and we can reuse them not only within one project but also by replicating them across projects, or by adding our most frequently used modules directly to our bootstrap files.

MODULES IN CRAFT CMS

When developing a project with Craft CMS, we break our code into templates and major layout components such as header, footer and sidebar. Why? Because they repeat—you see them on every page and usually only have small changes between individual pages.

The same is true for buttons that you see throughout a website. Buttons are often one the most used elements on a site (and the most reusable), so it’s logical that we would put these in a module. Let’s walk through the process of developing a button module.

Some of the various buttons styles used on pinkstondigital.com

LET’S MAKE A MODULE

To keep our files well-organized, we have one folder where we store all of our partials. In our project setup, we place all module files inside the “craft/templates/_includes/modules/” folder. We’ll make a file named “button.html” so we can find it easier and an html button with some basic attributes and text:

<button class="button btn-primary" type="button">Some Text</button>

This is a simple partial, but it’s not a module. The definition of a module is very clear—it must be reusable and we need to have an option to change part of the code snippet. We know that buttons can have a different look, type value or text, so we’ll substitute a few twig expressions:

<button class="button {{ button.css }}" type="{{ button.type|default('button') }}">
  {{ button.text }}
</button>

You can see that I’m using a twig “default” filter inside the “type” attribute value to return a value of “button” if no value is defined. This is a handy solution when the value will be the same for most cases, but you still need the ability to change it.

You could also just check if the value is defined and then add an attribute and value:

<button class="button {{ button.css }}" {% if button.type|default %} type="{{ button.type }}{% endif %}">
  {{ button.text }}
</button>

HOW DO WE USE MODULES?

In our project setup, we have several cases where we use modules, so let’s start with the hardcoded values for our “button” object. We can optionally add values to the object before including the “button” partial:

{% set button =  {
   css: "btn-primary",
   text: "Some text"
} %}
{% include "_includes/modules/button.html" %}

You can also set property values right inside of the include statement:

{% include "_includes/modules/button.html" with { button: 
   css: "btn-primary",
   text: "Some text"
} %}

I would suggest setting the properties inside of the include statement. This way, values won't inadvertently get passed to other partials.

USING MODULES WITH SINGLE CHANNEL FIELDS & MATRIX FIELDS

Using modules with fields inside Craft CMS is pretty straightforward—we simply change our hardcoded value with twig expressions into field values. So if we have a field with the handles buttonCSS and buttonText, we would write our inclusions like this:

{% set button =  {
   css: entry.buttonCss,
   text: entry.buttonText
} %}
{% include "_includes/modules/button.html" %}

Or inside of the include statement like this:

{% include "_includes/modules/button.html" with { button: {
   css: entry.buttonCss,
   text: entry.buttonText
}} %}

When using with matrix fields, we would replace the entry handle with a block handle.

Nesting Modules

Now what if we had a bigger module, for example a form that uses other smaller modules, and one of those modules is button? Let’s make that form module example:

<form action=”form-action”>
 <fieldset>
   <legend>...</legend>
   {% for input in form.inputs %}
       {% include "_inc/modules/input.html" with { input: input } %}
   {% endfor %}
   {% include "_inc/modules/button.html" with { button: form.button } %}
 </fieldset>
</form>

For our form module to work, we will need to set our object properties for the form, input and button modules. You can see that for the button, we could just add the ‘button’ object inside the form object, but because we are using a for loop for form inputs, we need to set an array:

{% set form = {
 inputs: [
   {
     type: "text",
     name: name
   }
 ],
 button: {
   css: "btn-primary",
   text: "Some text"
 }
} %}
{% include "_inc/modules/form" with { form: form } %}

FINAL THOUGHTS

Modularity is a methodology that not only makes for a more efficient development process, it also improves our overall workflow. By focusing on one module at a time, we can ensure that it works in every case we intend to use it.

Simply put, modularity makes our code a whole lot better!