Guidelines for writing scalable and maintainable style-sheets
Latest commit 189d7a5 Jul 8, 2016 @dsheiko tunning wordings

Readme.md

PCSS

ver. 1.2.1

Pragmatic CSS is guidelines for writing scalable and maintainable style-sheets. PCSS divides the whole UI into portable and reusable components. Every component is described in a separate CSS (SASS/LESS/etc) module. PCSS's naming convention makes it easier to locate a module corresponding to a problem and encourages developer on producing optimized object-oriented CSS.

PCSS doesn't reveal much unique, but extracts and combines the best parts of existing standards and practices. It borrows Base, State and Theme rules from SMACSS, element and subtype (modifier) naming conventions from BEM, the idea of common OOP principles in CSS (inheritance, OCP, SRP) from OOCSS, context-independent class names from Modular CSS naming conventions

Contents

Key concepts

Component

Class Location
.panel ./Component/_panel.scss
.nav-bar ./Component/_nav-bar.scss

Component is a reusable module of UI (e.g. nav-bar, panel, form). Component consists of elements (e.g. form__title) and can be extended by subclasses.

Element

Class Location
.panel__header ./Component/_panel.scss

Component is built of elements. Elements is an integral parts of a component and cannot be reused outside of component scope.

Subclass

Class Location
.panel--primary ./Component/Panel/_primary.scss

Following OOP practices, we inherit from a base component to a its subclass For example, when we are required of a dialog window, we create ./Component/_dialog.scss where put the base styles for any dialogs that we can have within the application. Then we add ./Component/Dialog/_alert.scss where set the extending styles for the concrete modal window. Now we refer to a concrete component in the HTML like that:

<div class="dialog dialog--alert">..</div>
<div class="dialog dialog--prompt">..</div>

Themed Component

Class Location
.theme-halogen .foo ./Component/_foo.scss

Component Example

HTML
<div class="progressbar progressbar--big">
    <output class="progressbar__status">
        Install is 70% complete
    </output>
    <progress class="progressbar__progress" value="70" max="100"></progress>
    <div class="progressbar__actions">
        <div class="icon icon--pause">pause</div>
        <div class="icon icon--play is-hidden">resume</div>
    </div>
</div>
./Component/_progressbar.scss
.progressbar {
  position: relative;
}
.progressbar__progress {
  border: 0;
  position: absolute;
  width: 100%;
  bottom: 0;
  left: 0;
  appearance: none;
  &::-webkit-progress-bar {/*..*/ }
  &::-webkit-progress-value {/*..*/ }
  &::-moz-progress-bar {/*..*/ }
}
.progressbar__status {
  display: flex;
  position: relative;
  font-size: 1rem;
}
.progressbar__actions {
  position: absolute;
  bottom: 0;
  right: 0;
  > .icon { /*..*/ }
}
./Component/Progressbar/_big.scss
.progressbar--big > .progressbar__status {
  font-size: 1.6rem;
  text-transform: uppercase;
  padding: 16px;
}
./Component/Progressbar/_small.scss
.progressbar--small > .progressbar__status {
  font-size: 1.1rem;
  text-transform: lowercase;
  padding: 11px;
}

State

State classes are intended to represent a UI unit state: .is-expanded, .is-hidden, .has-error.

HTML
<div class="l-main has-error">
<aside class="sidebar is-hidden">...</aside>
</div>
./Component/_l-main.scss
.l-main {
  /* default style */
  &.has-error {
    /* state modified style */
  }
}
./Base/_global-state.scss
/* Global state */
.is-hidden {
  display: none !important;
}

Theme

Theme classes used to alternate the style of a component or a layout depending on the context.

HTML
<html class="theme-foo">
  <div class="l-main">
  <aside class="sidebar">...</aside>
  </div>
</html>
./Component/_sidebar.scss
.sidebar {
/* default style */
}
.theme-foo .sidebar {
/* alterntive style */
}

Programmatic Theming

If we need components to change styles according to a set theme (.theme-baz and .theme-qux), we can use a mixin like:

@mixin theme-dialog($theme) {
  $bg: get-theme-style($theme, "bg");
  .theme-#{$theme} .dialog {
    background-color: #{$bg};
  }
}
@each $theme in $themes {
  @include theme-dialog($theme);
}

Where we have in ./Base/_defenitions.scss:

$themes: baz qux;
@function get-theme-style($theme, $key) {
  $baz-map: (
    "bg": $baz-bg
  }
  $qux-map: (
    "bg": $qux-bg
  }
  @if $theme == "baz" {
    @return map-get( $baz-map, $key );
  }
  @if $theme == "qux" {
    @return map-get( $qux-map, $key );
  }
  @return map-get( $baz-map, $key );
}

Theme Example

<div class="theme-foo">
  <article class="entry">
    <h2 class="entry__heading">Lorem ipsum dolor</h2>
    <time datetime="2008-02-14 20:00" class="entry__time">2 hours ago</time>
  </article>
  <article class="entry">
    <h2 class="entry__heading">Lorem ipsum dolor</h2>
    <time datetime="2008-02-14 20:00" class="entry__time">2 hours ago</time>
  </article>
  <article class="entry">
    <h2 class="entry__heading">Lorem ipsum dolor</h2>
    <time datetime="2008-02-14 20:00" class="entry__time">2 hours ago</time>
  </article>
</div>

<div class="theme-halogen">
...
</div>

File Structure Example

Styles
├───Component
│   │   _form.scss
│   │   _l-holygrail.scss
│   │
│   └───Form
│       │   _auth.scss
│       │   _nav.scss
│       │
│       ├───Auth
│       │       _login.scss
│       │
│       └───Nav
│               _search.scss
│
└───Base
    │   _h5b-normalize.scss
    │   _base.scss
    │   _definitions.scss
    │   _global-state.scss
    │   _animations.scss
    │
    └───Mixin
            _media.scss


Naming Conventions

  • Class name represents source location. Let's say styles for .form--nav--search is expected in the file Component/Form/Nav/_search.scss File Structure).
  • Layout component classes/source files are prefixed with l-.
  • State classes are prefixed with is- or has- (e.g. .is-hidden, .has-success).
  • Theme classes are prefixed with theme-.
Class Entity
.btn, .main-nav a component (only hyphen delimited names)
.main-nav__title element (subcomponent)
.btn--primary, .main-nav--landing-page subclass
.is-hidden, .has-success a state
.l-holygrail a layout component
.theme-default, .theme-garland a theme
Further readings

Selector Conventions

Keep selectors short

Remember that browser reads selectors from right to left, long selectors may give it an extra workload. Besides it is a unwanted contribution to production style-sheet file size. Deep nesting in CSS-preprocessor sources may cause the described problems even without your awareness. So, keep nesting no more than 3-4 levels.

Avoid @extend-ing in SASS/LESS. It adds a long CSS selectors in a compiled code.

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

IDs can be used in HTML for fragment identifiers and JavaScript hooks, but IDs should never be used in CSS. Functional element attributes can be quite handy for styling (e.g. input[disabled]), but we rather avoid styling via custom attributes (data-attr) for a better separation of concerns. 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.

Loose Coupling (Tag Independence)

Avoid qualified selectors (prepended with tag). Thus you will gain additional agility in moving classes around components.

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.

What's wrong with BEM?

BEM introduces an excellent class naming convention. However they suggest to use Modifier both as component extension and as component state. Consider the following example:

<a class="btn btn--primary">OK</a>

Here we see a concrete button of type primary (btn--primary) that extends Subclass represents a component.

<a class="btn btn--primary is-hidden">OK</a>

is-hidden sets primary button in a particular state. State is usually set on component dynamically (e.g. by JavaScript).

By distinguishing states and subclasses PCSS encourages for better code maintainability

Further Reading

Analytics Bitdeli Badge