Hi! I’m Brad!

I’m an award-winning software developer from Laramie, Wyoming.

Tag: Sustainable Styles

  • Sustainable Styles: An Object-Oriented twist on BEM

    I have been working in the web for a long time, and I have worked full-time professionally at UL for almost 5 years.  In that time, I’ve learned a lot about how developers perceive CSS, and why so many projects are absolutely littered with novice-level CSS.

    BEM was introduced a few years ago to deal with one of CSS’ biggest problems: taxonomy, the art of naming things.  Its primary goal was the provide a sane, sustainable method for writing class names.  Another goal was to create a sustainable approach for changes in state..  For example, BEM provides an answer for “How do we style a blog post (block) that is featured (modifier)?”  Consider some of these candidates

    .blogPostFeatured {}
    .blog-post.featured {}
    .blog .post.featured {}

    None of these fully address the application of a block-specific featured state, and the intended portability of a CSS class. BEM methodology here is going to be as atomic as possible with components (like a post), and as verbose as possible to avoid collisions.  For example, using .featured with .blog is fine, but what if another entity, like .page comes along, and also uses the .featured class?  What happens when these two entities (posts and pages) need different styles for featured?

    Tweaking .featured for one entity will also affect the other.  Unintended changes across the app lead to what I call this the Pick Up Sticks problem.  For this reason, using verbose Modifiers is the solution to the problem.  Consider the following SCSS. Notice that .post--featured only works with .post, and so the modifier’s effect is isolated to the block.

    .post {
        &.post--featured {} // => .post.post--featured
    }
    
    .page {
        &.page--featured {} // => .page.page--featured
    }

    Now, examine the accompanying HTML markup…

    <!-- Works great! -->
    <div class="post post--featured"></div>
    <div class="page page--featured"></div>
    
    <!-- Featured states have no effect when misapplied -->
    <div class="page post--featured"><div>
    <div class="post page--featured"></div>

    Essentially, this creates namespaces for the .page and .post blocks, and any of the modifiers for these blocks are useless outside of the associated namespaces.  This is a powerful, clean, sustainable approach that will help you tame and harness the cascade, and master CSS.

    Styling relationships

    When parent-child relationships exist within a data model, it is common to see that relationship reflected by markup and styles. Consider the following data model, written in TypeScript.

    class Post {
        public comments: Comment[];
    }
    
    class Comment {}

    It’s easy to see that Post has many Comment, as indicated by the Post.comments array of Comment objects. How do we turn this into a sustainable CSS design system?

    First, start with two root-level selectors to represent the Post and the Comment.

    .post {}
    .comment {}

    Since Post has a property to contain many comments, represent this relationship in with an Element (preceded by two underscores) in the SCSS:

    .post { // block
        .post__comments {} // element
    }
    .comment {} //block

    Have a look at the markup that could be styled by this stylesheet…

    <div class="post">
        <div class="post__comments">
            <div class="comment">…</div>
            <div class="comment">…</div>
            <div class="comment">…</div>
        </div>
    </div>

    So far so good!  It’s clean, easy to understand, easy to maintain, and it’s only the slightest bit verbose.  You can see that the Post block contains a Post Comments Element, and we put many Comment blocks into the Post Comments Element.

    Let’s continue to style two Comment blocks when they are next to each other in the Post Comments Element…  We can use a variant of the lobotomized owl to create a simple spacing system that doesn’t bring unnecessary gaps of whitespace in our page.

    .post {
        .post__comments {
            > .comment + .comment {
                margin-top: 1.5rem;
            }
        }
    }
    

    Styling boolean states

    Next, consider representing boolean states on a Block.  The boolean test might look like this…

    class Post {
        comments: Comment[] = [];
        get hasComments(): boolean {
            return this.comments && this.comments.length > 0
        }
    }

    The boolean is represented in SCSS as a Modifier:

    .post {
        .post--has-comments {} // post.hasComments() === true
        .post--no-comments {} // post.hasComments() === false
    
        .post__comments {}
    }

    When the post has comments, our markup will represent this by including the modifier with the block:

    <div class="post post--has-comments">
        <div class="post__comments">
            <div class="comment">…</div>
            <div class="comment">…</div>
            <div class="comment">…</div>
        </div>
    </div>

    We are now able to modify the Post appearance, as well as the children, when the has-comments modifier is added:

    .post {
        // post.hasComments() === true
        &.post--has-comments {
            background: url('comments-icon.png') top right;
    
            // post.comments when post.hasComments
            .post__comments {
                border: solid 1px #ccc;
            }
        }
    
        // post.hasComments() === false
        &.post--no-comments {
            .post__comments { display: none }
        }
    }

    The has-comments state can be used to leverage the cascade (the C in CSS), to further affect descendant elements, without a bunch of bloat.

    <div class="post post--has-comments">
        <div class="post__comments"></div>
    </div>

    Our class names are written to represent our schema, its relationships, and its states.  This prevents the pick-up sticks problem, and makes the entire project easier to style.  Consider a new scenario where a blogging platform allows multiple blogs to exist, so we need to style a series of posts…

    class Blog {
        title: string;
        posts: Post[]
    }

    No problem! We already have styles for Post so we just need to author the styles for the Blog block which contains many Post blocks. We’ll add a simple spacing rule, and some nice styles for a title. We also benefit from the styles that were written for Comments within Post.

    .blog {
        .blog__title { font-weight: bold; font-size: 2.5rem; }
        .blog__posts {
            // within .blog__posts, any direct-descendant post that is 
            // after any post gets a margin-top to space things out
            > .post + .post { margin-top: 1.5rem; } 
        }
    }

    By being verbose, and using a reliable strategy to map state and schema to CSS appearance, we avoid a host of architectural issues that have been confusing developers for decades.

    Putting it together

    Here’s a cheat sheet to help apply BEM methodology to the mapping of objects to class names.

    When you’re styling…Use…For example…
    A business entity (a row in a database)
    a Block
    .post
    .comment
    .user
    A property on an entity (a column in a database)
    an Element
    .post__title
    .post__content
    A child relationship
    an Element filled with blocks
    .post__comments > .comment + .comment
    .comment__author > .user
    A state
    a Modifier
    .post--is-featured
    .comment--is-post-author
    .user--is-admin