React & CSS

Problems with CSS at scale

In a scaling project, the way CSS spans out can bring us various problems. Let’s look at these issues, and discuss them in detail.

  1. Globals
  2. Minification
  3. Dependencies
  4. Minification
  5. Sharing Constants
  6. Isolation
  7. Non-deterministic Resolution

Globals

Let’s start by building a button. In our CSS file, let’s create two classes for our buttons.

.btn {
	background: #0000FF;
	border: 1px solid #000;
	border-radius: 2px;
}

.btn-depressed {
	background: #00FFFF;
	color: #0000FF;
}

By adding .btn and btn-depressed to our CSS file, we have introduced glbal vaiables!

CSS is lacking in this regard, that the best practice is to use global variables, when in languages like JavaScript, the best practice is to strictly avoid globals.

CSS Extension

At Facebook, they extended CSS by introducing a new syntax:

/* btn.css */
.btn/container {
	background: #0000FF;
	border: 1px solid #000;
	border-radius: 2px;
}

By adding /, it is now going to be a local variable

That means the .btn/container can only be used in the file called btn.css.

Global Variables

If you want a global variable, you can append /public in front of the class name.

  .btn/container/public {
  	background: #0000FF;
  	border: 1px solid #000;
  	border-radius: 2px;
  }
  

The call-site will be changed by a new function called cx (class extension).

Dependencies

At this point having split our CSS into many files, we will have to manage the dependencies between those files.

We can use cx to automatically inject requireCSS call.

/* btn.js */
reqireCSS('btn');

<div className={cx('btn/container')}>

Dead Code Elimination

Since cx is the only way to generate the name, we can also solve the dead code issue.

In your CSS, if you have a rule .btn/container, then search for btn/container in your codebase and you’ll find the call-sites. If there is none left, you can kill it!

Minification

We can minify all the class names and send both JS and CSS a bit faster to users.

/* btn.css */
._f8z {
/* btn.js */
<div className={'_f8z'}>

Sharing Constants

Sharing constants between CSS and JavaScript is not ideal. But unfortunately there are many use cases where you have to do this. You could use comments in both CSS and JS files to keep things in sync. But this approach doesn’t scale very well.

/* btn.css */
.btn/container {
	margin: 5px;
}
/* btn.js */
var buttonMargin = 5;
CSS Variables

To fix this, Facebook borrowed the var syntax specification of CSS, and exposed a cssVar function to JS.

/* btn.css */
.btn/container {
	margin: var(button-margin);
}
/* btn.js */
var buttonMargin = cssVar('button-margin');

To generate those, you have to use your backend.

/* CSSVar.php */
$CSSVar = array(
	'button-margin' => 5
);

Non-deterministic Resolution

What if we need to overrrule a style? in CSS specificity plays a big role in this. But if we have our CSS in different files, this becomes a nightmare when the files are bundled and they are loading asynchronously.

The most popular way to workaround this issue is to increase the specificity of the rule that conflicts but this is super brittle.

/* overlay-btn.css */
.overlay-btn/container {
	background-color: #000;
	color: '#FFF';
}
/* btn.css */
.btn/container {
	background-color: #FFF;
	color: '#000';
}
/* btn.js */
<div className={cx(
	'btn/container/public',
	'overlay-btn/container'
)}

Isolation

Designers have the ability to modify the style of the internals via selectors. The override looks like regular CSS, so it’s often not being caught by code review. It’s also nearly impossible to write lint rules against it.

/* btn.css */
.btn/container {
	/* override everything! */
}
/* btn.js */
<div className={cx('btn/container')}

Non-deterministic Resolution and isolation is still not resolved yet.

Be first to comment

Leave a Reply