C3 CSS

component _child & context

A methodology to help you write human-friendly CSS.

↓ Table of contents ↓

Table of contents

  • Introduction
  • Quick start
  • Component
  • Child
  • Context
  • File structure
  • Key concepts

Introduction

C3 (Component, Child, Context) is a methodology for writing CSS, centred around a selector naming convention. This approach is intended for anyone who wants to write code which is semantic, easy to maintain, and scalable. If you are familiar with BEM, you will be familiar with two thirds of C3. Either way, you should find all the information you need in this guide.

Semantic

In CSS terms, semantic has come to mean selectors with names that convey what an element represents; and not what the element looks like. Essentially following the W3C guideline:

[…] authors are encouraged to use values that describe the nature of the content, rather than values that describe the desired presentation of the content.

C3 is very much geared towards semantic CSS selectors. If this is something that’s important to you, C3 should feel like a natural fit.

Maintainable

C3 helps make code easy for humans to understand and navigate. Components are easy to identify, both in the HTML and CSS. All the styles for a given component are located in one place.

Scalable

C3 is designed to facilitate isolation of styles, and minimise inheritance. This means that as your code grows, complexity doesn’t have to. Adding or removing components needn’t be scary.

Quick start

C3 is a component based approach to writing CSS. The idea is that every element in your page is either a Component, or a Child. The third ‘C’ describes the Context that a component is in.

Component

A stand-alone part of a page. Represented by a class. The class is named after what the component is, never what it looks like, or its state. Class names are formed of lowercase words, separated by a hyphen (-). Components can be nested within one another (in the markup, not in CSS).

Example

<!-- valid component class -->
<nav class="main-menu"></nav>

<!-- invalid, we don't describe appearance here -->
<nav class="horizontal-menu"></nav>
.main-menu {
  /* styles */
}

Child

A Child could be thought of as a Component Child. A Child is a constituent part of a Component, not intended to be used separately from it. Just like components, children are represented by a class, named after their purpose, and never their appearance or state. Class names are formed of the parent Component name, an underscore, and Child name. If there are multiple words in the Child section of the selector name, they are separated following the same convention as components (lowercase words, separated by a hyphen). Children can also be nested within one another (in the markup, not in CSS).

Example

<ul class="main-menu"> <!-- Component -->
  <li class="main-menu_item"> <!-- Child -->
    <a class="main-menu_link"></a> <!-- Child -->
  </li>
</ul>
.main-menu_item {
  /* styles */
}

.main-menu_link {
  /* styles */
}

Context

Context describes the context of a Component or Child. Context here means circumstances, not appearance. These circumstances are typically:

  • State: A transient property of a component. It is “active”, for example.
  • Metadata: Similar to state, but could be persistent. For example, how many items are in a list.
  • Parent: A component that is a parent (at any level) of the component in question. So, if this component is in a sidebar, for example.
  • Media: The screen size, orientation etc of the device.

When possible, state, and metadata should be handled with attribute selectors.

Example

<ul class="main-menu" data-count="2"> <!-- Metadata -->
  <li class="main-menu_item">
    <a class="main-menu_link" aria-current="page"></a> <!-- State -->
  </li>
  <li class="main-menu_item">
    <a class="main-menu_link"></a>
  </li>
</ul>
.main-menu_link {
  /* styles */

  &[aria-current="page"] {
    /* styles */
  }
}

This documentation uses CSS nesting, as a way of making Context most easily identifiable in the working style sheets. This approach can be utilised via a PostCSS plugin. Nesting is also possible if you are using a preprocessor, such or SASS. A non-nested version of the above selector would be .main-menu_link[aria-current="page"].

If for some reason, you need to use a class for state or metadata, it should be constructed using an attribute, two hyphens, and a value. For example active--true or transition--enter.

Component

A stand-alone part of a page or interface.

<nav class="main-menu"></nav>
.main-menu {
  /* styles */
}

An element should be considered a Component (rather than a Child) if it doesn’t depend on other elements existing.

Naming

A Component is represented by a class. The class is named after what it is, never what it looks like, or its state.

Class names are formed of lowercase words, separated by a hyphen (-). You could use a camel-case if preferred; the crucial rules are to be consistent, and that underscores (_) are not permitted.

Nesting

Components can be nested within one another (in the markup, not in CSS).

illustration of a navigation component, inside a header component
Example of a naviagtion component, nested inside a header component.

Multiple instances

An interface can contain multiple instances of a Component. For example, you might have a button component, used for multiple buttons in your interface.

A distinction should be made between wanting multiple instances of a component, and wanting to reuse styles from a component. Just as a Component is defined by its function (not what it looks like), each instance of component should be related by function. Wanting a link to look like a button, is not a use-case for it being the same component as an element that behaves like a button.

Child

A Child could be thought of as a Component Child. A Child is a constituent part of a Component.

<ul class="main-menu"> <!-- Component -->
  <li class="main-menu_item"> <!-- Child -->
  </li>
</ul>
.main-menu_item {
  /* styles */
}

If an element is not intended to be used without its parent or sibling elements, it should be considered an Child, and thus belong to a Component.

Naming

Just like components, children are represented by a class, named after their purpose, and never their appearance or state. Class names are formed of the parent Component name, an underscore, and Child name. The child name is lowercase. If there are multiple words in the child section of the selector name, they are separated by a hyphen (-).

Nesting

As well as being nested with a Component (in the markup, not in CSS), children can also be nested within one another. Note, the first prefix of the selector name is still derived from the Component.

<ul class="main-menu"> <!-- Component -->
  <li class="main-menu_item"> <!-- Child -->
    <a class="main-menu_link"></a> <!-- Child -->
  </li>
  <li class="main-menu_item"> <!-- Child -->
    <a class="main-menu_link"></a> <!-- Child -->
  </li>
</ul>

Multiple instances

A Component can contain multiple instances of a Child. For example, you might have a menu component which contains multiple menu items.

illustration of navigation - menu items highlighted
Example of a multiple child instances inside a component.

Context

Context describes the context of a Component or Child.

<ul class="main-menu">
  <li class="main-menu_item">
    <a class="main-menu_link" aria-current="page"></a> <!-- Context -->
  </li>
</ul>
.main-menu_link {
  /* styles */

  &[aria-current="page"] {
    /* styles */
  }
}

Context means circumstance, not appearance. These circumstances are typically:

  • State: A transient property of a component. It is “active”, for example.
  • Metadata: Similar to state, but could be persistent. For example, how many items are in a list.
  • Parent: A component that is a parent (at any level) of the component in question. So, if this component is in a sidebar, for example.
  • Media: The screen size, orientation etc of the device.

State example

<ul class="main-menu">
  <li class="main-menu_item">
    <a class="main-menu_link" aria-current="page"></a> <!-- State -->
  </li>
  <li class="main-menu_item">
    <a class="main-menu_link"></a>
  </li>
</ul>
.main-menu_link {
  /* styles */

  &[aria-current="page"] {
    /* styles for main-menu_link, when it is current page */
  }
}

Metadata example

<ul class="main-menu" data-count="2"> <!-- Metadata -->
  <li class="main-menu_item">
    <a class="main-menu_link"></a>
  </li>
  <li class="main-menu_item">
    <a class="main-menu_link"></a>
  </li>
</ul>
.main-menu {
  /* styles */

  &[data-count="2"] {
    /* styles for main-menu, when it contains two items */
  }
}

Parent example

<div class="home-page"><!-- Parent -->
  <ul class="main-menu">
    <li class="main-menu_item">
      <a class="main-menu_link"></a>
    </li>
    <li class="main-menu_item">
      <a class="main-menu_link"></a>
    </li>
  </ul>
</div>
.main-menu {
  /* styles */

  @nest .home-page & {
    /* styles for main-menu, when it is within the home page */
  }
}

Multiple contexts

Multiple contexts are added to a single code block.

.main-menu {
  /* styles */

  &[data-count="2"] {
    /* styles for main-menu, when it contains two items */
  }

  @nest .home-page & {
    /* styles for main-menu, when it is within the home page */
  }

  @media (min-width: 600px) {
    /* styles for main-menu, on a screen at least 600px wide */
  }
}

This documentation uses CSS nesting, as a way of making Context most easily identifiable in the working style sheets. This approach can be utilised via a PostCSS plugin. Nesting is also possible if you are using a preprocessor, such or SASS.

illustration of navigation in header and off canvas
Example of a navigation component in different contexts.

Naming

When possible, state, and metadata should be handled with attribute selectors. This helps give a distinct syntax for Context, and provides consistency with ARIA. If for some reason, you need to use a class for state or metadata, it should be constructed using an attribute, two hyphens, and a value. For example active--true or transition--enter.

Considerations

Context makes use of specificity to override existing styles. With this power comes responsibility. If you find your CSS is getting complicated, a new Component might make more sense than a parent context.

File structure

The way that your project is structured is largely down to preference, and requirements of the project. However, C3 does make some suggestions. Each Component should have a corresponding CSS file of the same name. For example, if you have a main-menu component, you will have a file named main-menu.css, or an equivalent, such as main-menu.scss. This file will contain a code block for the main-menu component, as well as a code block for each Child of that component.

.main-menu {
  /* styles */

  @media (min-width: 600px) {
    /* contextual styles */
  }
}

.main-menu_item {
  /* styles */
}

.main-menu_link {
  /* styles */

  &[aria-current="page"] {
    /* contextual styles */
  }
}

To the extent that it is possible and practical, all styles that effect main-menu (and its children) should be within this file.

You will probably want to have a few global styles, to perform some reset or base level styling. Such applying a font-family, for example. If these styles need to be overridden for a component, they are done so in the style sheet for that component. The exception to this rule is in circumstances where an element cannot have a class applied to it, such as a p generated from Markdown. In this case a context is added to the global element code block.

p {
  /* global paragraph styles */

  @nest .my-component & {
    /* styles for paragraphs in this component */
  }
}

This ensures that when looking at an element in HTML, you know, thanks to the class name or lack of, exactly where to look for the corresponding styles.

To make the component files easy to locate, they should be contained within a components directory. This might be a folder that contains all files related to a component (including Javascript files, for example), or a folder within a dedicated CSS directory.

It is assumed that the individual CSS files are not what will be served to the browser, and that they will be compiled into a single file, minified, and/or injected into style tags (depending on project aims).

Key concepts

Isolation

One of the key objectives of C3 is to make your CSS scalable. This essentially means that as your codebase grows, people can continue to make changes without fear of breaking something. There are generally two approaches to achieving this: isolation, and abstraction. The C3 methodology takes the isolation approach. This means, one element (Component or Child) is isolated from the styles of other elements. There is no “leaking” of styles. This methodology aims to offer predictability, by virtue of this isolation, no matter how large your codebase gets.

Single source of truth

A core aim of C3 is to avoid any ambiguity about where the styles for a given element can be found. To achieve this you are aiming for each element of an interface having one class assigned to it. This class should be either a Component or a Child. All of the styles for which will be found in the code block for that element. There will be exceptions, the below covers how to handle those.

No classes

There will be times when you have elements that do not have a classes attached to them, for example a generated paragraph tag. This is fine. If you need to style such an element, those styles should live in a global elements.css file. Any contextual overrides should live in this file too.

p {
  /* global paragraph styles */

  @nest .my-component & {
    /* context can be applied to global elements */
  }
}

Multiple classes

There is no “mixing” of components in C3, so multiple classes are limited to specific circumstances. C3 suggests that you use something other than a class for anything other than a Component or Child selector. An attribute selector, for example. If you need to use a class however, it should be distinguishable from the Component or Child. If you are using a class for Context, using the Context syntax of attribute--value achieves this. This concept should apply to any other additional classes that are applied to an element. So if an additional class is required for a Javascript hook, it should be identified as such. For example, js-my-selector.

Semantic

“Semantic” in C3 refers avoiding any descriptions of appearance in selector names. C3 selector names follow this W3C guideline:

[…] authors are encouraged to use values that describe the nature of the content, rather than values that describe the desired presentation of the content.

This offers the freedom to restyle elements for infinite contexts, while maintaining easy to understand HTML.

Semantic example

<button class="shopping-cart_remove-button">
  Remove item from shopping cart
</button>

Non-semantic example

<button class="background-red hover-background-dark-red text-white font-size-6 font-bold paddingY-2 paddingX-4">
  Remove item from shopping cart
</button>
A CSS methodology by Sam Smith
c3css.com