How we write CSS

6 Dea81 C5 6 F91 451 D 875 C 9 A37 D0 Ca2 E64

From our experience, we’ve learned that creating components which are a combination of CSS and HTML solves more problems than separating HTML and CSS.

Rules

  1. A component always consists of CSS and HTML (JS is optional)
  2. HTML and JS of a component are located in ComponentName.js
  3. Styles of a component are located in ComponentName.scss
  4. Each CSS class starts with the unique component name
  5. The HTML of each component only uses its own prefixed classes
  6. Components can use other components, but never overwrite their styles

Our decision to structure our CSS like this came from acknowledging the following problems, which are solved by adhering to the rules above:


⚠️ CSS classes are used with a wrong HTML structure

Examples:

  • .link was styled to be used with <a>, but gets used with <button> instead. This was not intended and therefore does not overwrite the default browser styles
  • HTML structure changes, e.g. nesting an element deeper in the DOM since a div was added. Hence :nth-child don’t work anymore

Solution:

  • components are always a combination of HTML and CSS, which work together
  • e.g. React components which render HTML and use CSS classes designed for this specific HTML

⚠️ Compiling CSS properties in your head

Example:

  • a CSS class uses multiple mixins and additionaly overwrites some properties of those mixins
  • developers now have to look at each mixin and remember, which properties it yields in case any of them get overwritten
  • mixins could overwrite each other
  • updating mixins could unintentionally affect other classes
  • a mixin can consist of other mixins
  • this leads to jumping between files and memorising their output
.button {
  @include button;
  @include font-family;
  @include size;
  color: white;
  background: $color-primary;
}

Solution:

  • no or very few mixins
  • all styles are only written for the specific HTML of the component
  • all classes are prefixed with the unique component name
  • locating styles of a component is easy: the class prefix is the file name
  • files contain all styles of a component, since only very few mixins are used. this keeps jumping between files to a minimum

⚠️ Consistent updates to visual elements is tedious and error-prone

Example:

A button is used in different places of the app/site and looks different although the same class is used. The reason for this is other elements overwriting styles of the button class.

If the design of visual element is contextual, updating all instances of an element requies finding all places where this element is used and check if its styles are overwritten.

The amount of effort to makes these kind of changes increases with the size of the code base. This is tedious, error-prone and sometimes impossible to figure out if the rendering logic is complex.

Solution:

  • no component is allowed to overwrite styles of another component
  • unique class prefixes are supposed to prevent overwriting styles
  • deviations can be found easily: a class is not prefixed with the filename
  • variations of components are handled by the component itself, e.g. <Button theme=”border”>

⚠️ Import order leads to different css output

Example:

Classes in different files overwrite each others’ properties and the order of imports decides which one of them wins.

<div class="form box">
// form.scss
.form { border: 0; }

// box.scss
.box { border: 2px solid black; }

// imports.scss
@import "form";
@import "box";   // <- changing this order leads to different results

Solution:

  • HTML is always a part of a component, therefore the classes would have the same unique prefix
  • all styles of a component live in the same file which is named after the component:
    • .box is defined in Box.scss
    • .form is defined in Form.scss
    • .form__input is defined in Form.scss
  • .form and .box cannot be used on the same HTML element, since they are not part of the same component

⚠️ Dead Code Elimination

Example:

How do I know which styles are redundant?

Solution:

  • unused components can be deleted safely
  • they can be found easily:
    • search for usage of the component, e.g. <Name
    • webpack doesn’t compile if a component in use gets removed
  • deleting component styles is safe, since there are no dependencies between components due to using unique class prefixes

Examples

Button with modifier

// Button.scss

.button {
  border: 2px solid black;
}

.button--borderless {
  border: 0;
  background: gray;
}
// Button.js

const Button = ({ theme = '' }) =>
  <button className=`button ${theme === '' ? '' : `button--${theme}`}`>
    {children}
 </button>

Nesting components

// SearchField.js

const SearchField = ({ value }) =>
  <div className='search-field'>
    <input className='search-field__input' type='search' value={value} />
    <Button>Suchen</Button>
  </div>
// SearchField.scss

.search-field {
  // styles
}

.search-field__input {
  // styles
}

Advice on component variations

In this exmaple, if <Button> in <SearchField> should look different, one would add a new variation to <Button> via a property, e.g. <Button theme='small' icon='magnifier'>. Do not overwrite Button styles in SearchField.scss!

If the design of this button differs too much or if these styles are very specific to SearchField, one doesn’t need to use the generic <Button> component. In these cases it’s valid to style the html tag <button> directly, e.g. <button className='search-field__button'> or create a new specifc component if it’s used in multiple places, e.g. <SearchButton>.


How this page came to life