Okay, time for Part 2 of Months 6.5-8-5 / the sixth installment of the Designgineering Chronicles! The post contains a smattering of technical notes and code snippets from project Cantaloupe over the past two months. Make sure to read Part 1 of this Chronicle to get caught up to speed…or don’t, that’s up to you!
Architecture Check-in
So, how has the pattern architecture worked out so far? Very well, for the most part! Interestingly, the crux of the design system appears to be markup patterns based on atomic design principles, paired with utility class configurations. Perhaps “templating framework” is a more accurate phrase than “design system” for what we currently have, but after Cantaloupe, I’ll be syncing up with the design team to get more “design” into the sytsem.
The architecture is pretty much what I envisioned way back, pre-Cantaloupe, and what I wrote about last time, but now it’s powering an entire, big-time website that will hopefully launch on time, so it’s worth mentioning again.
In a sentence: Markup partials are written in Twig in the pattern library (powered by kss-node) then the individual Twig templates are parsed into PHP template parts. Those PHP template parts are imported and configured with data and CSS utility classes in the standard WordPress template files.
Parser
The Twig to PHP parser has been remarkably stable. There are a few PHPCS formatting issues, but those can be fixed easily with a phpcbf
command. Having the restricted Twig features hasn’t been a problem either โ I would actually say it has helped the overall system to have those restrictions. It should probably be written in Node and part of PMC Build Utils (our tooling / webpack config repo) but for now, it is written as a PHP class in the theme.
One problem we faced was getting a lot of warnings when variables were undefined in the PHP templates. AS โ the back-end lead โ had the brilliant idea to use the null coalescing operator, which required a small update to the parser, appending the ?? ''
to the end of the variable names. Now, the templates look like this (note that the whitespace is messed up in the code block here โ something I will fix at a later date!):
<?php
// This is a generated file. Refer to the file located at /assets/src/patterns/04-components/c-label/c-label.twig for adjusting this markup.
?>
<span class="c-label <?php echo esc_attr( $modifier_class ?? '' ); ?> <?php echo esc_attr( $c_label_classes ?? '' ); ?>" <?php echo esc_attr( $c_label_data_attr ?? '' ); ?>>
<?php if ( $c_label_url ) { ?>
<a href="<?php echo esc_url( $c_label_url ?? '' ); ?>" class="c-label__link <?php echo esc_attr( $c_label_link_classes ?? '' ); ?>">
<?php } ?>
<?php echo esc_html( $c_label_text ?? '' ); ?>
<?php if ( $c_label_url ) { ?>
</a>
<?php } ?>
</span>
Handling data attributes has been a little weird, but as you can see above (if you scroll right), we mostly figured it out by echoing the entire attribute and value as HTML.
Also, it was, surprisingly, pretty simple to register a command to execute the parser function along with the production build command (we do not have the luxury of compiling assets on the sever, unfortunately):
// package.json
"scripts": {
"prod": "NODE_ENV=prod pmc-build-utils prod && npm run parser",
"parser": "php -r 'include \"../inc/classes/class-twig-parser.php\";
}
Primitive Patterns and Modules
The “primitive patterns” are the base markup patterns in the system, and are called components and objects. The primitive patterns will have a bit of CSS that defines their base characteristics โ things like font family or some basic layout (if that can always be predicted). Margins, padding, color, and font-size are always the responsibility of utilities. Until we have synced up more with the designers and the designs themselves are to spec, it’s basically impossible to have those declarations as part of a pattern and have it be reusable.
One example of a primitive pattern is c-tagline
โ a component, or “atom” in atomic design โ that looks like this, pretty much just a p
:
<p class="c-tagline {{ modifier_class }} {{ c_tagline_classes }}">{{ c_tagline_text }}</p>
Even though it’s so small and simple, this p
now has a name and a purpose and is part of a system. An example of an object (equivalent to a “molecule” in atomic design) is o-nav
:
<nav class="o-nav {{ modifier_class }} {{ o_nav_classes }}" {{ o_nav_data_attributes }}>
{% if o_nav_title_text %}
<h4 class="o-nav__title {{ o_nav_title_classes }}" {{ o_nav_title_data_attributes }}> {{ o_nav_title_text }}</h4>
{% endif %}
<ul class="o-nav__list {{ o_nav_list_classes }}" {{ o_nav_list_data_attributes }}>
{% for item in o_nav_list_items %}
<li class="o-nav__list-item {{ o_nav_list_item_classes }}" {{ o_nav_list_item_data_attributes }}>
{% include "../../04-components/c-nav-link/c-nav-link.twig" with item %}
</li>
{% endfor %}
</ul>
</nav>
Our rule is that a component becomes an object as soon as it includes another component or object.
These primitive patterns are then assembled into modules
which are the highest level element and equivalent to “organism” in atomic design. Their names should come from a product or project manager, not a developer, and are generally specified in a JIRA ticket. Other than the name of their file or possibly some specific data, there should be no reference to the name of module in CSS or JavaScript the codebase, except in the event that some CSS doesn’t fit into utilities or algorithms. A module is an assemblage of existing patterns and utilities to form a probably unique piece of interface.
For example, article-tags
contains primitive patterns, wrapper markup and utility classes, plus a single class for a declaration that didn’t fit into algorithms or utilities:
<div class="article-tags // u-flex u-align-items-center u-flex-direction-column@mobile-max u-justify-content-center pmc-u-margin-b-1">
{% include "../../04-components/c-label/c-label.twig" with c_label %}
{% include "../../05-objects/o-nav/o-nav.twig" with o_nav %}
</div>
It’s not uncommon for a module
to contain a single include
of a primitive pattern โ even if it could just be o-nav
, it’s still useful to have the business name associated with its utility class configuration, which looks like this in a PHP template:
$posts_tags = \Cantaloupe\Inc\Article::get_instance()->get_post_tags();
if ( $posts_tags && ! empty( $posts_tags ) ) {
$article_tags = [
'c_label' => [
'c_label_text' => __( 'Read More About:', 'pmc-cantaloupe' ),
'c_label_classes' => 'pmc-u-font-size-16 pmc-u-margin-r-025',
],
'o_nav' => [
'modifier_class' => 'o-nav--horizontal',
'o_nav_list_classes' => 'u-justify-content-center@mobile-max pmc-u-margin-a-00 u-flex-wrap-wrap',
'o_nav_title_classes' => '',
'o_nav_list_item_classes' => 'u-text-transform-uppercase pmc-u-font-size-12 a-icon-before a-icon-forward-slash pmc-u-margin-l-050 pmc-u-margin-b-050@mobile-max',
'o_nav_list_items' => $posts_tags,
],
];
\PMC::render_template(
sprintf( '%s/template-parts/patterns/modules/article-tags.php', untrailingslashit( CHILD_THEME_PATH ) ),
$article_tags,
true
);
}
When something is a one-off in our system, it means the markup is written outside of Twig, only in PHP templates. I shared our documentation about One-off Patterns in this separate post.
Utility class generators are a success
Sass utility generators outlined in the last Chronicles post are working great, and anything not accounted for in a generator can be written out as needed to the same file (but is not prefixes with pmc-*
). This is a nice way to start isolating design tokens, and see where we can make compromises after the project.
The font-size generator, for example, looks like this:
@include pmc-u-font-size(
(
($font-size-8,)
($font-size-10,)
($font-size-12,)
($font-size-14,)
($font-size-16,)
($font-size-18,)
($font-size-20,)
($font-size-24,)
($font-size-28,)
($font-size-32,)
($font-size-50,)
($font-size-12, mobile-max)
($font-size-14, mobile-max)
($font-size-18, mobile-max)
($font-size-26, mobile-max)
($font-size-18, tablet,)
($font-size-24, tablet,)
($font-size-28, tablet,)
($font-size-38, tablet,)
($font-size-14, desktop,)
($font-size-18, desktop,)
($font-size-24, desktop,)
($font-size-28, desktop-xl,)
// ... and there's more ...
CSS Algorithms
Algorithms are a name for the CSS you can’t really handle with utilities that isn’t necessarily associated with a component. For us, utilities are single declaration rule-sets, property: value;
and maybe with a breakpoint or a :hover
, and named exactly according to the declaration. Component and object styles are very, very sparse and rudimentary if they exist at all.
Anything more than that โ like pseudo-elements, specific CSS Grid specifications, or patterns requiring multiple selectors that relate to each other (e.g. absolute positioning on a parent, positioned child) is an algorithm. In our system, algorithms are not associated with components or objects, but in another system, they certainly could be.
Here’s one thing that could be cause for concern, but also maybe not: I have written 95% of the algorithms we have so far, but there is another front-end developer on the team. Does this paradigm only make sense to me? That’s dangerous.
This could be because a) I have several years more CSS experience, or b) algorithm is not a term most would associate with CSS, so it might be off-putting at the moment. It’s probably a mixture of the two, and I’m okay with that. However, I do think that, to write an algorithm, a developer needs to really understand CSS, otherwise this paradigm could prove to be brittle and disastrous.
So far, there are 20 algorithms in the Cantaloupe system. Here are some names and descriptions of a about half of them:
.a-article-grid
and.a-archive-grid
โ Two specifiic CSS Grid specifications with fallbacks..a-become-close-button
โ LOL naming! This is actually a good name though. Apply this algorithm to an element and it does, indeed, become anX
button by way of borders and psuedo-elements..a-content
applies base typography styles to an element intended to hold the post content..a-glue
is the evolution of.u-glue
, which I wrote about here, and is a way to group absolute positioning declarations..a-grid
sets the display to grid, then contains modifiers that update a custom property to change the amount of columns. This is a pretty cool one and comes from a Sass generator. It is deserving of it’s own blog post..a-scrollable-grid
has proven super useful and works well with.a-grid
. It makes a row of elements have a horizontal overflow at a certain breakpoint..a-social-color
contains a bunch of selectors for social buttons that have a background hover color that would be really annoying to do with utility classes..a-unstyle-button
,.a-unstyle-link
,.a-unstyle-list
โ you can probably imagine what these do..a-icon
is also a pretty cool one deserving of its own blog post โ it adds an icon inside abefore
orafter
pseudo-element by way of a custom property.
Also, because it’s interesting and exciting, here is the breakdown of all our patterns at about 70-80% completion of the interface development:
- 12 components
- 12 objects
- 20 algorithms
- 12 utility categories (e.g. u-width, u-border, u-flex)
- 27 modules (most of these do not contain their own CSS)
- 20 one-offs (these are basically modules but their Twig and PHP markup are not linked for whatever reason)
What about JavaScript?
Well…what about it?
We can pretty much copy everything from the Rolling Stone’s code-base, the most recently redesigned of PMC’s brands. The features and designs are similar, so it contains the majority of the functionality Cantaloupe needs.
It’s funny how little JavaScript I write….still! Someday PMC’s sites will embrace Gutenberg and I’ll be full steam ahead in React, but for now, client-side JavaScript is mostly a solved problem for us. I’ve actually written more Node for tooling in the past months than client-side JavaScript for the brands’ sites.
Based on conversations in the Twitter-verse, I am an outlier, but I’m sure there are lots of people with similar experience!
What’s coming up?
I am so impressed with myself for finishing not one, but two Designgineering Chronicles posts this weekend. Good job, Lara! So, what’s coming up for me is a beer at Hop Farm Brewing with my family as I am back visiting Pittsburgh for a few days.
Oh, what’s coming up with Cantaloupe? Well…besides finishing the thing, here are a couple of thoughts.
Store utility class configurations in a centralized place
Previously, in the IndieWire pattern library, the biggest problem was redundancy in markup. There were two copies of a pattern’s markup, both in Twig and the actual markup in the PHP template. Now, the markup is no longer redundant, but the utility class configurations are: the values for data like o_nav_list_classes
and o_nav_title_classes
are stored in both JSON files for KSS, and the PHP objects, so the pattern library still becomes out of date quickly.
This will definitely be something to tackle after Cantaloupe, and in general, this is a much better problem to have than redundant markup. What we need is a centralized way to store the pattern schemas that can be consumed by both Twig and PHP.
More work with Sparkbox
We have some consulting hours left with Sparkbox for post-project work, and I’m looking forward to tackling the above problem with their developers โ potentially via a custom KSS builder, for the shared config โ as well as working with PMC engineers to figure out the PHP side of it.
Build out the video player as the first share-able module
One element we will be tackling this sprint is a video player. The element is almost exactly the same on the Rolling Stone, and IndieWire has also requested it. It is a prime candidate to be the first shareable module across brands. I don’t know exactly what publishing a module looks like quite yet, but I’ll be thinking about it as I develop the player.
Re-evaluate webpack chunking
There are some cool possibilities for very smart dependency management with the patterns. At the moment, I do not have chunking in webpack set up in the ideal way, so will need to address that probably after Cantaloupe as well.
Create a design system roadmap
First and foremost on my list for post-Cantaloupe, is to talk to several folks at PMC and come up with a roadmap for the design system. The first step will probably be getting the brands to use PMC Build Utils for their asset compilation, but there are also other redesigns in the pipeline that I want to factor into the map. Part of these meetings will be syncing up with the design team to see what verbiage and systems stuff we can incorporate into their workflow to help the developers better interpret the design artifacts.
Okay, that’s all!
I’m guessing I will not write another Chronicles until Cantaloupe launches in mid-April (or maybe possibly likely after), but I got a lot of information out in these last couple of posts so I feel good about it.
In the mean time, there will probably be some more CSS algorithms oriented content on my blog because people seem to like that, and I like writing about it ๐
Thanks for reading, and bye for now!