10 Things You Need to Know About CSS

CSS may look as a simple language. In fact it can be simple only to use, but definitely not simple to maintain.

Observing that the maximum number of people who can productively simultaneously work on CSS is one -- @threedaymonk
The skills required to write good CSS code are by and large the same skills required to write good code in general. -- Lea Verou

Everybody who used to work on a large-scale projects knows how hard it can be to keep constantly growing CSS sources readable and consistent, styles reusable and loosely coupled. Moreover while going responsive web design (RWD) we also deal with increasing cyclomatic complexity. Learning from own experience I collected 10 vital principles that help turning your styles into efficient and highly maintainable code.

1. Be aware that browser reads selectors from right to left

It is a fundamental fact, yet surprisingly a little known: browsers read selectors from right to left. It means harmlessly looking selectors like .feed nav ul li h2 are unacceptably inefficient. In order to solve this riddle browser will find first all the h2 in the document, then all li and so forth until it comes to .feed and filters formerly found elements to compose a matching node list. This problem can be avoided by shortening selector path to the element (e.g. .feed-list-heading). Yet we still can do nesting when using a child selector. It gives considerably good efficiency because browser needs to check only one level higher in the DOM and then stops (e.g. .feed-list > li > h2)

Further Reading

2. Keep to component-based CSS

How do we style a web application? There is still a method of going from element to element and styling each in place. Easy, isn't it? Don't do it! Besides that you repeat yourself again and again you will end up with a viscous architecture resisting to any change. Instead you ought to look for repeating patterns in the PSDs and extract reusable components such as navbar, breadcrumbs, dropdowns, thumbnail, panel. Components are supposed to be small and independent. Let's say we have a number of button-like controls across the application. So we create an abstract component identified by class name .btn. The corresponding rule-set describes look & feel applicable to any button in the application.

.btn {
  cursor: pointer;
  -webkit-user-select: none;
  text-align: center;
  white-space: nowrap;
  border-width: 0.1rem;
  border-radius: 0.4rem;
  display: inline-box;
}

Then we add rules per each concrete button type:

.btn-primary {
  color: #fff;
  background-color: #428bca;
  border-color: #357ebd;
  border-style: solid;
}

Depending on location we achieve the intended style on an element by combining CSS classes:

<button class="btn btn-primary">Submit</button>

There are a few standardized approaches for component-based CSS architecture SMACSS, OOCSS, ACSS, BEM. It's a matter of personal choice which one to follow.

So you adopt any of them, you can take best practices of a few mixed (see for example) or be on your own way.

Further Reading

3. Structure your CSS into modules

Did it occur to you to look thought a single-file style-sheet of a large-scale project? It looks inscrutable. A complex problem, though, can be broken into simpler tasks. So the entire system becomes easier to debug, update and modify. We can split the whole style-sheet into multiple files so that every file represented a component. The application style-sheet we will do by combining required components:

/* app.css */
@import "./module/page-header.css";
@import "./module/breadcrumbs.css";
@import "./module/panel.css";
...

So to modify a style we don't need to go through the monstrous source code file, but confine the problem scope to a single component file. The bad news is that application style-sheet now produces numerous HTTP requests during the page load. Yet it is no problem if you use a CSS preprocessor. Preprocessors compile all the declared dependencies into a single output CSS file.

4. Apply software design principles to CSS

CSS is no programming language yet it is a code that must be maintainable. The very same principles we are used in programming languages encapsulation, loose coupling and reusability apply to CSS:

Reusability

Style the components, not a page, so that you could apply once created style an infinite amount of times across the project. Do not repeat rules while styling, but leverage cascade or mix-in styles

Loose Coupling (Tag Independence)

Avoid qualified selectors (prepended with tag). Thus you will gain additional agility in moving classes around components. Examine this, you have a form to style. The form has a submit button. You style it as button.btn. Everything seems fine until you need to set style for download button. It must look like a button, but it's a link and button.btn won't match there. With tag-independent selectors (.btn) you can reuse styles regardless of tag.

Loose Coupling (Location Independence)

Avoid long selectors with descendant/child combinators (.feed nav ul li h2). Long selectors besides harmful affect on selector performance mean that style rule-set is tied to particular location in the DOM. Independent selectors allow us to move components around our markup more freely.

We know “5 first principles” of computer programming. Whatever strange it may sound for a declarative language, Miller Medeiros has shown how S.O.L.I.D. applies to CSS.

It may be controversial to consider CSS in terms of LSP, ISP and DIP (it makes a perfect sense though if you give it a second though). Yet the first two principles are essential for maintainable CSS.

The Single Responsibility Principle (SRP)

The principle implies that A class should have one, and only one, reason to change. In CSS we have

.btn {
  cursor: pointer;
  -webkit-user-select: none;
  text-align: center;
  white-space: nowrap;  
  border-radius: 0.4rem;
}
.btn-primary {
  color: #fff;
  background-color: #428bca;
  border-color: #357ebd;
  border-style: solid;
}
.btn-lg {
  padding: 1rem 1.6rem;
  font-size: 1.8rem;
  line-height: 1.33;
  border-radius: 0.6rem;
  border-width: 0.1rem;
}

Here every class represents only one concept. .btn states for button base style, .btn-primary for a concrete cosmetic style and .btn-lg for the box-model. By combining these classes we have a number of options like:

<button class="btn btn-primary btn-lg">OK</button>
<button class="btn btn-secondary btn-lg">Cancel</button>
<button class="btn btn-primary btn-small">X</button>

If requirements change, we need to change the only class rules and all the buttons of the web application will change consistently.

Further Reading


The Open/Closed Principle (OCP)

The principle states that you should be able to extend a classes behavior, without modifying it.

In CSS it means that we have to design base class style so that we could easy extend it without its modification. If we have a base .btn class defining a border, but want any button of .toolbar context to go with no border, we can only reset that border rule.

.btn {
  border: 0.1rem solid #357ebd;
}
.toolbar .btn {
  border: none;
}

That is violation of OCP. We ought to augment the base class instead of editing it:

.btn {
  /* base rules */
}
.btn-outlined {
  border: 0.1rem solid #357ebd;
}

This design clarifies the intend behind the classes, besides, it doesn't break SRP.

Further Reading


Composition over Inheritance

The cascading styles suggest any rules assigned to a parent are being inherited by its descendants. It's a great thing and, at the same time, it can make your life difficult. Consider the following example:

The HTML:

<span class="badge">100</span>
<h2><span class="badge">100</span></h2>
<a><span class="badge">100</span></a>
<table class="modifier-a modifier-b"><tr><td class="modifier-c"><span class="badge">100</span></td></tr></table>

The CSS:

.badge {
  font-size: 1.25em;
}

Now try to predict what style will be the badge. The first line sets the font-size to 16px (browser default font size) * 1.25em = 20px, font-weight to normal and none to text decoration. The second line changes font size and weight according to h2 style (16px * 1.5em * 1.25em = 30px). Next it changes inheriting text decoration from a. In the last line we have multiple inheritance, what we have to avoid in programming “at all costs”.

In order to make styles clean and predictable we need to use composition rather than inheritance. This principle implies that complex objects should be composed from much smaller, rather than inheriting behaviour from a much larger, monolithic object.

Thinks back to our SRP example. We composed button UX component from smaller and unrelated objects.

Further Reading


5. Remember that naming in CSS is hard

There are only two hard things in Computer Science: cache invalidation and naming things. -- Phil Karlton

How much time you spend looking for a most appropriate class name? If not much I don't think you have maintainable CSS. It's not so easy to get these names. First of all we need to came up with a strategy. Frequently used names can be split into following categories:

Functional class names

<div class="is-relative is-responsive"></div>

Content-based class names

<div class="registered-user-avatar"></div>

Presentational class names

<div class="round-box"></div>

No category of the list by itself is good enough as to show selector intent as to keep to the principles we examined in the previous chapter.

A good naming convention will tell you and your team

  • what an entity class represents;
  • where a class can be used;
  • what (else) a class might be related to.

You can find guidelines on naming in SMACSS, OOCSS, ACSS, BEM.

I personally keep to class names describing the objects, their modifiers and states:

.noun {}            // parent: .post
.noun-noun {}       // child:  .post-title
.adjective-noun {}  // example: .dropdown-button
.is-state {}        // state: is-selected, is-hidden
.adjective {}       // examples: .left, .right, .block, .inline

Further Reading

6. Use classes are for styling, IDs and data-attributes to bind JavaScript

You can run into IDs used for styling in many open-source projects. Do not adopt this practice. Why?

  • ID has top specificity and no amount of chained classes can override a rule-set assigned to an id.
  • ID can be used on page only once, so we cannot make any reusable styles with an ID in selector.

So we rather to use IDs in HTML for fragment identifiers or for JavaScript bindings. Element attributes can be also reached from CSS and they don't have the issues enlisted above. Yet for a better separation of concerns it's not recommended to use them in styling. When you use only classes for styling and keep IDs and attributes for JavaScript binding you get much more flexibility in moving styles across the document.

By the way, use of !important directive in most cases isn't a good idea. But you can strengthen specificity of a selector by repeating class name:

.class.class.class.class { ... }

Further Reading

7. Follow the standard flow

When creating a layout, we should think of working with normal flow first. The boxes under the flow act according the well-documented block and inline formatting context models. So when the parent box changes all the inner boxes adjust their positions to fit best the new layout dimension. Floating boxes are specific, nonetheless they also participate in the flow. However absolute/fixed positioned boxes are completely out of the flow. While fixed boxes we use in special cases and that is fine, but absolutely positioned boxes often replace the normal flow and there we run into the pitfalls.

Positioning boxes in the flow

Instead of absolute positioning boxes we rather rely on margins and paddings of the normal flow. Mostly it works obvious, but sometimes in cascading boxes we are getting unaccountable vertical gaps or opposite – lost spacing. You need to be aware of collapsing margins. The issue can be solved simply by setting up e.g. a transparent border.

Responsive design

When positioning and sizing boxes in RWD, we rely mostly on relative units such as percentage. If you want images adjusting horizontally you do:

  .box > img { width: 100%; }

when vertically:

  .box > img { height: 100%; }

For other boxes we use percentage for margins to position them against the parent block. If we need an explicit offset we use calc expression:

.box {
  width: calc(100% - 40px);
}

When we have a grid we set column widths in percentage also. It may bring to a question if you need some spacing between columns. You can go again with calc(25% - gap width), but I prefer to use transparent background instead, which is inside box model and doesn't require box re-sizing. The trick is you need to prevent background to extend underneath box border.

  background-clip: padding-box; // background extends on padding, but not on border

If you need the columns of the same height without using table, it may appear challenging. In fact the issue can be usually solved as easy as that:

.item {
    margin-bottom: -99999px;
    padding-bottom: 99999px;
}

Centering a box of unknown size

In RWD the elements sizes are mostly relative. It makes tricky to position children elements. Well, if you know exact static size of child element you can do position:absolute, left/top in percents and adjust to the desired position with negative margin. But often we don't know the box size. We can go, of course, with table-cell and suffer its quirks. A better choice would be to leverage vertical-align property available for inline-block elements. Its behavior is also not very obvious. If you simply set vertical-align to middle on parent element, the child element doesn't shift to the center. There must be some magic involved:

.container {
  text-align: center;
  font-size: 0;
}
 
.container:before {
  content: '';
  display: inline-block;
  height: 100%;
  vertical-align: middle;
  margin-right: -0.25em; /* Adjusts for spacing */
}

.content {
  display: inline-block;
  vertical-align: middle;
  width: 300px;
}

In some cases that is the best option available but, in general, before choosing a proper centering approach you rather consult to Centering in CSS: A Complete Guide.

Going HTML5 way

There were times when we used tables to layout the page, now we go mostly with floats. However when it comes to RWD (responsive web design) we need hacks over hacks. In fact in modern browsers we have special tools such as CSS Flexible Box Module,CSS Grid Layout Module, CSS Regions Module, CSS Multi-column Layout Module. The problem is that not all the modules already widely supported. But if you can sacrifice support for as old browsers as a collection of layout examples solved with Flexbox.

Further Reading

8. Make simple graphic shapes with CSS

When you need a simple shape on a page you can use an image and it means an addition HTTP request except it is a previously loaded sprite or embedded Base64. You can go with embedded SVG or you can make a shape out of a generic HTML element with CSS.

If you need a circle or oval you may do with border-radius on a block element as easy as it:

.circle {
  width: 10rem;
  heigh: 10rem;
  border-radius: 50%;  
  background-color: red;
}

If you wish for an arrow that changes direction on hover, it can be done with border.

.arrow {
  width: 0;
  height: 0;
  border-top: 2rem solid red;
  border-left: 2rem solid transparent;
  border-right: 2rem solid transparent;
  border-bottom: 2rem solid transparent;
}
.arrow:hover {
border-top: 2rem solid transparent;
border-bottom: 2rem solid red;
}

If we bring a little of animation, we can get really fancy stuff. Check this Burger Menu out. It became a UX trend.

By using pseudo-elements :after, :before, multiple backgrounds (CSS3 Patterns Gallery) we can achieve unbelievable complex drawings. Just take a look at this collection A Single Div: a CSS drawing project. Mind that all these pictures are made of a single div.

I'm not saying that CSS drawings are to replace binary image files or SVG, but these techniques are something you rather keep in mind.

9. Achieve generic UX interactions without JavaScript

You probably know that some interactions on the front-end can be achieved with CSS only. Considering CSS is no programming language, a mini-app such as a game written on it looks like impossible magic. In fact the principles behind CSS control on user iterations are not hard to grasp.

:hover

It the old days we needed JavaScript to power drop-down menus. Now we rely on :hover pseudo selector:

<li class="item">
  Item
  <nav class="subnav"></nav>
</li>

.subnav {
  display: none;
}
.item:hover .subnav {
  display: block;
}

Checkbox-hack (:checked)

When we click on a label element that associated to an input of type checkbox, the input changes its state (checked or no). The state is available in CSS as :checked pseudo selector.


    <input id="btn1" type="checkbox" class="vbtn-state" />
    <label for="btn1" class="vbtn-trigger">Click me</label>
    <div class="vbtn-view">
      expandable content
    </div>

In CSS we need to hide the input .vbtn-state and using sibling combinator ('+' or '~') we declare that when the input state is :checked our expandable content is visible. I'm using here adjusting combinator to specify which exact view I want to be shown (one that follows .vbtn-trigger, that follows .vbtn-state).

.vbtn-state {
  position: absolute;
  top: -9999px;
  left: -9999px;
  /* For mobile, it's typically better to position checkbox on top of clickable
    area and turn opacity to 0 instead. */
}
.vbtn-trigger {
  cursor: pointer;  
}
.vbtn-view {
  display: none;
}
.vbtn-state:checked + .vbtn-trigger + .vbtn-view {
  display: inline-block;
}

Look at all the stuff you can do with the “Checkbox Hack”

:target

:target pseudo-selector is matched if element is referred by fragment identifier in URI. In other words if we have a URI site.com/#some requested the styles of #some:target selector will be matched.

<span id="about" class="target"><!-- Hidden anchor to open adjesting modal container--></span>
<div class="modal">
  expandable
  <a href="#">Close this</a>
</div>
<a href="#about">Click here</a>

In CSS we just need to use a sibling combinator to specify which sibling we what to show up. Note that the target element we set into fixed position. That is to avoid “jumping” within open document.

.target {
  display: block;
  left: 0;
  position: fixed;
  top: 0;
  width: 0;
  height: 0;
  visibility: hidden;
  pointer-events: none;
}
.modal {
  display: none;
}
.target:target + .modal {
    display: block;
}

You can find an example of modal window using :target at Fancy Modal Windows without JavaScript


:focus, :active

Closest alternative to a 'click' event handler in CSS is the :active or :focus pseudo class selectors. Here we need to remember that the first selector ceases to match once the mouse-button is released. The the second selector ceases to match once the given element is no longer focused. Yet it can be used still, see 4 Methods CSS3 Tabbed Content

You may think now why to bother with CSS tricks while this functionality is easy achievable with JavaScript. Besides CSS implementation is always faster, we shall not forget about progressive enhancement. If you made menu, expandables, tabs and modals with CSS, you deliver the core user experience before JavaScript is loaded and even when it's, by some reason, wasn't loaded at all.

10. Set typography sizes in relative units

You have may noted that sound projects usually have font sizes set in relative units (e.g. em). That gives addition flexibility. You can scale fonts across the entire document by changing base font size e.g. via media query. Unfortunately it comes at a cost. The deeper cascade the harder sizes in ems to maintain. Modern browsers support rem units, that relative not to the parent, but to the base font size. So we can set base font size as 62.5%. That gives us translation px to rem as easy as 14px /10 = 1.4rem. However while doing RWD we need would like to scale not only globally but in scope of a component (see Component-based CSS chapter). So we set components base font size in rems and component inner elements typography relative to it in ems.

/* Global scope */
html {
  font-size: 62.5%; // 10px
}
@media (max-width: 960px) {
  html {
    font-size: 56.25%; //9px
  }
}

/* Components */
.header {
  font-size: 1.6rem;
}
.footer {
  font-size: 1rem;
}
.sidebar {
  font-size: 1.4rem;
}

/* scale with components */
h1 {
  font-size: 2em;
}
h2 {
  font-size: 1.4em;
}
h3 {
  font-size: 1.2em;
}

Modern browser support a wide range of other units. I personally found useful viewport percentage lengths such as vw. It is relative not to base font size, but to viewport width itself and scales with document view resizing simultaneously.

Further Reading


Afterword

I collected here 10 aspects that I found important. Of course it cannot cover everything what developer shall take care of. My intend was to bring attention to the fact that CSS is fat more complex thing that it may seem and must be taken as seriously as any other declarative or imperative language.


CSS may look as a simple language. In fact it can be simple only to use, but definitely not simple to maintain.Everybody who used to work on a large-scale projects knows how hard it can be to keep constantly growing CSS sources readable and consistent, styles reusable and loosely coupled. Moreover while going responsive web design (RWD) we also deal with increasing cyclomatic complexity. Learning from own experience I collected 10 vital principles that help turning your styles into efficient and highly maintainable code.