Arrange a Masterclass

‘Scope’ in CSS

Written by on CSS Wizardry.

N.B. All code can now be licensed under the permissive MIT license. Read more about licensing CSS Wizardry code samples

Table of Contents
  1. Global scope
    1. Featured case study: NHS
  2. Loose selectors
  3. Scope in CSS
    1. Nesting?
    2. No nesting!
    3. BEM
  4. Scope all the things?
  5. In summary

One thing you will no doubt be familiar with, as a web developer, is the idea of scope. Wikipedia’s introduction to the subject:

In computer programming, a scope is the context within a computer program in which an identifier is in effect, and can be resolved to an entity – when the entity is visible.

A super simple example of scope in JavaScript:

var foo = "foo";

function myFunction() {
    var bar = "bar";
}

console.log(foo);
console.log(bar);

Running this, and keeping an eye on your console, you should see two lines:

foo
Uncaught ReferenceError: bar is not defined

The first line reads foo. This is because foo is defined in global scope and is later logged in global scope. bar is also logged in global scope, but is defined in ‘local’ scope, inside myFunction(), which is why the second line reads Uncaught ReferenceError: bar is not defined. We don’t have access to the bar variable because it exists in a different scope.

Scope basically deals with what is visible to a program based on the current scope, and the scope of the thing the program is trying to interact with.

Global scope

Now, I am far from a programmer. I have a theoretical understanding of scope, but most, if not all practical, real life knowledge is nonexistent. Please forgive me if my details are a little rough around the edges.

Global scope is typically a bad thing in programming. Global scope means defining and using variables with no sandboxing; variables in global scope are unpredictable because:

  • When you access them later in your program, you have no sure way of knowing what state they will be in.
  • They can be accidentally called in other parts of the program.
  • They can be accidentally reassigned by other global variables.

Featured case study: NHS

How I helped the NHS rapidly build a brand new product.

Read case study…

Let’s take a simple PHP-based example. Let’s imagine you have a variable in global scope called $name:

$name = <holds the name of the logged in user>;

We can use this to display a message to the user on the homepage:

<h1>Hi there, <?php echo $name; ?>!</h1>

This is all very simple, and it works a treat, however, what happens later in our program when we want to list the name of the user’s friends and, forgetting about our previous definition, we redefine $name again?

$name = <holds the name of the logged in user’s friend>;

$name—which used to hold the logged in user’s name—now holds completely different data. We have lost the old variable and reassigned it because we were dealing with the two in the same scope.

Of course, no one would ever write any code like this, even someone like me can see that it’s an accident waiting to happen! Yet, we seem to have no problems with writing CSS this way…

Loose selectors

Loose selectors are something I have written about before, but it is recently that I have noticed a parallel that can be drawn between them and scope in programming. Please do note that this is only a metaphor. CSS does not have scope like programming languages do; I am merely trying to use scope as an analogy for helping explain the impact and danger of loose selectors.

A loose selector in CSS might be very much like the $name we used previously; whilst this is accurate, it is not explicit or sandboxed enough. We might initially have .name{} to style up a form field. Then, six months later, another developer might come along and need to style up a user’s profile page, where he wraps the user’s name in a heading with a class of, you guessed it, .name{}!

The problem we have here is that styling meant for the user’s name will leak into the form field styling and, conversely, the styling on the form field will pick up styling meant for the user’s name.

A lot of the time, a loose selector is just one which is poorly named, so it’s not always necessarily about scope, but coupling better naming with the idea of at least quasi-scoping CSS can solve a lot of our problems.

Scope in CSS

CSS doesn’t have scope in quite the same way programming languages do, but let’s take a look at the following:

/**
 * ‘Global’ scope.
 */
.some-widget{
}

    .title{
    }

Here we can see we have a component called .some-widget{} and a class of .title{}. Some widget is a kind of scope unto itself; it’s a discrete chunk of CSS that styles everything to do with .some-widget{}. In terms of scope, this is certainly a start.

With .some-widget{} we also have a class of .title{}, a very loosely named class which exists in a quasi global scope. .title{} is meant to be part of .some-widget{} but it completely lacks any scope at all and is very loose, much like our .name{} example. We could quite accidentally reassign or accidentally reuse this class now because it exists in a quasi global scope; what’s to stop another developer using this .title{} class on a ‘Mr/Mrs/Ms’-style form input six months down the line?

This is CSS’ equivalent of global scope.

Nesting?

We could, of course, give our classes definite scope by nesting them! If we were to look at a piece of Sass we can actually see how the scoping might work, because it looks almost programmatic:

/**
 * ‘Local’ scope, best illustrated with Sass.
 */
.some-widget{

    .title{
    }

}

Looking at that it is really easy to see a nice, explicit scoping; a .title{} inside of .some-widget{}. Now any styles on this titling element will only apply if it exists in the scope of the widget! Perfect… or is it?

No nesting!

Unfortunately, for all this Sass gives us the scope we were after, it compiles out to the following, which is overly specific:

.some-widget{
}

.some-widget .title{
}

Nesting in CSS is a bad idea.

I have written a few times about the importance paying attention to your CSS selectors, and nesting is one of the easiest ways to fly in the face of that.

The main, and most fundamental problem with nested selectors is that they unnecessarily increase specificity, and specificity is a bad thing.

So, even though nesting gives us rock solid scope (and about as close to actual scope in CSS as you’ll ever get), it’s not the right answer. So what is?

BEM

The best way to handle ‘scope’ in CSS is with a quasi, implied scope, and the best way to achieve this, in my opinion, is BEM.

/**
 * ‘Implied’ local scope, using BEM.
 */
.some-widget{
}

    .some-widget__title{
    }

BEM is a naming convention that I have written about previously, so you can familiarise yourself with it there. The way BEM helps us with scope is to imply it by namespacing your selectors with the scope in which they function. A class of .bar{} operating in the scope of .foo{} would now be .foo__bar{}.

This now means our loose .name{} example becomes a nicely scoped .profile__name{} or .form__name{}. We have a very detailed class which would be nigh on impossible to reuse or reassign!

Scope all the things?

Not everything in CSS needs a scope, some things do need to exist globally. Your .left{} helper class, for example, does not have a scope inside of something else.

However, .left{} is still a fairly loosely named class. Although it doesn’t necessarily require scope as such, it might be better named .float--left{}. This removes any ambiguity and decreases the chance of someone marking up, say, map directions using a class of .left{}. This is another similarity between CSS and programming; the need to name things. Naming things is very difficult, but we need to get better at it.

Write longer classes; instead of a class like .logo{}, opt for .site-logo{}. Make your classes a lot less loose by naming them a lot more specifically. It might not always be a case of scoping, it might just be a case of picking a better name for something with global scope.

In summary

Make sure any classes you write aren’t loose; make sure they’re always well named, and scoped if they need to be. Nesting selectors is a bad way of scoping, so use a naming convention like BEM to provide a quasi scoping.

Global scope and poorly named variables are absolute programming basics; there is no reason for our CSS to have the same unpredictable and loose traits that developers spend so much time avoiding.

N.B. All code can now be licensed under the permissive MIT license. Read more about licensing CSS Wizardry code samples




By Harry Roberts

Harry Roberts is an independent consultant web performance engineer. He helps companies of all shapes and sizes find and fix site speed issues.


Hi there, I’m Harry Roberts. I am an award-winning Consultant Web Performance Engineer, designer, developer, writer, and speaker from the UK. I write, Tweet, speak, and share code about measuring and improving site-speed. You should hire me.

You can now find me on Mastodon.


Suffering? Fix It Fast!

Projects

  • inuitcss
  • ITCSS – coming soon…
  • CSS Guidelines

Next Appearance

Learn:

I help teams achieve class-leading web performance, providing consultancy, guidance, and hands-on expertise.

I specialise in tackling complex, large-scale projects where speed, scalability, and reliability are critical to success.