6 Examples of CSS Custom Properties Improving OOCSS

Nicole Sullivan defines A CSS object like this:

Basically, a CSS “object” is a repeating visual pattern, that can be abstracted into an independent snippet of HTML, CSS, and possibly JavaScript. That object can then be reused throughout a site.

A popular interpretation of this has led to us creating classes to form layouts or objects – classes that control the positioning of elements (structure) without applying styling. They’re designed to be re-usable and flexible.

Recently I experimented with CSS custom properties (sometimes called CSS variables) and applying them to these objects. I’ve found that they actually make these objects even more flexible while keeping the selector strength down to a single class. That allows any “component” or “module” to adjust the style – even on the child elements.

In this article I’ll show six examples of CSS objects and show how CSS custom properties can increase their flexibility. Before we get started, I want to say that the names I’m using are not official. You might know these objects by a different name or just prefer to call them something else – the techniques are the important part here.

6. Box

The box object squares up contents. It has padding and it removes the top margin from the first child and bottom margin from the last child, keeping the spacing around the contents consistent. In its most simple form, the object looks like this:

.o-box {
    padding: 0.5em;
}

.o-box > :first-child {
    margin-top: 0;
}

.o-box > :last-child {
    margin-bottom: 0;
}

However, if we set the padding as a CSS custom property, we can access it in a child element to create something that I like to call a “lip”. The lip has negative side margins and side padding equivalent to the box’s padding, allowing us to have a background or border that spans the full width of the box.

.o-box {
    --o-box-spacing: 0.5em;
    padding: var(--o-box-spacing);
}

.o-box__lip {
    margin-left: calc(var(--o-box-spacing) * -1);
    margin-right: calc(var(--o-box-spacing) * -1);
    padding-left: var(--o-box-spacing);
    padding-right: var(--o-box-spacing);
}

As a CSS custom property, we can simply sub-class the o-box class to adjust the spacing and the sides of the lip.

.o-box--example {
    --o-box-spacing: 2em;
}

If you want to remove the spacing from the box you must include a unit rather than just setting --o-box-spacing to 0. Including a unit allows the calc() to continue working. In this situation, using 0 would cause the calc() to fail and create blank value for the margin-left and margin-right properties. This would give the same effect as 0 but you may not want that in every scenario. I’ve found that 0px works more consistently then 0%.

5. Stack

Vertically stacking elements doesn’t get enough credit, especially when you check your design to see how often it’s actually done. An object that handles the heavy lifting in that regard can be very useful. A simple version could look like this:

.o-stack {
    display: flex;
    flex-direction: column;
}

.o-stack__item {
    margin-bottom: 0.5em;
}

.o-stack__item:last-child {
    margin-bottom: 0;
}

This becomes a lot better when you replace the set amount of margin-bottom with a CSS custom property. You can also create a “height” CSS custom property and set it to initial as a placeholder.

.o-stack {
    --o-stack-item-height: initial;
    --o-stack-spacing: 0.5em;
    /* ... */
}

.o-stack__item {
    height: var(--o-stack-item-height);
    margin-bottom: var(--o-stack-spacing);
}

We now have the ability to define a height of, for example, 10em and to modify the spacing. To remove the bottom margin from the last child, we can just set just explicitly set it to 0 but I found a a really useful trick when we implicitly set it using CSS custom properties.

.o-stack__item:last-child {
    --o-stack-spacing: 0;
}

Not only does that set the bottom margin but it gives us a way to regain it, either by sub-classing the item or the parent.

.o-stack__item--maintain {
    --o-stack-spacing: inherit;
}
/* or */
.o-stack--maintain > :last-child {
    --o-stack-spacing: inherit;
}

The inherit keyword means “give this the value from the parent” which, in our case, is 0.5em. It’s a simple and extremely useful keyword and works really well with CSS custom properties.

4. Pack

For horizontally laying out elements I tend to use a “pack” object. It’s a similar mentality to the “stack” object with a couple of more interesting CSS custom properties. Let’s start with a basic, CSS custom property-free example.

.o-pack {
    display: flex;
    flex-direction: row;
    width: 100%;
}

.o-pack__item {
    margin-left: 0.25em;
    margin-right: 0.25em;
}

.o-pack__item:first-child {
    margin-left: 0;
}

.o-pack__item:last-child {
    margin-right: 0;
}

With CSS custom properties, we can define the spacing and break it down into both sides. We can also update the first and last child selectors – remember to include the unit.

.o-pack {
    --o-pack-spacing: 0.5em;
    --o-pack-spacing-left: calc(var(--o-pack-spacing) / 2);
    --o-pack-spacing-right: calc(var(--o-pack-spacing) / 2);
    /* ... */
}

.o-pack__item {
    margin-left: var(--o-pack-spacing-left);
    margin-right: var(--o-pack-spacing-right);
}

.o-pack__item:first-child {
    --o-pack-spacing-left: 0px;
}

.o-pack__item:last-child {
    --o-pack-spacing-right: 0px;
}

Remember that we can use inherit to restore the previous value. We can also add placeholders for height and width for the items, also taking into account the spacing we’ve created.

.o-pack {
    --o-pack-item-height: initial;
    --o-pack-item-width: initial;
    /* ... */
}

.o-pack__item {
    height: var(--o-pack-item-height);
    width: calc(
        var(--o-pack-item-width)
        - var(--o-pack-spacing-left)
        - var(--o-pack-spacing-right)
    );
    /* ... */
}

Setting the “width” and “height” CSS custom properties don’t need to have the placeholder value initial (trying to access CSS custom properties before setting them won’t throw any errors) but I’ve found it useful to include them to give developers a better idea about how to use them.

3. Waffle

Laying out elements in a grid-like pattern requires the waffle object. I’ve called it “waffle” instead of something like “grid” to avoid confusion with the new CSS grid and existing grid patterns. Unlike those, the waffle object has a series of children that are the same width and has consistent spacing between them all. The basic CSS for this involves adding a margin on the items (to create the spacing) and an equal negative margin on the parent. The children edges line up with other elements but also have the spacing.

.o-waffle {
    display: flex;
    flex-flow: row wrap;
    margin: -0.25em;
    width: calc(100% + 0.5em);
}

.o-waffle__item {
    margin: 0.25em;
    width: calc(25% - 0.5em);
}

The main benefit that CSS custom properties offer us are the abilities to define the spacing in a single place and set the number of columns instead of relying on specific percentage widths.

.o-waffle {

    --o-waffle-columns: 4;
    --o-waffle-item-width: calc(100% / var(--o-waffle-columns));
    --o-waffle-spacing: 0.5em;

    display: flex;
    flex-flow: row wrap;
    margin: calc(var(--o-waffle-spacing) / -2);
    width: calc(100% + var(--o-waffle-spacing));

}

.o-waffle__item {
    margin: calc(var(--o-waffle-spacing) / 2);
    width: calc(var(--o-waffle-item-width) - var(--o-waffle-spacing));
}

This isn’t the most complicated CSS object ever written but it’s one of the best examples of how CSS custom properties can make CSS objects easier to work with. All that needs to happen to make a waffle of 3 columns rather than 4 is this:

.o-waffle--wider-columns {
    --o-waffle-columns: 3;
}

Adjusting the spacing is just as easy – change a single property to update the parent and children.

2. Ratio

This object has a couple of names. The most famous is probably “embed” from Bootstrap. Quite simply, the ratio object creates a rectangle of a certain aspect ratio. It’s usually used to scale videos. The simple version looks like this (if you’re familiar with Inuit then this will look very familiar):

.o-ratio {
    display: block;
    position: relative;
}

.o-ratio::before {
    content: "";
    display: block;
    padding-top: 100%;
    width: 100%;
}

.o-ratio__content {
    bottom: 0;
    height: 100%;
    left: 0;
    position: absolute;
    top: 0;
    width: 100%;
}

This is a simple 1:1 ratio with the padding-top in the pseudo-element. The problem is that we have to work out the padding-top each time and add a note or obvious class name to help other developers understand the aspect ratio.

.o-ratio--4\:3::before {
    padding-top: 75%;
}

.o-ratio--16\:9::before {
    padding-top: 56.25%;
}

.o-ratio--16\:10::before {
    padding-top: 62.5%;
}

With the aid of CSS custom properties, we can make the aspect ratio much more explicit as well as allow us to create any ratio in the future without having to do the calculations manually.

.o-ratio {
    --o-ratio-width: 1;
    --o-ratio-height: 1;
    /* ... */
}

.o-ratio::before {
    padding-top: calc((var(--o-ratio-height) / var(--o-ratio-width)) * 100%);
    /* ... */
}

The sub-classes then just define the width and height values without any unit.

.o-ratio--4\:3 {
    --o-ratio-width: 4;
    --o-ratio-height: 3;
}

.o-ratio--16\:9 {
    --o-ratio-width: 16;
    --o-ratio-height: 9;
}

.o-ratio--16\:10 {
    --o-ratio-width: 16;
    --o-ratio-height: 10;
}

While I was battle-testing these objects, I needed the client’s logo to scale upwards and using the radio object seemed sensible. I simple created a variant that looked like this:

.o-ratio--logo {
    --o-ratio-width: 215;
    --o-ratio-height: 60;
}

I realise that I’m biased, but that makes more sense to me than a padding-top of 27.906976744186046%.

1. Media

The poster-child of OOCSS, the media object lays text next to an image. There are a couple of variations but I prefer the “picture” to be at the top and the “text” to be vertically centred but adjust the alignment to the top if the text is taller than the picture.

.o-media {
    display: flex;
    flex-direction: row;
    width: 100%;
}

.o-media__left {
    align-self: flex-start;
    margin-right: 0.5em;
}

.o-media__right {
    align-self: flex-start;
    margin-left: 0.5em;
}

.o-media__body {
    align-self: center;
    flex-grow: 1;
}

The goal of CSS custom property-enabled CSS objects is to allow us to change a parent’s properties and see results in either that parent or its children. We shouldn’t have to adjust properties on the child for a change to take effect. This means that the media object needs 3 CSS custom properties: one for the side alignment, one for the body alignment and one for the spacing.

.o-media {
    --o-media-side-alignment: flex-start;
    --o-media-body-alignment: center;
    --o-media-spacing: 0.5em;
    /* ... */
}


.o-media__left {
    align-self: var(--o-media-side-alignment);
    margin-right: var(--o-media-spacing);
}

.o-media__right {
    align-self: var(--o-media-side-alignment);
    margin-left: var(--o-media-spacing);
}

.o-media__body {
    align-self: var(--o-media-body-alignment);
    /* ... */
}

The ability to quickly sub-class the media object to set vertical alignment is actually the flag object but without the need to wrap the image in another element. CSS custom properties and flex have allowed us to combine these two objects into one, easy-to-remember object. For example, a media object variant with the image and text aligned to the bottom would look like this:

.o-media--align-bottom {
    --o-media-side-alignment: flex-end;
    --o-media-body-alignment: flex-end;
}

Conclusion

CSS custom properties can make manipulating CSS objects so much easier. Although browser support isn’t high enough for everyone to enjoy these benefits yet, I hope that this article has given an indication of some of the things that will be possible shortly.

Leave a Reply

Your email address will not be published. Required fields are marked *