CSS Best Practice: Class Composition


This is not a new topic, but I still see a lot of production code which could have been simplified by CSS class composition. So here’s a little hint about how to do it.

Let’s say you’re building a list in which items can be flagged as new or deleted, or even both at the same time. Here are the requirements for the behavior:

  • The items are displayed in a list, in normal text;
  • The new items are in bold with a yellow background;
  • The deleted items have gray text and are stroked.

Some beginners in CSS would have the reflex to do a class for each possibility. So they would create something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.new {
    background-color: yellow;
    font-weight: bold;
}
 
.deleted {
    color: gray;
    text-decoration:line-through;
}
 
/* This is wrong, read on */
.new-deleted {
    background-color: yellow;
    font-weight: bold;
    color: gray;
    text-decoration:line-through;
}

I must admit, in my first run through with CSS, this was the kind of code I wrote myself. While this seems clear and easy to do, its maintainability is quite bad. What happens if we wish to have starred items in our list, where we want the bullet to be a star instead of a simple circle? Then we’d need to have the following classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
.new {
    background-color: yellow;
    font-weight: bold;
}
 
.deleted {
    color: gray;
    text-decoration:line-through;
}
 
/* This is wrong, read on */
.new-deleted {
    background-color: yellow;
    font-weight: bold;
    color: gray;
    text-decoration:line-through;
}
 
.star {
    list-style-image:url('star.png');
}
 
/* This is wrong, read on */
.new-star {
    background-color: yellow;
    font-weight: bold;
    list-style-image:url('star.png');
}
 
/* This is wrong, read on */
.deleted-star {
    color: gray;
    text-decoration:line-through;
    list-style-image:url('star.png');
}
 
/* This is wrong, read on */
.new-deleted-star {
    background-color: yellow;
    font-weight: bold;
    color: gray;
    text-decoration:line-through;
    list-style-image:url('star.png');
}

See how we doubled our classes for the addition of a single situation? For the mathematicians among you, in the case where you’ll have n situation (new, deleted and star means 3 situation), you’ll end up with 2n – 1 CSS class (7 in our example). Furthermore, it is hard to remember in which order the keywords are used in the class, is it deleted-star-new? star-deleted-new? new-star-deleted? and so on…

Class Composition

This is where class composition kicks in. You can provide more than one class to a HTML element. Here’s the CSS we’ll use:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* content of style.css */
.new {
    background-color: yellow;
    font-weight: bold;
}
 
.deleted {
    color: gray;
    text-decoration:line-through;
}
 
.star {
    list-style-image:url('star.png');
}

And here’s the HTML to which it will be applied:

1
2
3
4
5
6
7
8
9
10
<ul>
    <li>My standard item</li>
    <li class="new">My new item</li>
    <li class="star">My starred item</li>
    <li class="deleted">My deleted item</li>
    <li class="new deleted">My new and deleted item</li>
    <li class="star deleted">My starred and deleted item</li>
    <li class="star new">My starred and new item</li>
    <li class="star new deleted">My starred, new and deleted item</li>
</ul>

This simplifies a lot our CSS, and lets us forget about the order of the classes to apply.

Targeting a special element with multiple classes

There’s one notion which is important to know when using class item. Sometimes, you want to apply many classes which are in conflict together, or you want to target a specified combination of classes.

Let’s say that we wanted starred and deleted items to have red text, here’s the CSS how to do it:

1
2
3
.star.deleted {
    color: red;
}

When combining the CSS classes that way, you target only the items which have the star AND deleted classes applied.

Here’s a clearer example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* This targets all the items with the star class */
.star {
    list-style-image:url('star.png');
}
 
/* This targets all the items with the deleted class */
.deleted {
    color: gray;
    text-decoration:line-through;
}
 
/* 
   This applies to all the items which have BOTH the star and deleted class.
   And since the selector is more precised, this text color will be used instead of the deleted class.
*/
.star.deleted {
    color: red;
}
 
/* This is only a reminder, this will apply to all deleted items which are child of a star element */
.star .deleted {
    color: blue;
}

Comments are closed.