Skip to content

Instantly share code, notes, and snippets.

@swervo
Last active August 29, 2015 14:08
Show Gist options
  • Save swervo/439f83208940cd625a15 to your computer and use it in GitHub Desktop.
Save swervo/439f83208940cd625a15 to your computer and use it in GitHub Desktop.
CSS Architecture and Style

CSS Architecture and style

This page describes a suggested architectural approach for CSS. This is followed by guidance on CSS style. For many on this project CSS is unfamiliar and while it is easy to achieve an acceptable short-term result in many cases this can be at a huge cost for future flexibility and maintainability. The industry in general suffers from a lack of rigour in terms of good CSS practices and there is a lack of consensus in terms of which approach to follow. This is largely due to the widely differing requirements of web development projects. However, with the shift towards web application development the importance of good CSS architecture is becoming more apparent.

Note that this page is a work in progress; refer to a backlog of additional items in the end of this page.

Developer pre-requisites

CSS developers need to be familiar with the following identified CSS preprocessor tools:

These tools have great potential for making authoring and maintaining CSS easier, however, they also have some pitfalls and, if used incorrectly, can contribute to CSS code bloat and introduce development overheads. Increasingly our frameworks will be authored using SASS and Compass and the CSS output will be compiled rather that produced by hand. Thus editing the CSS will no longer be possible. Developers will need to be able to interpret and author SASS if they want to be able to make changes to CSS.

Here are a couple of short articles on using SASS:

CSS Architecture

Goals

  • PREDICTABLE

    • Predictable CSS means your rules behave as you’d expect.
    • When you add or update a rule, it shouldn’t affect parts of your site that you didn’t intend.
    • On small sites that rarely change, this isn’t as important, but on large sites with tens or hundreds of pages, predictable CSS is a must.
  • REUSABLE

    • CSS rules should be abstract and decoupled enough that you can build new components quickly from existing parts without having to recode patterns and problems you’ve already solved.
    • It should be possible to cut a module from your markup and paste it somewhere else without any adjustments to the corresponding CSS.
  • MAINTAINABLE

    • When new components and features need to be added, updated or rearranged on your site, doing so shouldn’t require refactoring existing CSS.
    • Adding component X to the page shouldn’t break component Y by its mere presence.
  • SCALABLE

    • As your site grows in size and complexity it usually requires more developers to maintain.
    • Scalable CSS means it can be easily managed by a single person or a large engineering team.
    • It also means your site’s CSS architecture is easily approachable without requiring an enormous learning curve.
    • Just because you’re the only developer touching the CSS today doesn’t mean that will always be the case.
  • PERFORMANT

    • It must be recognised that poor CSS can adversely affect performance
    • CSS must be written to be as performant as possible

CSS selector categories

  • BASE
    • The defaults
    • Frequently single element selectors
    • Whenever this element is on the page, it should look like this
  • LAYOUT
    • Divide the page into sections
    • Hold the modules together
  • MODULE
    • Reusable modular parts of the design
    • The bulk of the application
  • STATE
    • Rules to describe how modules or layouts look in different states
    • Also how modules look within different views or on different sized displays
    • It's implicit that state selectors are applied/removed using javascript
    • States are applied on the root element of modules
  • THEME
    • Control the appearance of modules according to the current theme

CSS selector naming

  • BASE
    • self-explanatory thus no prefix required
  • LAYOUT
    • should be prefixed with layout
  • MODULE
    • Should not require prefixing
    • Anything that isn't prefixed or a base selector is a module selector
  • STATE
    • Prefix with is as in is-hidden or is-selected
  • THEME
    • Control the appearance of modules according to the current theme

CSS code structure

Modules should be styled within separate .scss files to make it easy to work on individual components.

The SASS import statement: @import "modules/moduleName.scss"; can be used for combining modules together for deployment.

CSS Reset/Normalise

Instead of using a blanket CSS Reset, e.g. (for Compass)

@import "compass/reset"

Either, adjust/reset styles individually as required, or, normalise the CSS and then adjust/reset styles individually as required.

normalise.css

A normalise.css plugin for compass is available here:

compass-normalise-plugin

Check that CSS has not been normalised or reset already, keep CSS DRY.

Basic Rules

Do not

  • Do not mix styles across the rule categories (see above)
  • Do not use the HTML structure to determine the style
    • This is a guideline, the aim should be to decouple the CSS and HTML as much as possible
    • There will be situations where descendent selectors are the best solution
    • Try not to go more than 1 level deep
  • Do not over-qualify rules
  • Do not use ID selectors for applying styles use classnames instead
    • ID selectors have very high specificity and thus are hard to override

Do

  • Try and keep your CSS DRY (Don't Repeat Yourself)
  • Use a bootstrap for core styles and for avoiding repetition
    • Compass provides many bootstrap features
    • A Hola specific CSS bootstrap needs to be developed.
  • Separate out styles for positioning
  • Do try to use temporal styling
    • Where the order of css class application determines the style without requiring complicated css selectors
    • Note that the order of the class declarations in the stylesheet determines their specificity NOT the order in which they are applied

References

CSS Style

Grouping Properties

CSS properties should be grouped in the following order:

  1. Box
  2. Border
  3. Background
  4. Text
  5. Other

Where the following applies

  • Box
    • Includes any property that affects the display and position of the box such as:
      • display
      • float
      • position
      • left, top
      • height, width
      • padding, margin
      • box-sizing
      • z-index
  • Border
    • Includes any property that affects the appearance of the border such as:
      • border-width, border-color, etc
      • border-radius
      • border-image
  • Background
    • Includes any property that affects the display and presentation of the background such as:
      • background-color, background-position, background-repeat, etc
      • background-image
      • background-size
  • Text
    • Includes any property that affects the display of text, e.g. :
      • font-family, font-size, etc
      • text-indent, text-transform
      • letter-spacing, line-height
  • Other
    • Includes any property that doesn't fit in any of the categories above, e.g. :
      • transition, transform, appearance
      • user-select
      • cursor, pointer-events
      • opacity

Colours

Colour declarations should use the short-form hexadecimal notation where possible, e.g. :

#FFF

Where this isn't possible the long form hexadecimal notation should be used instead, e.g. :

#00AEEF

Obviously if a transparent colour is required (i.e. rgba) then the appropriate syntax should be used instead, e.g. :

rgba(255, 255, 255, 0.5)

Note that SASS provides a function for converting from hexadecimal to rgba if required:

background-color: rgba(#00AEEF, 0.2);

when compiled results in:

background-color: rgba(0, 174, 239, 0.5);(

CSS units and sizing

Values of 0 (zero) do not require a unit, e.g.

  • DO NOT: margin: 5px 0px 0px 5px;
  • DO: margin: 5px 0 0 5px; Ideally relative units and percentages should be used for sizings to support easy adjustment and scaling. The rem is the easiest to use and stands for root em here sizes are defined as a proportion of the root size. Thus, if a value of 10px is set on the html element: html { font-size: 10px; } Then all other units can be specified relative to this value thus (e.g. for 24px): div { height: 2.4rem; } This allows changes in the root font-size to be cascaded down the CSS. There is a good article on the rem unit here:

The rem unit - via snook

There are other relative units available (eg. the 'em' unit) and more are forthcoming:

The vh, vw (and vm) unit - via snook

The intention here should be to minimise reliance on hard-coded pixel values wherever possible.

CSS Preprosessors and style

SASS provides 4 alternative outputs

  • Nested
  • Expanded
  • Compact
  • Compressed

The compressed version should be used. CSS authors need to become familiar with the SASS syntax so the readability of the CSS output from the compiled SASS is irrelevant.

CSS Lint

The mandatory rules will be reinforced by a Lint-bot which runs as part of the BITR process, in Gerrit.

A CSS lint program will run to evaluate all mandatory rules which can be lint-checked. If all changed css declarations pass the lint test, +1 will be given. If any fail, -1 will be given.

Mandatory Rules for CSS style

Initially these will be based on the rules applied by CSS Lint. However, not all of these may be appropriate so some may be removed.

CSS Selector Naming

Where possible CSS should use semantic naming, this is a good reason not to rely on the HTML structure for applying styles as this provides little information about the function of the elements used.

Meaningful classnames convey far more information about the elements being styled.

useCamelCase, etc, see SMACSS

Comments

The best comments do not need to be written. Do not comment self-explanatory code. Make your code self-explanatory if possible.

SASS Style

There is a good guide here:

CSS Performance

Refer to this page for details of CSS optimisation considerations:

Case study - Application accordion component

To improve the performance of the accordion component a rewrite of the corresponding CSS was performed. The changes that were made produced 2 major benefits:

  1. 30ms reduction in opening latency
  2. Cleaner and simpler CSS - easier to understand, easier to maintain

The performance benefit came from a number of changes:

  1. Elimination of TAG rules
  2. Removal of descendant selectors
  3. Reduction in selector specificity to allow simpler overwriting

Improvements in CSS legibility came from:

  1. Following the CSS style guidelines above
  2. Simplification of selectors and reduction in CSS rule qualification
  3. Removal of redundant units (eg. 0px becomes 0)

Tools

The developer tools provide a CSS Selector Profile tool. This is located under the Profiles tab.

To conduct a CSS Selector profile click 'Start', perform a task, and click 'Stop'.

This provides an overview of how much time is spent matching each CSS selector.

The results vary considerably between profiles so run them several times for best results.

Some selectors will take a long time (>5ms) to match and these are the ones to pay attention to.

For example, for the accordion, the

.accordionElement header

selector was taking about 7ms to match during the accordion opening animation. By changing this to:

.accordionElement-header

(ie., changing it from a tag selector to a class selector) resulted in the matching time being reduced to 2.5ms which is a 4.5ms saving for a single CSS selector

CSS Performance hit list

During the accordion tests peformed above it became apparent that CSS can have a significant impact on performance.

Use of the following CSS properties seemed to be the source of most of the delays:

  • Visibility: hidden

    • Many of the key application screens (those with the class "systemcard": Next, Phone, Lockscreen, Activation Screen, Pin Entry Container, etc) are set to visibility: hidden when not in use
    • This has a significant effect on the accordion opening latency which could be reduced by 60% (from 150ms to 60ms) by setting these elements when not in use to display: none
    • Visibility: hidden means that the elements remain in the DOM and thus their CSS has to be recalculated during the accordion animation.
    • For example, the .pin-key class is one of the top selectors in the CSS profile generated during accordion opening - this is despite the fact that it is not used by the accordion.
  • Pseudo elements (eg., ::before or ::after)

    • Pseudo elements are super useful and extremely versatile largely because they are one of the view CSS selectors that allow the author to define the content
    • Currently, the Next screen (system.css, line 216) uses a pseudo-element for attaching an ellipsis to Next items that lead to a second screen (eg., "Call...", "Text...", etc)
    • Unfortunately, this is the most expensive selector (apart from the browser defaults: div, etc) in the CSS profile generated during accordion opening taking up around 10ms
    • The suspicion is that, as the element is not a real element and thus not really 'in the DOM', it needs to be drawn every frame of the animation
    • This should probably be removed and replaced and the text generated correctly instead
  • :focus pseudo-class

    • This selector also ranks highly in the CSS profile generated during accordion opening. Not clear why this is exactly but making the element read-only during animation could help
    • Suspicion is that replaced elements (ie, form elements, images) are inherently more complicated to animate as they rely on interplay between the browser and the host OS.
  • Replaced elements, eg., form elements, images

    • Replaced elements are likely to be a source of performance issues
    • Images need to have a height and width specified so that the browser knows how much estate to allocate
    • Form elements appear to need a paint operation for every frame of an animation, even with visibility: hidden
      • Seems that this may be due to the caret blinking with focus forcing a paint operation.
      • Make sure to blur form elements when not in use.
  • Descendent selectors

    • Bit of a surprise this one. But Mozilla do not allow the use of descendent selectors in their components due to low efficiency
    • Much of the CSS used for this application uses descendent selectors, generally these are of the form #componentID .selector but some components use .parentElement .element throughout
    • Here it would be more efficient to use a single class name composed like this: .componentName + elementName
    • Thus, instead of #pinEntryContainer .pin-key (taken from lockscreen.css) use .pinEntryKey instead
  • Overqualified rules and high specificity

    • CSSLint, by default, flags using IDs as CSS selectors as an error, the reason for this is that they have really high specificity
    • This high specificity makes it very difficult to overwrite the styles when required without using either in-line stylings or complicated "AND" CSS selectors eg., #componentName.className
    • High specificity is a barrier to simple CSS scaling (note use of the !important statement that has already appeared in our CSS)
    • Using IDs as CSS selectors prevents "temporal styling" where simply adding a class changes the styles applied by an earlier class
    • Related to this is the practice of adding redundant elements to selectors, eg., div#fullScreenNotificationContainer or div.nextBackspace
    • Adding redundant elements to selectors increases their specificity again making them difficult to easily overwrite by application of a simple class
    • The prevalence of overqualified rules in the CSS makes it very difficult for systemUI to provide generic global helper classes eg., .appHide {display: none} as these will never be able to override the overqualified rules
  • Helper classes

    • Related to the overqualified rules issue above is the unavailability of helper classes in system.css
    • It would be useful to have a set of generic helper classes in system.css that could be used by developers throughout the application experience.
    • eg., .appAccelerated { -webkit-transform: translate3d(0, 0, 0) }, so that anyone who wants to make their element a layer they can just add this class
    • Similar classes could be provided for hiding, making elements invisible, font styles, etc
    • Unfortunately the general specificity of the CSS selectors in use means that helper classes would be difficult to apply
    • Alternatively, SASS mixins would be created and used as modules for creating elements styles
  • Overwritten style declarations

    • Many of the styles applied by custom CSS overwrite styles that have already been applied by base classes
    • This means that the browser is having to do a lot of unnecessary work
    • For example this overqualified rule: div#fullScreenNotificationContainer (system.css, line 28) overwrites 100% of the styles applied by .systemcard (card-deck.css, line 53)
    • Keep CSS DRY (Don't Repeat Yourself).
  • !important

    • The super selector !important is in use in some places in the CSS (system.css, line 179, keypad.css, line 67). This should not be necessary.
    • Generally the use of !important is a sign that the developer has selector specificity problems which cannot be overcome other than by using !important.
    • Ideally it should not be necessary to use !important
  • Position: fixed

    • Historically, position: fixed was a source of problems on iOS particularly when it came to scrolling
    • Position: absolute can normally be used instead so Position: fixed should be avoided if possible

Backlog of Additional Topics

Using placeholder/templates

SASS provides a really useful feature called "placeholders" which basically allows the author to create reusable snippets of CSS. These are more versatile than simply using @extend as this can result in considerable CSS bloat. These are similar in nature to another concept called "templates" which, again, are snippets of CSS only in the template case these are intended to be used as additional classes that are applied to elements so that an elements style is built up of multiple modular classes. SASS placeholders have tremendous potential for keeping CSS DRY and should be utilised wherever possible. However, at the beginning of a project, identifying CSS patterns suitable for use as placeholders may be difficult, particularly if the design is not finalised.

There is a good article on using placeholders here:

This page introduces the concept of templates:

Other topics

  • CSS naming convention needs to be determined - namespace in the class name

    • eg. Instead of .viewName .firstDes .secDes .elName (ie., using descendent selectors)
    • Use: .viewName-firstDes-secDes-elName (ie., a single class name constucted from the concatenated elements)
    • Or, better still, use: .viewName-elName (ie., a shortened version of the above)
  • SASS indentation and styling?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment