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 */
}
}
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).
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.
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 */
}
}
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.
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>