Skip to content

Instantly share code, notes, and snippets.

@bradfrost
Last active October 24, 2019 08:30
Show Gist options
  • Save bradfrost/f491b20af157861ea6b0019f37f506e1 to your computer and use it in GitHub Desktop.
Save bradfrost/f491b20af157861ea6b0019f37f506e1 to your computer and use it in GitHub Desktop.
Frontend Guidelines WIP

Frontend Guidelines

These guidelines will govern how we write frontend code for The Spruce (and hopefully beyond!)

HTML Guidelines

HTML Principles

  • Author semantic markup - HTML gives structure to content, and browsers, search engines, and assistive devices make use of that structure to benefit users. Headings should be <h1> through <h6> tags, groups of objects should be marked up as <ul> or <ol> tags, and so on.
  • Legibility - Markup should be easily readable by authors, especially considering that markup is often mixed with Freemarker and other non-HTML code. Use proper spacing, naming, and commenting conventions to keep code legible for yourself and your teammates.
  • Bake in accessibility - Incorporate accessibility best practices into markup as you author markup rather than tacking it on as an afterthought.

Commenting

All closing tags for HTML elements that span multiple lines must contain a comment on the same line that says identifies what the tag belongs to, such as <!--end primary-nav__item-->.


<div class="card">
		...
</div><!--end card-->

Spacing

Finding the sweet spot between density and whitespace can be tricky. With that in mind, spacing in HTML isn't strictly enforced, but add appropriate space around like items to cluster related tags together in order to keep the code legible.

This:


<ul class="primary-nav__list">
		
		<li class="primary-nav__item">
				<a href="" class="primary-nav__link">Link</a>
		</li><!--end primary-nav__item-->
		
</ul><!--end primary-nav__list-->

But maybe not this:

<ul class="primary-nav__list">
		<li class="primary-nav__item">
				<a href="" class="primary-nav__link">Link</a>
		</li><!--end primary-nav__item-->
</ul><!--end primary-nav__list-->

Or this:


<ul class="primary-nav__list">
		
		<li class="primary-nav__item">
			
				<a href="" class="primary-nav__link">Link</a>
		
		</li><!--end primary-nav__item-->
		
</ul><!--end primary-nav__list-->

Again, spacing isn't strictly enforced, but don't be afraid of a little white space.


CSS Guidelines

Guiding principles

  • Consistency is key - Writing as a team means sticking to conventions we collectively established. Writing CSS consistently makes authoring, testing, and refactoring easier, which keeps everybody happy.
  • Modularity – There should be clear separation between components to ensure each component is as reusable and portable as possible. That involves choosing agnostic names that describe a component's structure rather than the content that may live inside it (i.e. checklist vs recipe-checklist). Components exist in a hierarchy so make even simple components (molecules) stand on their own two feet rather than being dependent on a broader context. Legibility is key. – Developers should be able to understand CSS code at a glance and understand the purpose of any given selector. Use consistent syntax, appropriate spacing, and comprehensive commenting to ensure anyone can easily understand what's going on.
  • Legibility over succinctness – Keeping CSS legible and understandable means sacrificing a shorter syntax.
  • Keep things flat – Long selector strings should be avoided wherever possible in order to keep CSS as DOM-independent and modular as possible.

Class prefixes

Class prefixes are used to each class to make it more apparent what job that class is doing. Here’s what class prefixes we landed on:

  • Unprefixed classes are reserved for UI components only. .card or .header or .social-share are examples of UI components.
  • l- for layout-related styles, such as .l-grid__item
  • u- for utilities, such as .u-margin-bottom-double
  • is- for specific states, such as .is-active or .is-disabled. These state-based classes would apply to
  • js- for targeting JavaScript-specific functionality, such as .js-modal-trigger. No styles are bound to these classes; they’re reserved for JavaScript targeting only. (Note: this approach may be revisited if the system isn't set up for

BEM syntax

We're using BEM as the CSS methodology. BEM stands for “Block Element Modifier”, which means:

  • Block is the primary component block, such as .card or .btn
  • Element is a child of the primary block, such as .card__title.
  • Modifier is a variation of a component style, such as .alert--error.

Utilizing this methodology removes ambiguity about the job a class is doing and allows us to create more legible code. A few notes about BEM syntax:

  • Never include multiple child elements in a single class name (i.e. primary-nav__item__link). We don't say "I need to scratch my face nose," so keep it to a single name.
  • Never use capital letters in class names. Words are separated by a - dash, such as .primary-nav or .primary-nav__subnav-link.
  • Elements of a component must appear in their own line rather than nested inside the block element (see nesting guidelines below). So this:
.card {  
    ...
}  

.card__title { }

Not this:

.card {  
    &__title {  }
}  
  • Modifiers can appear nested inside the parent block if only a few lines long. See "Sass nesting" below for more details.
  • Modifier styles modify a block's attributes (i.e. person--tall) rather than a block's context (meaning person--is-on-a-bus would be incorrect).

Commenting

Our code base mostly follows the commenting guidelines laid out in CSS Guidelines.

Top-level commenting

The top of any major Sass partial should begin with a comment block containing the name of the component or partial in all uppercase preceded by a pound sign (#COMPONENT, for example). This serves as an "h1" for the partial, and the pound sign allows us to easily find the component within the site structure.

/*------------------------------------*\
    #COMPONENT
\*------------------------------------*/

Style block-level comments

/**
 * Component element
 */

Each declaration

Headnotes

We're using headnotes to explain non-obvious style declarations. There's no need to explain what background: red does, but create a headnote for

/**
 * Component element
 * 1) Places element in the top right corner of parent
 */
.component__element {
    display: flex;
    position: absolute; /* 1 */
    top: 0;
    right: 0;
    z-index: 2;
}

Sass nesting

Nesting in Sass can be very convenient, but runs the risk of poor output with overly long selector strings. Follow the Inception Rule and never nested Sass more than three layers deep.

In order to keep the CSS flat principle in mind, we are limiting Sass nesting to only the following four use cases:

  1. Modifiers of a style block
  2. Media queries
  3. Parent selectors
  4. States

Style block modifiers

For modifiers, if the rule is only a few lines long, the modifier can be nested inside the parent like so:

/**
 * Alert message banner
 */
.alert {
    border: 1px solid gray;
    color: gray;

    /**
     * Error Alert
     */
    &--error {
        border-color: red;
        color: red;
    }
}

Style modifiers longer than 3 or 4 lines reduce the legibility of the code. In these instances do not nest the style modifiers and instead move it below the primary style block.

Media queries

Component-specific media queries should be nested inside the component block.

.primary-nav {
    /* Base styles */

    /**
     * 1) On larger displays, convert to a horizontal list
     */
    @media all and (min-width: 40em) {
        display: flex; /* 1 */
    }
}

Sass parent selectors

The CSS makes use of Sass’s parent selector mechanism. This allows all rules for a given component to be maintained in one location.


/**
 * Primary navigation
 */
.primary-nav {
    /* Base styles */

    /**
     * Primary navigation appearing in the header
     * 1) Since the header is set to `display: flex`, setting the left margin to `auto`
     *    positions the navigation to the right.
     */
    .header & {
        margin-left: auto; /* 1 */
    }
}

When to use parent selectors vs style modifiers

Consider a .person block. There are properties that are innate to a person: height, weight, hair color, etc. .person--tall and .person--short may be modifiers of the main .person block, as they modify the intrinsic properties of the .person block.

Objects also exist in different contexts, and that can influence style and behavior. A .person can be riding in a subway car or taking a shower, and behavior will no doubt change based on that context. The intrinsic properties of the .person do not change, but their changed context requires modifications.

.person {
    /* Base styles */

    .on-subway & {
        /* Context-specific styles */
    }
}

CSS declaration order

While exact ordering of CSS declaration isn't strictly enforced, try to follow the following conventions:


.component {
		[box-model properties]
		[positioning properties]
		[font properties]
		[other cosmetic properties]
		[animation properties]
}

Note: the reason we're not alphabetizing declarations is because some related declarations (i.e. display: flex, flex-direction: column, align-items: center, and justify-content: center) would appear in wildly different places.

Anatomy of a Sass partial

Putting all this together, let's take a look at a sample Sass partial that can serve as a boilerplate for creating

/*------------------------------------*\
    #COMPONENT NAME
\*------------------------------------*/

/**
 * 1) A brief description of the component.
 * 2) Display like properties together
 */
.component-name {
    display: flex; /* 2 */
    flex-direction: column;
    align-items: center;
    justify-content: center;

    &:hover, &:focus {
        background: blue;
    }

    /**
     * Modifier
     */
    &--error {
        border-color: red;
    }

    /**
     * Component appearing inside Another Component
     * 1) This is a Sass parent selector, used to
     * 2) This positions the component in the top right corner
     */
    .another-component & {
        position: absolute; /* 2 */
    }

    @media all and (min-width: $bp-large) {
        margin-bottom: 2rem;
    }
}

/**
 * Component child
 */
.component-name__child {
    font-size: $font-size-large;
}

Variables

Sass variables are used to control. Here are some prefixes we'll follow for Sass variables:

Colors

  • $color-brand- as a prefix for brand-related colors
  • $color-gray- for neutral colors. The value of the variable is the percentage of black, so $color-gray-50 means "50% black", aka #808080.
  • $color-dim- for semi-transparent colors. This
  • $color-utility- for utility colors, such as success, error, warning, and info messages.

Fonts

  • $font-family- is used to define a font stack.
  • $font-size- for font sizes. We use t-shirt sizing for font size variables, such as $font-size-small or $font-size-large.

Layout

  • $l- for layout-specific rules, such as $l-max-width for capping the maximum width of a container.

Border

  • $border- for border-related variables, such as border width or radius values.

Animation

  • $anim- for animation-specific variables, such as animation duration or easing values.

Breakpoints

  • $bp- for major breakpoint values that are shared across

Sass file structure

[TODO]

JavaScript Guidelines

[TODO]

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