C3 in practice

What I learned while refining my CSS methodology (C3) through a series of test cases.

7 minute read

While my CSS methodology (C3) derives from my own style of writing CSS, developed over many years, it was, at the time of formalising it, pretty untested. For two reasons:

  1. Until now I'd never considered myself to be using a methodology. There were no rules in place, so my approach would have meandered, depending on circumstance.
  2. While the ruleset I am developing has been largely shaped by my practice, it has also been informed by theoretical musings, prompted by this current task of formalising the way I work. The combination of all this had yet to be put back into practice.

To put the new methodology to the test, I worked reflectively on a series of test cases.

Test case one #

I first went back and refactored some of the code behind this blog. The code was several years old, and offered a lot of scope for the refactoring. For the most part this was a satisfying but uneventful test, with the new methodology only being helpful. I noticed on reflection however, that in one place I had deviated slightly from my rules. The old codebase had the following selector:

css
.post-header .post-image.halftone {}

This was used to style some of the post header images to look a bit like a halftone print. Each of my blog posts has a large featured image, for this I will generally either create a bespoke illustration, or grab a Creative Commons photo, and stylise it. The latter is when the halftone class gets applied. In my refactored code, the above became:

css
.post-header_image[data-image-effect="halftone"] {}

Structurally this brings the selector inline with C3, but it's still breaking one rule - it is describing appearance. This could be rectified pretty easily in this case, by changing it to something like:

css
.post-header_image[data-type="raw"] {}

I think the reason I held off from this initially was to leave open the possibility of having multiple effect options. If I wanted to to be able to give post X the halftone effect, and post Y a glitch effect, for example. According to my new rules, rather than do this

css
.post-header_image[data-image-effect="halftone"] {}
.post-header_image[data-image-effect="glitch"] {}

you would need to include some metadata, that does not describe the appearance, but that could be used as a hook for it. This feels potentially a little unreasonable, if not unrealistic. I can imagine ways this could work, based on perhaps thematic tags attached to the post, but there are certainly limitations here. Would it be unreasonable to want to choose an effect on a whim, or because it looks good for a reason you can't put into a word or two? You might argue that the pragmatic answer here would be "there will be situations when you need to bend the rules", but if part of what I'm trying to achieve is the ability to change appearance without touching any HTML, then having any exception surely undermines the effort.

I needed to step back at this point and recognise that the ideology that I am pursuing is bigger than a naming convention. I think what the above example demonstrates is that for the methodology to be effective, its principles need to be applied consistently throughout the project. In the image effect scenario the "problem" starts with the fact that visual characteristics are being transferred from the content to the stylesheet (via the data attribute). This information existing in the metadata is OK, but when it gets passed into the content, it becomes at odds with the principles of the methodology; the direction of travel for styles should only be from CSS to HTML. This means that a description of an image effect, has no business being in the data attribute in the first place. So the answer to the above quandary (for the purists, at least), would be to go with something like the [data-type="raw"] solution; and if you want multiple effects, they should be initiated in the stylesheet (based on something like the post title, or category), or output as CSS directly. By outputting as CSS I mean something like adding a style tag to the template, or perhaps a link to an additional style sheet.

Test case two #

Even having just thrown up one selector for scrutiny, the testing felt fruitful. For the next test I wanted to build something in an environment that might have its own ideas about how CSS should be written. I turned to a JavaScript framework. I decided to build a Vue.js app, using Nuxt.js. When using frameworks such as Vue, styles are typically coupled with markup, to form self contained components. I wanted to make sure that my C3 notion of components was compatible. Vue can also add useful classes for you, for things like transitions, for example. How would this fit with my methodology?

I had recently started keeping a record of gigs I went to. To ensure a minimal burden, I limited myself to a two word summary of each gig (in hindsight, this added the burden of trying to avoid constant puns). I decided to use this as content. Each gig could be a component, that appears on its own page, and also on an index page. I could add some sort of animation between each page, to test the transition classes. That's what I built, you can see the result at smth.uk/reviews.

The concept of component in Vue and in C3 worked perfectly together, I felt no conflict of opinions between the two. The page transitions on the other hand were slightly problematic. First of all, Vue requires that the transition selectors be classes. Transitions are part of Context in C3 parlance, and to this point were required to be declared as attributes. So something like data-transition="enter". My rule on this was clearly too restrictive. I needed to add a new class syntax to be used for context (because every class needs to be recognisable as Component, Child, or Context). I decided to extend the existing paradigm of key (attribute) and value into a class syntax. This will take the form of key--value, for example transition--enter. I was considering specifying that when I class is used for context, it should still be written in the CSS using the attribute syntax: [class="transition--enter"], but abandoned this idea when discovering that Vue transitions don't work with this syntax (presumably due to some required regular expression).

Test case three #

There's nothing to focus the mind like a real live project, so the next case was a small client site. By this point the methodology felt solid, but on reflection I did find one aspect that could be refined. I was setting and overriding some element styles, in the global style sheet, something like:

css
p {
/* global paragraph styles */
@nest .page-footer & {
/* styles for paragraph in footer */
}
}

I think it sometimes makes sense to do this, but it occurred to me that it was introducing some uncertainty about in which style sheet that style would be found. It could only be in one of two places (the global style sheet or the footer style sheet), but in my opinion, having to look in two places is significantly worse than knowing exactly where it will be. For this reason I created a rule that specifies the context always be added in the component style sheet, unless the element in question cannot have a class applied to it. For example a p generated from Markdown. In this case context is added to the global element code block. This way, when looking at the element in HTML, you know, due to the class name or lack of, exactly where to look for the style.

Start a conversation. @ me on Mastodon.