Conventions

  • Create and extend CSS files following global patterns. Minimize customization, as features are rarely styled in isolation.
  • Avoid descendant selectors (i.e. don’t use .sidebar h3).
  • Avoid attaching classes to elements (i.e. don’t do div.header or h1.title).
  • Avoid using !important, except in rare cases.
  • Use a LESS / SCSS compiler for readability. CSS-in-JS is popular, but not as useful in Agile teams with only basic CSS skill sets.
  • Use shorthand properties everywhere (i.e. border: 1px solid red)
  • Always specify margin and padding as complete selector sets (i.e. use margin: 1rem 0 0 0 to specify top margin rather than margin-top: 1rem)
CSS Files

Aim for the smallest CSS footprints by organizing LESS / SCSS styles into separate files that can be imported selectively for each view as needed.

Order styles from general to specific. Watch out for selector order - the last selector loaded wins.

Variables

Repeating elements are good candidates for turning into variables:

  • @media breakpoints
  • color
  • border and border-radius
  • box-shadow
  • @font-face
Declaration Order

Order declarations alphabetically under selectors (they're easier to maintain that way).

Place vendor selectors (-webkit, -moz, -ms, -o) before their conventional counterparts.

.site-header {
    align-items: center;
    border-bottom: 1px solid var(--color-border);
    display: flex;
    justify-content: flex-start;
    opacity: 1;
    padding: .5rem 2rem;
    top: 0px;
    -webkit-transition: 1s;
    -moz-transition: 1s;
    -ms-transition: 1s;
    -o-transition: 1s;
    transition: 1s;
    visibility: visible;
    width: 100%;
    z-index: 100;
}
Selector Names

Leverage the name to describe a feature or function better than or in place of a comment (i.e. .site-header-mobile).

Classes

  • Follow a kebab lowercase case convention (class="block-header-mobile"), or Block_Element-Modifier (BEM) syntax when not using SCSS/LESS (class="search-card__header-mobile).
  • Name and collect selectors into the blocks, features or components they describe. Avoid one-offs.
  • Avoid IDs as code hooks, but if unavoidable, prefix the class name with "js-" or "ts-" (class="js-search-button")

IDs

  • Avoid IDs as styling hooks. Reserve for JavaScript/TypeScript hooks. Follow camelCase convention (i.e. id="modal-Dialog-Open-Click-21")

Order classes to create a visual hierarchy, like:

.card-wrapper
.card
.card_header
.card_header_image
.card_main
.card_main-red
...

Create global classes that can be added as modifiers to any HTML element as needed (i.e.: hide, center and apply block shadow to the card block class="card hide center shadow")

Text Case

Sentence

  • upper => THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
  • lower => the quick brown fox jumps over the lazy dog

Flat

  • upper => THEQUICKBROWNFOXJUMPSOVERTHELAZYDOG
  • lower => thequickbrownfoxjumpsoverthelazydog

Pascal

  • TheQuickBrownFoxJumpsOverTheLazyDog

Camel

  • theQuickBrownFoxJumpsOverTheLazyDog

Snake

  • upper (aka train case, screaming kebab) => THE_QUICK_BROWN_FOX_JUMPS_OVER_THE_LAZY_DOG
  • lower => the_quick_brown_fox_jumps_over_the_lazy_dog
  • pascal => The_Quick_Brown_Fox_Jumps_Over_The_Lazy_Dog
  • camel => the_Quick_Brown_Fox_Jumps_Over_The_Lazy_Dog

Kebab

  • upper => THE-QUICK-BROWN-FOX-JUMPS-OVER-THE-LAZY-DOG
  • lower (aka dashcase, spinalcase) => the-quick-brown-fox-jumps-over-the-lazy-dog
  • pascal (aka HTTP-Header-Case) => The-Quick-Brown-Fox-Jumped-Over-The-Lazy-Dog
  • camel => the-Quick-Brown-Fox-Jumped-Over-The-Lazy-Dog
Units

Use rem instead of px units for sizing. This helps the UI scale proportionally when the browzer zooms. Set the rem base scale globally by specifying a font-size in the <html> and/or <body> element.

Set this font-size to ten pixels so rem units can be readily be calculated as multiples of ten. For example: 10px = 1rem, 16px = 1.6rem, 250px = 25rem, 599px = 59.9rem ... and so on.

html,
body {
    font-size: 10px;
}

Breakpoints

Write base styles for mobile devices first and override them with breakpoint media queries for progressively larger displays.

Spec breakpoints that fall between the widths of major display types. The combination of max and min width breakpoints approach makes working with phones easier.

@media (max-width: 59.9rem) {}
@media (min-width: 60rem) {}
@media (min-width: 90rem) {}
@media (min-width: 120rem) {}
@media (min-width: 180rem) {}

Variable names can help implementation.

@phone: ~"screen and (max-width: 599px)";
@tablet: ~"screen and (min-width: 600px)";
@desktop: ~"screen and (min-width: 900px)";
@widescreen: ~"screen and (min-width: 1200px)";
@superwide: ~"screen and (min-width: 1800px)";

More expressive variable names are better.

@mixin for-phone-only {
    @media (max-width: 59.9rem) {
        @content;
    }
}

@mixin for-tablet-portrait-up {
    @media (min-width: 60rem) {
        @content;
    }
}

@mixin for-tablet-landscape-up {
    @media (min-width: 90rem) {
        @content;
    }
}

@mixin for-desktop-up {
    @media (min-width: 120rem) {
        @content;
    }
}

@mixin for-big-desktop-up {
    @media (min-width: 180rem) {
        @content;
    }
}

Position media queries so they are close the base styles they modify.

.site-menu-desktop {
    display: none;
}

@media (min-width: 600px) {
    .site-menu-desktop {
        display: initial;
    }
}

Or

.site-menu-desktop {
    display: none;

    @media (min-width: 600px) {
        display: initial;
    }
}

Fonts

Font Families

Current favorite web font families are sans-serif => Poppins, Montserrat, Whitney SSm, Avenir, and Barlow. For code blocks => Monospace

Font Scale

A typographic scale for font sizing based on classic proportions like 1:2, 2:3, 2:5, 5:8, etc.

Note: a Minor Third scale can accomodate up to five header sizes. Most sites only need three.

Colors

Using Hex instead of HSL is usually easier for others on the team edit and maintain. Keep the byte count low by using the three-digit shorthand syntax.

:root {
    // Theme Colors
    --color-primary: #1E70B3;
    --color-secondary: #4CA982;
    --color-tertiary: #E6A400;

    // Gray Scale Colors
    --color-black: #000;
    --color-gray-extra-dark: #333;
    --color-gray-medium: #999;
    --color-gray-light: #CCC;
    --color-gray-extra-light: #EEE;
    --color-white: #FFF;

    // Element Color Variations
    --color-nav: var(--color-primary);
    --color-background-shade: #F7F7F7;

    // darken(var(--color-primary), 5%);
    --color-font-link-selected: #1b629d;

    // darken(var(--color-primary), 10%)
    --color-button-dark: #175587;
    --color-nav-active: #175587;

    // lighten(var(--color-primary), 5%);
    --color-searchbar-background: #227ec9;
// darken primary color by 10%
--color-font-link-selected(var(--color-primary) l(-10%)
$color-black: #333;
$color-gray: #999;
$color-green: #70bf63;
$color-lightgray: #d4d4d4;
$color-red: #ff0000;
$color-white: #fff;

$color-background-darkgray: #e7e7e7;
$color-background-gray: #f7f7f7;
$color-background-panel: #007bff12;
$color-border-dark: #b4b4b4;
$color-border-light: #d4d4d4;
$color-border-primary-action: #007a87;
$color-border-secondary-action: #b4b4b4;
$color-button-primary-action: #007a87;
$color-button-secondary-action: #707070;
$color-button-disabled: #e7e7e7;
$color-global-navbar: #1063b7;
$color-global-navbar-active: #0c4a88;
$color-icon: #007a87;
$color-link: #007a87;
$color-link-selected: #595959;

Using HSL allows for more programmatic color control.

// Primary hues
@hueR: 10;
@hueO: 29;
@hueY: 42;
@hueG: 111.7;
@hueB: 210;
@hue0: 0;

// Theme color mix
@color-blu: hsl(@hueB, 84%, 39%);
@color-yel: hsl(@hueY, 95%, 63%);
@color-orn: hsl(@hueO, 86.5%, 59%);
@color-grn: lighten(hsl(@hueG, 42%, 42%),15%);
@color-red: lighten(hsl(@hueR, 100%, 50%), 25%);

// Base color variables
@color-background: hsl(@hue0, 0%, 97%); //#F7F7F7
@color-blu-light: lighten(@color-blu, 10%);
@color-blu-dark: darken(@color-blu, 10%);
@color-nav: @color-blu;
@color-nav-active: darken(@color-blu, 10%);
@color-link: lighten(@color-blu, 10%);
@color-icon: lighten(@color-blu, 20%);

Keep colors global where possible for repeatability across products.

Color Functions
/* set defaults to 'light' prior to choice of background */
:root body {
  --color-background-code: #e1e8ed;
  --color-background-primary: hsla(40, 95%, 100%, 1);
  --color-background-secondary: hsla(40, 95%, 99%, 1);
  --color-background-tertiary: hsla(220, 5%, 90%, 1);
  --color-border: #ccc;
  --color-border-hover: #666;
  --color-font-code: hsla(220, 5%, 20%, 1);
  --color-font: hsla(220, 0, 0, 1);
  --color-font-headers: hsla(220, 5%, 5%, 1);
  --color-font-hover: hsla(40, 5%, 70%, 1);
}

body.light {
  --color-background-code: #e1e8ed;
  --color-background-primary: hsla(40, 95%, 100%, 1);
  --color-background-secondary: hsla(40, 95%, 99%, 1);
  --color-background-tertiary: hsla(220, 5%, 90%, 1);
  --color-border: #ccc;
  --color-border-hover: #666;
  --color-font-code: hsla(220, 5%, 20%, 1);
  --color-font: hsla(220, 0, 0, 1);
  --color-font-headers: hsla(220, 5%, 5%, 1);
  --color-font-hover: hsla(40, 5%, 70%, 1);
}

body.dark {
  --color-background-code: #293742;
  --color-background-primary: hsla(220, 5%, 15%, 1);
  --color-background-secondary: hsla(220, 5%, 10%, 1);
  --color-background-tertiary: hsla(40, 5%, 80%, 1);
  --color-background-featured: hsla(0, 5%, 15%, 1);
  --color-border: #666;
  --color-border-hover: #ccc;
  --color-font-code: hsla(40, 5%, 90%, 1);
  --color-font: hsla(40, 5%, 95%, 1);
  --color-font-headers: hsla(40, 5%, 100%, 1);
  --color-font-hover: hsla(220, 5%, 30%, 1);
}
Color Functions
color: color(var(--color-background-primary) l(-10%));

Icons

Font icons like IcoMoon (with the online IcoMoon App) can consume less memory than PNG and JPG sprites. These icon sets can also be versioned. The defaults are free, and you can upload your own.

@import "icomoon-variables.less";

@font-face {
    font-display: block;
    font-family: '@{icomoon-font-family}';
    font-style: normal;
    font-weight: normal;
    src: local('☺'),
    url('@{icomoon-font-path}/@{icomoon-font-family}.ttf?@{icomoon-version}') format('truetype'),
    url('@{icomoon-font-path}/@{icomoon-font-family}.woff?@{icomoon-version}') format('woff'),
    url('@{icomoon-font-path}/@{icomoon-font-family}.svg?@{icomoon-version}#@{icomoon-font-family}') format('svg');
}

// declare IcoMoon vector font icons globally
[class^="icomoon-"],
[class*="icomoon-"] {
    .font-light();
    font-style: normal;
    font-weight: normal;
    font-variant: normal;
    text-transform: none;
    line-height: 1;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    &:before,
    &:after {
        font-family: '@{icomoon-font-family}' !important;
        text-decoration: none !important;
    }
}

Use named wrapper (container) and block classes to shape w/ width, height, overflow around base class elements.

.thisthat-wrapper {
	display: flex;
	margin: 0 auto; 
	
	.thisthat-block {
		color: red;
		
		.this {
			font-size:  1rem;
		}
		
		.that {
			font-size: .5rem;
		}
	}
}
Scalable Vector Graphics

Good resolution at any scale. Can be version controlled individually. This approach employs a pseudo element to add an SVG icon before or after a string of text with a mask property. It is aligned with display:inline or display:inline-block, and remember to add the content selector.

Example 1
SVG code specified directly in mask-image url.

<a class="outside-link" href="https://somewhere" target="_blank">Block Element</a>
.outside-link {
    color: var(--color-font-link);
    display: inline-block;
    font-family: system-ui;
    font-size: 1.6rem;
    overflow-wrap: break-word;
    text-decoration: none;
    word-break: break-word;
}

.outside-link:after {
    background-color: var(--color-font-link);
    content: "";
    mask-image: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2048 2048'><path d='M1792 256v640h-128V475l-851 850-90-90 850-851h-421V256h640zm-512 1007h128v529H256V640h529v128H384v896h896v-401z'/></svg>");
    -webkit-mask-image: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2048 2048'><path d='M1792 256v640h-128V475l-851 850-90-90 850-851h-421V256h640zm-512 1007h128v529H256V640h529v128H384v896h896v-401z'/></svg>");
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-clip-path: padding-box inset(.4rem 0);
    clip-path: padding-box inset(.4rem 0);
    -webkit-mask-size: 1.3rem;
    mask-size: 1.3rem;
    outline: none;
    padding: 0 1rem;
}

Example 2
SVG file path specified in mask-image url.

<span class="globalnav__login">Login</span>
.globalnav__login {
    display: inline-block;
    width: 10rem;

    &:before {
        background-color: $color-white;
        -webkit-clip-path: border-box inset(2rem 0);
        clip-path: border-box inset(2rem 0);
        content: "";
        margin: 0 .8rem;
        -webkit-mask-image: url("/assets/avatar.svg");
        mask-image: url("/assets/avatar.svg");
        -webkit-mask-position: center;
        mask-position: center;
        -webkit-mask-repeat: no-repeat;
        mask-repeat: no-repeat;
        -webkit-mask-size: 1.5rem;
        mask-size: 1.5rem;
        outline: none;
        padding: 0 .8rem;
    }
}

/assets/avatar.svg

<svg xmlns="http://www.w3.org/2000/svg" width="13.64" height="16" viewBox="0 0 13.64 16"><defs><style>.a{fill:#fff;fill-rule:evenodd;}</style></defs><path class="a" d="M6.82,0A3.74,3.74,0,1,1,3.08,3.74,3.74,3.74,0,0,1,6.82,0ZM6.272,7.936H7.361a6.3,6.3,0,0,1,6.279,6.279A1.791,1.791,0,0,1,11.854,16H1.792A1.8,1.8,0,0,1,0,14.208,6.29,6.29,0,0,1,6.272,7.936Z" transform="translate(0)"/></svg>

External References

Architectures

  • Block Element Modifier (BEM)
  • Object Oriented CSS (OOCSS)
  • Scalable and Modular Architecture for CSS (SMACSS)

Frameworks

Concepts

Colors