Skip to content

Instantly share code, notes, and snippets.

@gaambo
Forked from stowball/front-end-guidelines.md
Created October 25, 2019 20:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gaambo/ed0495d2cfb571fe183b8a420274f0b4 to your computer and use it in GitHub Desktop.
Save gaambo/ed0495d2cfb571fe183b8a420274f0b4 to your computer and use it in GitHub Desktop.
Front-End Guidelines

Front-End Guidelines

Table of Contents generated with DocToc

CSS

Our CSS is written in Sass SCSS format using the BEM (Block, Element, Modifier) convention.

Sass/SCSS

Sass is an industry-standard CSS pre-processor, which allows us to extend CSS to add the features necessary to support building maintainable applications.

It gives us the ability to make CSS more of a programming language, with support for conditionals, functions, loops, math, variables and more.

I highly recommend you read the Sass Basics introduction if you're unfamiliar with its features.

As with any technology, some of Sass's features, such as @extend, can be used in detrimental ways. We should opt to write standard CSS as much as possible, and only enhance it with Sass where necessary.

BEM

BEM is a highly useful, powerful, and simple naming convention that makes your front-end code easier to read and understand, easier to work with, easier to scale, more robust and explicit, and a lot more strict.

I recommend you read this good introduction to the BEM syntax and BEMantic: DRY Like You Mean It to understand the reasoning behind and the benefits of using BEM.

Briefly, it works like this:

CSS Syntax

.block-name {
    // usually a root component, but new block contexts inside of blocks can be created
}

.block-name--modifier {
    // a variation of .block-name
}

.block-name__element {
    // a child of .block-name
}

.block-name--modifier {
    .block-name__element {
        // style this differently when the component is modified
    }
}

HTML Syntax

<div class="block-name block-name--modifier">
    <p class="block-name__element"></p>
</div>

Namespaces

We also supplement our class names with a few namespaces:

  • a-: Signifies that this element has an animation applied to it.
  • l-: Signifies that something is purely related to creating Layout, such as our grid system.
  • o-: Signifies that something is an Object, and that it may be used in any number of unrelated contexts to the one you can currently see it in. Making modifications to these types of class could potentially have knock-on effects in a lot of other unrelated places.
  • u-: Signifies that this class is a Utility class. It has a very specific role (often providing only one declaration) and should not be bound onto or changed. It can be reused and is not tied to any specific piece of UI. It is regarded as a last-resort when elements need to be styled uniquely and/or don't require a component context.
  • is-, has-: Signifies that the piece of UI in question is currently styled a certain way because of a state or condition. It tells us that the DOM currently has a temporary, optional, or short-lived style applied to it due to a certain state being invoked. State classes are chained in CSS to increase specificity, e.g .block.is-visible {}

Please read More Transparent UI Code with Namespaces to understand the use of each namespace.

Authoring Style

Our preferred CSS authoring style is determined in sass-lint.yml.

All developers should install an appropriate sass-lint editor plugin in order to provide immediate feedback of warnings and errors in the developer's code. We also provide command line linting via gulp lint.

Note: sass-lint currently has a bug that prevents us from getting a 100% pass. For this reason, it is the developer's responsibility to ensure they adhere to the formatting style, as we cannot yet add sass linting to a build step or pre-commit hook.

We also have additional editor configuration rules defined in .editorconfig, so please install an appropriate EditorConfig plugin for your editor.

By adhering to sass-lint and EditorConfig, we drastically reduce the amount of "coding standards issues" in Pull Requests.

Authoring Syntax Overview

  1. Try to use a BEM class name for styling every element. However, this is not always possible and may be too cumbersome in some circumstances.
  2. Limit nesting to modified children, pseudo elements and media queries.
  3. Do not nest more than 3 levels deep.
  4. All un-nested rules should start at column 0.
  5. Do not indent .block__elements to denote hierarchy.
  6. Do not use Sass' & selector to create BEM selectors, such as .block { &__element {} }.
  7. Indent with 4 spaces.
  8. Order properties alphabetically.
  9. Each selector starts on a new line.
  10. One new line between blocks.
  11. Use double quotes for strings.
  12. Create a Sass partial for every root Block.

References

CSS Guidelines and Sass Guidelines also provide lots of other sane guidelines to help writing maintainable CSS, which are often followed in our projects when not explicitly superseded by our Front-End Guidelines.

UI Framework Architecture and Features

To reduce the need to start from scratch with "sensible defaults" on each new, web project, we have a UI Framework that provides base styles, a flexbox grid system, common components and utility classes.

Features that may need to be customized on a per-project basis are provided as easily overwritten !default variables, which are defined at the top of each relevant file.

You can use as much or as little as you like from the UI Framework, but you'll often import the three parent partials from the root:

  • _helpers.scss: which includes, but is not limited to, functions to allow you to use consistent color, spacing and z-index values, tinting/shading of colors and generating custom utility classes.
  • _core.scss: which includes a reset and normalized elements cross-browser, the flexbox grid system, and over 20 utility classes.
  • _components.scss: currently only includes the base styles for accordions and tabs. Ideally, we'd identify components that can be pulled from application code and made more generic for later re-use.

The order in which the partials are included is important, and is detailed in the next section.

Using Colors

The UI Framework provides a function c() (and its longer alias color()) to extract a color from your application's palette for use in CSS.

With a palette defined like so:

$palette: (
    blue: (
        twilight: #f2ffff
    ),
    gray: (
        outer-space: #2d343e
    )
);

you can access these colors within a component:

.block-name {
    background-color: c(blue twilight);
    color: c(gray outer-space);
 }

will output:

.block-name {
    background-color: #f2ffff;
    color: #2d343e;
 }

to ensure that you're only using colors from your agreed palette in the application.

Using Spacings

Similarly to colors, having consistent spacings defined for margins and paddings will create a more unified look for the application.

With the following $u-spacings:

$u-spacings: (
    default: 10px,
    large: 16px
);

your CSS will have access to these spacings:

.block-name {
    margin: spacing(large);
    padding: spacing(default);
}

which will output:

.block-name {
    margin: 16px;
    padding: 10px;
}

Common Utility Classes

The utility classes you'll most commonly use are:

  • u-color-[variant]: For setting the color (and an SVG's fill) to a palette color from above.
  • u-display-[variant]: For changing how an element display, such as block, flex, and inline-block.
  • u-flex-[variants]: Various different utilities for changing parent and child flex behaviors.
  • u-spacing-[variant]: For creating consistent padding and margins between elements using the spacings mentioned above. With the default $u-spacing-types, spacing classes are provided for (margin|padding)(-(bottom|left|right|top|horizontal|vertical))?, which are represented as .u-spacing-(m|p)(b|l|r|t|h|v)-size {}.
  • u-visually-hidden: For accessibly hiding text from sighted users, while still making it available to assistive technologies. Can also be substituted with aria-label.

Creating Utility Classes

mixins/_generate-utils.scss allows you to generate "simple" utility classes, which have a 1:1 relationship between the class name and the property name/value.

For instance, the following SCSS:

$u-box-sizing-types: (
    border-box,
    content-box
) !default;

@include generate-utils(box-sizing, $u-box-sizing-types);

will render:

.u-box-sizing-border-box {
    box-sizing: border-box !important;
}

.u-box-sizing-content-box {
    box-sizing: content-box !important;
}

We passed in the name of the utility class (box-sizing) and a list of types that we wanted to generate ((border-box, content-box)), and the class names were constructed from these, with each of the properties and values set accordingly.

This works well where the CSS property name and values makes sense, but there are cases where we want to deviate from "mistakes" in CSS naming, or in cases where we wish to use an alternative, more descriptive name for our class names.

For example, where the the W3C accidentally omitted the hyphen in nowrap, we can fix that for consistency with our naming convention by specifying that it be assigned to no-wrap:

$u-white-space-types: (
    no-wrap: nowrap
) !default;

@include generate-utils(white-space, $u-white-space-types);

will output:

.u-white-space-no-wrap {
    white-space: nowrap !important;
}

We can take this even further by also providing an alternative property name for use in our class name only:

$u-text-case-types: (
    lower: lowercase,
    normal: none,
    title: capitalize,
    upper: uppercase
) !default;

@include generate-utils((text-case, text-transform), $u-text-case-types);

which generates:

.u-text-case-lower {
    text-transform: lowercase !important;
}

.u-text-case-normal {
    text-transform: none !important;
}

.u-text-case-title {
    text-transform: capitalize !important;
}

.u-text-case-upper {
    text-transform: uppercase !important;
}

Other, more complex utility classes, such as this to generate background colors for all colors in the project, are custom and cannot use generate-utils():

.u-bg-color {
    @each $hue, $color in $palette {
        @each $name, $value in $color {
            &-#{$hue}-#{$name} {
                background-color: $value !important;
            }
        }
    }
}

which will output something along the lines of:

.u-bg-color-blue-lynch {
    background-color: #638294 !important;
}

.u-bg-color-blue-boston {
    background-color: #2f96b4 !important;
}

…etc

Notice how the SCSS for this utility didn't also output a “placeholder selector” (%u-bg-color-#{$hue}-#{$name}) version of the class? The reason: as developers, we will never need to @extend this in our components, as we will already have access to the colors as discussed above, and it would create unnecessarily large, and often unwanted chained selectors.

Using the Flexbox Grid System

Flexbox is a modern CSS layout module that eliminates much of the issues and overhead of creating layouts with traditional methods like floats and display: inline-block.

The UI Framework has a robust and highly customizable, responsive grid system built on top of flexbox which allows us to produce layouts with ease. You can find it at layout/_l-grid.scss.

Flexbox can be quite hard to comprehend at first, so I recommend reading A Complete Guide to Flexbox to learn the concepts and properties of it.

As the grid system is a layout module, it uses the l- namespace in its class names as described above. It is most commonly used to create "arbitrary" layout to correctly position components and elements on the page, although it can also be used to create layout from inside of a component. However, it is often better to let the component handle the layout and positioning of its children through CSS, so that it is encapsulated within the component.

The grid system class names are typically assigned to <div>s, but lists such as <ul>/<ol> can be also used where it makes sense semantically.

A grid consists of a container .l-grid and any number of direct children, named .l-grid__cell.

<div class="l-grid">
    <div class="l-grid__cell"></div>
    <div class="l-grid__cell"></div>
</div>

By default, a grid is "fluid" and will try to give each cell an equal width, with a minimum width of the cell's content size, and wrap where necessary. However this can be controlled with simple BEM modifier classes:

  • .l-grid--dont-wrap: prevents the grid cells from wrapping, which is useful for maintaining a side-by-side icon & text layout regardless of the length of the text content.
  • l-grid__cell--do-shrink: allows a cell to shrink its minimum width and wrap its contents.
  • l-grid__cell--dont-grow: prevents the cell width from ever exceeding its content's width.

When creating more "rigid" layouts, we often need to specify that a column is a particular width. We can achieve this by using the responsive width modifier classes.

Our grid prefers that we use percentage widths (as opposed to column counts), as they make more sense when grids are nested. Column counts are achievable, but not enabled by default.

The percentage width classes follow this pattern:

  • .l-grid__cell--25: will always make the cell 25% wide.
  • .l-grid__cell--50-at-768: will make the cell 50% wide when the browser width is 768px or greater.

Note: the available widths and breakpoints available as classes are controlled by the variables $l-grid-sizes and $l-grid-breakpoints, or via the combined $l-grid-breakpoints-and-sizes which allows us to specify exactly which breakpoints should have specific widths to reduce the size of the generated CSS like so:

$l-grid-breakpoints-and-sizes: (
    0:    (20%, 25%, 33.3333%, 50%, 66.6666%, 80%, 100%),
    760:  (50%),
    1000: (33.3333%, 50%),
    1200: (33.3333%, 66.6666%),
    1400: (25%),
    1800: (25%),
    2200: (20%)
);

The grid can also provide automatic gutters between cells (in both rows and columns) by using a .l-grid--spacing-SIZE-NAME class, where SIZE-NAME is the name of a key/value pair from the $l-grid-spacings map. To maintain consistency with the spacing utility classes described earlier, we set $l-grid-spacings to equal a custom $spacings map, which is used by both the grid and $u-spacings.

The final, killer feature of flexbox is it allows us to control the alignment of children within the cells, both horizontally and vertically. It's well known that vertically aligning something to the center was notoriously difficult, but with our grid system, it's easy!

The default alignment options (as specified in $l-grid-alignments) are center and end, which generate alignment modifier classes for horizontal alignment (.l-grid__cell--align-ALIGNMENT) and vertical alignment (.l-grid__cell--justify-ALIGNMENT).

Managing z-indexes

z-index spaghetti is common, with developers desperately trying to use arbitrary values to create the desired layering of elements. The UI Framework provides a z() function, which when combined with a $z-indexes map of indices in the application, allows us to sanely manage the z-index with no collisions.

Read Sassier z-index Management for Complex Layouts to understand the premise and how to use it.

Application Architecture

While the UI Framework contains the base styles and features needed for a solid foundation, the majority of styles and components written will be specific to an application.

The architecture we use looks like this:

/main.scss
    settings/
        …partials.scss
    styles/
        …folders/
            …partials.scss

Where main.scss includes the UI Framework and application folders in this order:

// UI Framework Helpers
@import "/path-to/node_modules/totvslabs-ui-framework/web-styles/helpers";

// Project Functions
@import "styles/functions/…partials.scss";

// Project Settings
@import "settings/colors";
@import "settings/sizes";
@import "settings/spacings";
@import "settings/z-index";

@import "settings/overrides";

// UI Framework Core
@import "/path-to/node_modules/totvslabs-ui-framework/web-styles/core";

// UI Framework Components
@import "/path-to/node_modules/totvslabs-ui-framework/web-styles/components";

// Project Styles
@import "styles/mixins/…partials.scss";

@import "styles/animations/…partials.scss";

@import "styles/base/…partials.scss";

@import "styles/objects/…partials.scss";

@import "styles/components/…partials.scss";

@import "styles/layout/…partials.scss";

@import "styles/pages/…partials.scss";

@import "styles/utils/…partials.scss";

Within each group, partials should be added alphabetically. This enables you to easily locate a particular partial, and highlights that with BEM, the order in which they're included is not important.

Let's look at each in order.

Functions

styles/functions/…partials.scss

Above, we learned about some custom functions in the UI Framework to help manage colors, spacing and z-indexes. The application may need additional functions to help keep code DRY, with each "public" function in its own partial. Functions in Sass must return a value.

Settings

settings/_partials.scss

  • _colors.scss: Contains the application's $palette grouped by hue with descriptive color names for use in the .u-bg-color- and .u-color- utility classes. It's also "exported" as $colors for use with the c() helper and allows you to assign meaningful names to colors for consistent use throughout the application.

  • _sizes.scss: Variables of generic sizes, such as $icon-sizes, that are used throughout different partials should go here.

  • _spacings.scss: A map of $spacings for use throughout the application with spacing() and .l-grid.

  • _z-index.scss: A map of $z-indexes for use throughout the application with z().

  • _overrides.scss: A single place to override !default variables in the UI Framework with values specific to the application.

Mixins

styles/mixins/_partials.scss

A mixin lets you make groups of CSS declarations that you want to reuse throughout the application, and also allow arguments to be passed in to make them more flexible. A good example is the triangle() @mixin, which can create triangles of various sizes, directions and colors.

Animations

styles/animations/_partials.scss

Consistent animations are an integral part of creating harmonious user experiences. To achieve this, use namespaced .a- animation classes to attach animations to elements.

Base

styles/base/_partials.scss

Base should be used for generic changes to elements such as standard link styles, or for things that don't make sense as part of a component for (if and) when it is moved to the UI Framework.

Objects

styles/objects/_partials.scss

Objects are small pieces of shared/repetitive UI, that have little or no children and styling, and which do not require promotion to a component. Examples of objects include o-text-inputs and o-lists etc.

Components

styles/components/_partials.scss

Components are finite, discrete, implementation-specific parts of our UI that most people (users, designers, developers, the business) would be able to identify.

Components range from smaller pieces of UI, such as buttons to pagination, or more complex "view" components like header banners, which need to separated in to multiple BEM blocks (and files) for each component to follow our authoring/architectural process.

Components should be responsible for the majority of their implementation via CSS, but can and do contain other objects and components – such as icons – where it makes sense.

More complex components, or ones that are reliant on the re-use of certain values, should assign these values to named variables at the top of the file.

As mentioned in "UI Framework – Using Colors", all colors referenced within components should be used with c() to ensure that inconsistent, arbitrary colors from outside the agreed $palette don't litter the code-base.

Spacing within components need not always use agreed "structural" spacings, as component internals often need to break free to look natural.

Occasionally, some components may need to change appearance when they're consumed within another component. There are two approaches to handle this:

  1. BEM Mix: The preferred approach is to create a new __element class on the consuming .block-name, and apply this class to the HTML element under these circumstances only.
  2. When BEM Mixes aren't suitable (for whatever reason), to reduce the need to add modifiers to style the child component differently every time, it is OK in these instances to override the child in the parent component with a nested selector. Note that this increases the child component's specificity, which could potentially cause maintenance issues. so this practice should be used as sparingly as possible.

When you are confident that a component is re-usable enough for multiple projects, its base "pattern" should be extracted to the UI Framework, and application specific component modifications should be made where necessary.

Layout

styles/layout/_partials.scss

While the grid system is used for arbitrary layout within a view, applications generally have a defined layout of columns, rows, positioning and alignment that is fixed throughout. For these cases, custom layout classes should be created. They may or may or not use the .l- prefix.

Pages

styles/pages/_partials.scss

Highly customized styles for specific pages. As these are incredibly unique, they do not need to follow our standard authoring practices with regards to BEM naming and specificity, but should be contained within a .page--PAGE-NAME to quarantine the styles to this page only.

Utilities

styles/pages/_partials.scss

Similar to utility classes in the UI Framework, these are application specific utilities. As with components, these may end up being generic enough to warrant being added to the UI Framework.

Using Class Names in HTML

When writing HTML, the order in which classes should be applied to elements is:

  1. .l-: layout classes.
  2. component classes.
  3. .o-: object classes.
  4. .a-: animation classes.
  5. .u-: utility classes.

The order of classes within each group should be ordered by priority.


HTML

HTML is the most basic building block of the web: it describes and defines the content of a webpage, which is arguably the most important thing users need.

Although the web is turning more "app-like", a fundamental understanding of HTML is still required to present the application's content to as many users as possible.

Semantic Markup

HTML uses element tags to describe the type of content within. As HTML and the web has matured, new tags have been introduced to better cater for specific types of content. Most tags have implicit "roles" which convey meaning, but some are inert and purely presentational.

It is essential that you identify and use the element most suited to a piece of content. Let's break down the ones we typically use in an application:

Document Metadata

Metadata contains information about the page.

  • <title>: This element defines the title of the document, shown in a browser's title bar or on the page's tab. It can only contain text. It is imperative to use a descriptive title for each page/view so users understand where they are within the app.

Content Sectioning

Content sectioning elements allow you to organize the document content into logical pieces. Use the sectioning elements to create a broad outline for your page content.

  • <aside>: This element represents a section of a document with content connected tangentially to the main content of the document (often presented as a sidebar).
  • <footer>: This element represents a footer for its nearest sectioning content or sectioning root element. A footer typically contains information about the author of the section, copyright data or links to related documents.
  • <h1><h6>: These elements represent six levels of section headings. <h1> is the highest section level and <h6> is the lowest. The most important heading of the page should be assigned to <h1>, with future headings reducing in "size" as appropriate. Heading levels must not be skipped to ensure a logical hierarchy is created.
  • <header>: This element represents a group of introductory or navigational aids. It may contain some heading elements but also other elements like a logo, a search form, and so on.
  • <nav>: This element represents a section of a page that links to other pages or to parts within the page: a section with navigation links.
  • <section>: This element represents a generic section of a document, i.e., a thematic grouping of content, typically with a heading.

Text Content

Use text content elements to organize blocks or sections of content. Important for accessibility and SEO, these elements identify the purpose or structure of that content.

  • <div>: This element is the generic container for flow content and does not inherently represent anything.
  • <dl>: This element encloses a list of groups of terms and descriptions. Common uses for is to display metadata (a list of key-value pairs).
  • <dt>: This element identifies a term in a description list (<dl>). This element has to be a child element of a <dl>. It is followed by a <dd> element.
  • <dd>: This element indicates the description of a term in a description list (<dl>).
  • <main>: This element represents the main content of the <body> of a document or application.
  • <ol>: This element represents an ordered list of items, typically rendered as a numbered list.
  • <ul>: This element represents an unordered list of items, typically rendered as a bulleted list.
  • <li>: This element is used to represent an item in a list. It must be contained in a parent element: an <ol> or <ul>.
  • <p>: This element represents a paragraph of text.

Inline Text Semantics

Use the HTML inline text semantics to define the meaning, structure, or style of a word, line, or any arbitrary piece of text.

  • <a>: This element creates a hyperlink to other pages, files, locations within the same page, or any other URL.
  • <b>: This element represents a range of text without conveying any special importance, and is typically rendered in bold.
  • <em>: This element marks text that has stress emphasis. It is typically displayed in italic.
  • <i>: This element represents a range of text without conveying any special importance, and is typically rendered in italic.
  • <span>: This element is a generic inline container for phrasing content, which does not inherently represent anything. It can be used to group elements for styling purposes.
  • <strong>: This element gives text strong importance, and is typically displayed in bold.
  • <time>: This element represents either a time on a 24-hour clock or a precise date in the Gregorian calendar.

Images

  • <img>: This element represents an image in the document.

Table Content

The elements here are used to create and handle tabular data.

  • <table>: This element represents tabular data —that is, information expressed via a two-dimensional data table.
  • <tbody>: This element groups one or more <tr> elements as the body of a <table> element.
  • <td>: This element defines a cell of a table that contains data. It participates in the table model.
  • <tfoot>: This element defines a set of rows summarizing the columns of the table.
  • <th>: This element defines a cell as header of a group of table cells. The exact nature of this group is defined by the scope and headers attributes.
  • <thead>: This element defines a set of rows defining the head of the columns of the table.
  • <tr>: This element defines a row of cells in a table. Those can be a mix of <td> and <th> elements.

Forms

HTML provides a number of elements which can be used together to create forms which the user can fill out and submit.

  • <button>: This element represents a clickable button. A type of submit indicates that it will submit the form data to a server. A type of button indicates that it will trigger an in-page action via JavaScript.
  • <form>: This element represents asection that contains interactive controls to submit information to a server.
  • <fieldset>: This element is used to group several controls as well as labels (<label>) within a form.
  • <legend>: This element represents a caption for the content of its parent <fieldset>.
  • <input>: This element is used to create interactive controls for forms in order to accept data from the user. How an <input> works varies considerably depending on the value of its type attribute.
  • <label>: This element represents a caption for an <input>. It is important that all <input>s have an associated <label>, even if it is not presented visually.
  • <select>: This element represents a control that provides a menu of options.
  • <optgroup>: This element creates a grouping of options within a <select> element.
  • <option>: This element is used to create a control representing an item within a <select> or <optgroup> element.
  • <textarea>: This element represents a multi-line plain-text editing control.

Accessibility and Inclusivity

Making your web applications accessible is extremely important, and sadly, often forgotten.

However, when you start to consider, design and build for all users regardless of their physical and cognitive ability, their device, situation or skill level – instead of a limited, privileged subset – your application's UX invariably becomes better and easier to use for all users.

This broader ethos of designing for all is called Inclusive Design. I recommend you read Microsoft's Inclusive Design guidelines to understand how to recognize exclusion, learn from diversity and solve problems for many.

I also strongly recommend you view the presentation, "Building a better, accessible web" to learn about disability types, statistics, testing and how to implement with accessibility in mind.

Types of Disabilities

The three primary disability groups we need to cater for are:

  • Cognitive & learning disabilities: People with ADD, autism, dementia, dyslexia and more.
  • Mobility & physical impairments: people with limited dexterity or upper limb disabilities.
  • Vision disabilities: people with color blindness or low/no vision.

Assistive Technologies

The two primary assistive technologies are:

  • A keyboard (or a device that replicates keyboard functionality), thus we need to ensure that the page and every interactive element is accessible via standard key presses, such as Tab, Shift+Tab, Space, Enter and cursor keys.
  • A screen reader, which announces to the user everything they need to know to be able to successfully traverse the app without sight: the structure, the content, the interactive components and their current state, and much more.

Implementing Accessibility

Building accessible applications is based on three principles:

  1. Follow the Web Content Accessibility Guidelines (WCAG)
  2. Use semantic HTML (and understand the impact of CSS)
  3. Implement WAI-ARIA (often with JavaScript)

Mattstow’s hierarchy of #a11y*

1. Use WCAG

WCAG 2.0 is a W3C standard which recommends how to make web content more accessible to people with disabilities, while being generic enough to not dictate technology choices. As depicted in the diagram above, it should form the foundational concepts for your application.

Its criteria is divided in to twelve major guidelines across four core principles: Perceivable, Operable, Understandable, and Robust.

The core tenets can be summarized to:

Principle 1: Perceivable

  • Information and UI components must be presentable to users in ways they can perceive, regardless of user's situation.
  • Content is in a meaningful sequence and the structure and relationships of information can be easily conveyed.
  • Text alternatives are provided for non-text content.
  • Decorative content is hidden from assistive technologies.
  • Color is not used as the only visual means of conveying information.
  • A sufficient color contrast is provided for all elements.

Principle 2: Operable

  • Make all functionality available from a keyboard.
  • A sensible, sequential tabbing/focus order is used.
  • Focus indication states are clearly visible.
  • Headings and labels describe the topic or purpose.
  • The purpose of links and buttons can be determined from their text alone.

Principle 3: Understandable

  • Make text readable and understandable: use simple copy and avoid all caps and justified text.
  • Reduce the complexity of the UI and its functions.
  • Make content appear and operate in predictable ways: don't shift context unless initiated by a user.
  • Help users avoid and correct mistakes correctly labelling inputs and errors are identified and described to the user.

Principle 4: Robust

  • Use correct, valid markup.

2. Use Semantic HTML

#semantic-markup, we described the various, common HTML elements we can utilize and their purpose, so hopefully you have a better understanding of which tag to use and when.

We also mentioned that the basic content structure is the most important part of any interface. To this end, it's often beneficial to at first "build" UI unstyled, so you can solely focus on the content and markup. It's often too easy to replicate a visual design mockup without fully understanding the intrinsic relationship required between components and elements should you be unsighted.

With an unstyled document, you will clearly be able to determine if your hierarchy makes sense, whether things are in a logical order, and if an element is appropriate for its current use.

Here are some quick-win HTML tips:

  • Add a valid and relevant lang attribute to <html>.
  • Don‘t disable zooming. All users should be able read at their preferred zoom level.
  • Use appropriate HTML5 elements to define the structure of the application.
  • Don‘t skip heading levels. Headings are the primary way of describing hierarchy.
  • Use native browser controls where possible. They have full accessibility features baked it.
  • Only <a> and <button> should be “clickable”. How do you decide which to use? if you might right click it, use a link, otherwise use a button.
  • All form elements must be labelled, otherwise users won't be able to understand the purpose of an <input>.
  • Placeholders aren‘t labels. Placeholders defines a short hint intended to aid the user when the control has no value.

3. Use ARIA

HTML alone isn't always enough to make modern web applications acccessible. In these cases, we can utilize WAI-ARIA, a spec that provides additional features to HTML, to help create Accessible Rich Internet Applications.

ARIA uses role attributes to describe the type of element and aria- property attributes to describe relationships, functions and UI states.

Roles

While most HTML elements have intrinsic roles, we can supplement other elements to better describe the content within.

The two most common categories of role are navigational landmarks and widget roles.

Examples of typical landmark roles are:

  • banner: A region that contains mostly site-oriented content, rather than page-specific content.
  • complementary: A supporting section of the document, designed to be complementary to the main content at a similar level in the DOM hierarchy, but remains meaningful when separated from the main content.
  • contentinfo: A large perceivable region that contains information about the parent document.
  • main: The main content of a document.
  • navigation: A collection of navigational elements (usually links) for navigating the document or related documents.
  • search: A landmark region that contains a collection of items and objects that, as a whole, combine to create a search facility.

Examples of typical widget roles are:

  • alert: A type of live region with important, and usually time-sensitive, information.
  • dialog: A dialog is a descendant window of the primary window of a web application.
  • tab: A grouping label providing a mechanism for selecting the tab content that is to be rendered to the user.
  • tablist: A list of tab elements, which are references to tabpanel elements.
  • tabpanel: A container for the resources associated with a tab, where each tab is contained in a tablist.

Properties

ARIA properties describe specific information about an element. They are separated in to states – which may change frequently based on user interaction – and more static properties – which are much less likely to change throughout the application's life-cycle.

Properties are also are categorized as follows:

  • Widget attributes
  • Live Region attributes
  • Drag-and-Drop attributes
  • Relationship attributes

Examples of typical widget properties are:

  • aria-current (state): Indicates the element that represents the current item within a container or set of related elements. Useful for navigation items.
  • aria-expanded (state): Indicates whether the element, or another grouping element it controls, is currently expanded or collapsed.
  • aria-haspopup: Indicates the availability of an interactive popup element that can be triggered by an element.
  • aria-hidden (state): Indicates whether the element is exposed to an accessibility API.
  • aria-invalid (state): Indicates the entered value does not conform to the format expected by the application.
  • aria-label: Provide an accessible label to an element. Overrides any existing text content.
  • aria-pressed (state): Indicates the current "pressed" state of toggle buttons.
  • aria-selected (state): Indicates the current "selected" state of various widgets.

Examples of typical properties from the other groups are:

  • aria-atomic: Indicates whether assistive technologies will present all, or only parts of, the changed region.
  • aria-controls: Identifies the element (or elements) whose contents or presence are controlled by the current element.
  • aria-labelledby: Identifies the element (or elements) that labels the current element.
  • aria-live: Indicates that an element will be updated, and describes the types of updates the user agents, assistive technologies, and user can expect from the live region.
  • aria-owns: Identifies an element (or elements) in order to define a visual, functional, or contextual parent/child relationship between DOM elements where the DOM hierarchy cannot be used to represent the relationship.

Thinking Accessibly

You now know the concepts of WCAG, the responsibility of HTML to create structured content, and how to use ARIA to describe roles and states of the UI.

However, to put it all together and understand how to build accessible applications, you need to change your mindset to ask different questions while you develop the UI:

  • How could I use this if I couldn't see it?
  • Would I know where I am now?
  • Would I know where to go next?
  • Could I get there and have it announced to me?
  • How will I be notified of an action I performed, or if an error occurred?

Once you stop thinking of UI as disparate components and instead see the bigger picture of a user's journey, the experiences you create for all users will be greatly improved.


Building UI

When building and maintaining the UI for a long-running project, the end goal is to eventually not have to write any CSS at all. This might seem like an odd statement for a front-end developer to make, but in time, with a mature application, you should be able to utilize existing layouts, components, objects and utilities to create new features consistent with the rest of the application. There will, of course, always be exceptions.

On the flip-side to this, the beginning of a project will see you writing the most CSS: creating all the new components and objects required to recreate the design and UX teams' vision for the application. However, if, as discussed in Components, the UI Framework is kept up to date with base component patterns, subsequent projects should take less and less time to get off the ground.

Recognizing and Normalizing Patterns

When presented with a visual design, we need to ensure that we're not just building specific pages or views, but the building blocks for all views. We need to identify and separate out any spacings, colors, objects and components that have the potential to be re-used in the application.

Design teams often (accidentally) deviate from their own style guide, so we also need to take the time to normalize discrepancies. However, if the discrepancies are large, we may need to consult Design to work out more consistent, unified patterns as a team. It's important to remember that the greater the number of discrepancies and variations there are in a project, the more time developers will spend maintaining and documenting them.

Identifying Components

A piece of UI with components identified

At first glance, the above UI looks simple, but is actually constructed of nine unique components, and one object. By using a LEGO or Tetris approach to constructing UI, we are able to piece together simple, re-usable, single-responsibility components to form more complex organisms.


SVG

General Workflow

  1. For icons that fit in a square, use a canvas of 100 × 100. For other icons make the largest dimension 100px
  2. Simplify the amount of nodes and paths that are used: redraw as necessary and combine paths of identical colors
  3. Reduce the amount of fractional pixels used for node positions (unfortunately, the default in Sketch is to not draw on whole pixels)
  4. If the icon is to be colored via CSS, make its paths fill color #000
  5. Name the SVG file after what the icon is, not its function in the app
  6. Our gulp process minifies SVGs automatically, but for React Native minify manually with SVGOMG

Optimizing images for SVG

It is often not enough to export a SVG directly from an editor without manual modifications to reduce the file size.

Let's look at some comparisons:

Not optimized from Sketch

Not optimized Fluig Identity logo

Minifies to 9.18kb

Bad path edges

Notice how many path nodes there are, and that the shapes are actually badly drawn and don't align correctly.

Optimized manually

Optimized Fluig Identity logo

Minifies to 3.76kb

With this version, obscured paths have been simplified, paths have been redrawn to reduce the number of nodes, and identically colored paths have been combined.

However, Sketch doesn't seem to be able to achieve all of this, so Affinity Designer is my SVG/design tool of choice.

When exporting from Affinity Designer, ensure "Use document resolution" is checked, otherwise the SVG's viewBox will be incorrect.

SVG Icon System

TODO


React Native

Our method for building and styling React Native components and applications is heavily inspired by our approach for web applications: the architecture is similar, and we use a BEM-like approach for component styling, and utilize color and spacing methods and utilities as we would on the web.

Creating Components

Unless a component requires internal state or life-cycle hooks, a component should use the Stateless Functional Component (SFC) pattern to increase performance. It is trivial to convert an SFC to as class-based component later on where necessary.

Component Architecture

Generally:

  • Each Component resides in a PascalCase directory under components that matches the BlockName.
  • index.js: contains the component.
  • styles.js: contains styles (if necessary).

However, if a Block requires multiple child components:

  • BlockName.js: contains the outermost block component.
  • BlockName__Element.js: contains a child component.
  • index.js: imports BlockName and all BlockName__Elements, and exports them as named exports.
  • styles.js: contains styles for BlockName and all BlockName__Elements.

Stateless Functional Components

An SFC pattern resembles the following:

// @flow
import React, { PropTypes } from 'react';
// import { FEATURES } from 'react-native';
// import other-node-modules;
// import other-components;

const ComponentName = (props: Object) => {
    return (
        /* Component JSX */
    );
};

// Omit if ComponentName has no defaultProps
ComponentName.defaultProps = {
    KEY: VALUE
};

// Omit if ComponentName accepts no props
ComponentName.propTypes = {
    KEY: PropTypes.TYPE
};

export default ComponentName;

Class-based Components

TODO

Styling Components

As discussed earlier, the BEM methodology and naming convention allows us to reduce the complexity of styling and develop with speed and predictability. By following a similar approach to naming, modifiers and states (not to be confused with a component's internal state), we can create self-contained, easily styled components in any situation.

styles.js

This file exports a default object with contains keys and style properties for each component.

A component with child components, modifiers and states may look like this:

// @flow
export default {
    'component-name': {
        KEY: VALUE
    },
    'component-name--modifier': {
        KEY: VALUE_WHEN_MODIFIED
    },
    'component-name--state': {
        KEY: VALUE_WHEN_IN_THIS_STATE
    },
    'component-name__element': {
        KEY: VALUE_OF_ELEMENT
    },
    'component-name--modifier component-name__element': {
        KEY: VALUE_OF_ELEMENT_WHEN_MODIFIED
    },
    'component-name--state component-name__element': {
        KEY: VALUE_OF_ELEMENT_WHEN_IN_THIS_STATE
    }
};

Similar to using BEM in CSS, component styles are encapsulated within a namespace, and developers can clearly see the styles for all components, children and variations.

react-native-bem

This module allows us to style (the Block and Child) elements of a component based on a kebab-cased name, with optional modifier and state attributes.

Features

It exports three functions:

  • bem(): exported as the default function, which is used to apply styling to components.

    Its signature is bem (blockName: string, props: Object, rules: Object). blockName is the kebab-cased name of the component to style, props is the component's props and rules is the style object (from style.js).

  • renderBemChild(): exported as a named function, this can be used in place of rendering {prop.children} to pass a BEM selector to a specific child.

    Its signature is renderBemChild (props: Object, style: Array<any>, index: number = 0). props is the component's props, style is the result of calling bem() with a BEM Mix selector, and index is the index of the child element to style.

  • renderBemChildren(): exported as a named function, this can be used in place of rendering {prop.children} to pass a BEM selector to all child.

    Its signature is renderBemChildren (props: Object, style: Array<any>) and is identical to renderBemChild() but without index.

Modifiers

As with BEM in CSS, a modifier is a flag on a block or element, which is used to change an element's (or multiple elements') appearance.

react-native-bem supports two types of modifiers with the M prefix:

  1. Boolean: The prop MalignCenter={true} uses the --align-center style definitition.
  2. String: The prop Malign="right" uses the --align-right style definition.
States

While not a part of BEM, states are an elegant addition to signify that the piece of UI in question is currently styled a certain way because of certain condition.

Common stateful namespaces begin with is and has.

react-native-bem state props uses the S prefix, like so: SisDisabled={true}.

Common Usage
Component Definition

components/HelloWorld/index.js

import React, { PropTypes } from 'react';
import { View, Text } from 'react-native';
import bem from 'react-native-bem';
import styles from './styles';

const HelloWorld = (props: Object) => {
    const b = (selector) => bem(selector, props, styles);

    return (
        <View style={b('hello-world')}>
            <Text style={b('hello-world__text')}>
                Hello, world
            </Text>
        </View>
    );
};

HelloWorld.propTypes = {
    MbooleanModifier: PropTypes.bool,
    MstringModifier: PropTypes.string,
    SaStateName: PropTypes.bool
};

export default HelloWorld;
Styles Definition

components/HelloWorld/styles.js

export default {
    'hello-world': {
        backgroundColor: '#000',
        marginBottom: 5,
        padding: 5
    },
    'hello-world--boolean-modifier': {
        borderColor: '#f00',
        borderWidth: 4
    },
    'hello-world--string-modifier-foo': {
        padding: 20
    },
    'hello-world.a-state-name': {
        opacity: 0.5
    },
    'hello-world__text': {
        color: '#fff',
        fontSize: 20
    },
    'hello-world.a-state-name hello-world__text': {
        color: '#ff0'
    }
};
Usage within a View

View.js

import HelloWorld from './components/HelloWorld';



render() {
    return (
        <HelloWorld />
        <HelloWorld MbooleanModifier={true} />
        <HelloWorld MstringModifier="foo" />
        <HelloWorld SaStateName={true} />
        <HelloWorld MbooleanModifier={true} MstringModifier="foo" SaStateName={true} />
    );
}

which results in:

Demonstrating the Hello World component with all the modifiers and states applied

So, you can see that with adding and removing props, either at run-time or by default, you will be able to quickly adapt your components' appearance to reflect the current state of the application.

Advanced Usage
Manipulating Children

There may be times where you wish to pass in child components from within your view, like so:

render() {
    return (
        <HelloWorld>
            <HelloWorld__Text />
        </HelloWorld>
        <HelloWorld MbooleanModifier={true}>
            <HelloWorld__Text />
        </HelloWorld>
        <HelloWorld MstringModifier="foo">
            <HelloWorld__Text />
        </HelloWorld>
        <HelloWorld SaStateName={true}>
            <HelloWorld__Text />
        </HelloWorld>
        <HelloWorld MbooleanModifier={true} MstringModifier="foo" SaStateName={true}>
            <HelloWorld__Text />
        </HelloWorld>
    );
}

Out of the box, the children wouldn't be affected by parent modifier and state props (meaning the text color would always be white in the above example). however, by changing the HelloWorld component to accept children enhanced with BEM like so:

import bem, { renderBemChild } from 'react-native-bem';

const HelloWorld = (props: Object) => {
    const b = (selector) => bem(selector, props, styles);

    return (
        <View style={b('hello-world')}>
            {renderBemChild(props)}
        </View>
    );
};

where HelloWorld__Text is:

// @flow
import React from 'react';
import { Text } from 'react-native';
import bem from 'react-native-bem';
import styles from './styles';

const HelloWorld__Text = (props: Object) => {
    const b = (selector) => bem(selector, props, styles);

    return (
        <Text style={b('hello-world__text')}>
            Hello, world
        </Text>
    );
};

export default HelloWorld__Text;

we get the exact same output as before, just with the added flexibility of being able to control which child components are rendered from within the view.

As mentioned earlier, renderBemChild() also accepts a second parameter of a styles object (like those returned from a bem() selector) and an index.

This allows us to render children with additional styles applied, which can be useful when a component is consumed by another (known as a BEM Mix), and its appearance needs to change to reflect that.

If we update our existing files to this:

styles.js

export default {
     existing styles
    'bem-mix__text': {
        color: '#0ff'
    }
};

HelloWorld.js

const HelloWorld = (props: Object) => {
    const b = (selector) => bem(selector, props, styles);

    return (
        <View style={b('hello-world')}>
            {renderBemChild(props, b('bem-mix__text'))}
        </View>
    );
};

We can see that the additional styles were applied to the HelloWorld__Text components, turning them cyan.

Demonstrating the Hello World__Text component has inherited styles from the BEM mix

Using a Component's Internal State

Some components, such as a <TextInput />, need to be styled differently based on an internal state, such as if it has focus. Instead of cumbersomely alerting parent components to whether it's focused or not and changing a state prop in the view, we can merge in the component's state at render() time.

With the following component:

components/TextBox/index.js

// @flow
import React, { Component } from 'react';
import { TextInput } from 'react-native';
import bem from 'react-native-bem';
import styles from './styles';

class TextBox extends Component {
    constructor () {
        super();

        this.state = {
            SisFocused: false
        };
    }

    _onBlur = () => {
        this.setState({
            SisFocused: false
        });
    }

    _onFocus = () => {
        this.setState({
            SisFocused: true
        });
    }

    b (selector: string) {
        return bem(selector, Object.assign({}, this.props, this.state), styles);
    }

    render () {
        return (
            <TextInput
                {...this.props}
                style={this.b('text-box')}
                onBlur={this._onBlur}
                onFocus={this._onFocus}
            />
        );
    }
}

export default TextBox;

and its related styles.js

// @flow
export default {
    'text-box': {
        backgroundColor: '#fff',
        borderColor: '#000',
        borderWidth: 2,
        color: '#333',
        fontSize: 14,
        height: 40,
        paddingHorizontal: 10,
        paddingVertical: 0
    },
    'text-box.is-focused': {
        borderColor: '#00f'
    }
};

Because the TextBox component's b() helper merges the component's props with its internal state (which has a correctly named SisFocused property), it apply styles for when the TextInput is focused!

Using BEM with internal state to set a TextInput's focus style

Using Colors

TODO

Using Spacings

TODO

Utilities

TODO

SVG Icon System

TODO

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