Skip to content
This repository has been archived by the owner on Oct 6, 2018. It is now read-only.

Latest commit

 

History

History
490 lines (366 loc) · 17.6 KB

README.md

File metadata and controls

490 lines (366 loc) · 17.6 KB

Scally Components

Contents

What are they?

Components are the discrete custom elements of a UI that enclose specific semantics and styling, they also make up the bulk of a UI. Some examples:

  • Button
  • Pagination
  • Breadcrumbs
  • Dialog
  • Icon

Components are extremely focused implementing only a single part of a UI, so they should never try to do too much. They also shouldn't have any dependencies on ancestral context i.e. where they live in a UI. This makes them extremely portable and robust. Each component should be designed to stand-alone.

When to use?

Before utilising components consider the layout modules and objects. Many of the styles you need will exist in these two sections. In fact your components should hardly ever need any CSS concerned with layout. Utilities should also be considered if you are needing to implement simple, universal patterns such as hiding an element visually (.u-hide-visually), but these are generally used sparingly.

How to use

Take this common UI pattern:

alt text

We can see that this fits the criteria of a component because it:

  • is a discrete custom element of the UI,
  • encloses specific semantics and styling,
  • can exist completely stand-alone.

So in this case we would make the call to create a Pagination component.

HTML for the pagination component:

<nav class="c-pagination" role="navigation" aria-label="Pagination">
  <ul class="c-pagination__list">
    <li class="c-pagination__list__item c-pagination__list__item--count">Pages 1-20 of 200</li><!--
    --><li class="c-pagination__list__item">
      <a href="#" class="c-pagination__link" rel="prev">← Prev<span class="u-hide-visually">ious page</span></a>
    </li><!--
    --><li class="c-pagination__list__item">
      <a href="#" class="c-pagination__link"><span class="u-hide-visually">Page </span>1</a>
    </li><!--
    --><li class="c-pagination__list__item">
      <a href="#" class="c-pagination__link"><span class="u-hide-visually">Page </span>2</a>
    </li><!--
    --><li class="c-pagination__list__item">
      <a href="#" class="pagination__link is-active"><span class="u-hide-visually">You're currently reading page </span>3</a>
    </li><!--
    --><li class="c-pagination__list__item pagination__list__item--skip">
      <a href="#" class="c-pagination__link"><span class="u-hide-visually">Jump to page </span>21</a>
    </li><!--
    --><li class="c-pagination__list__item">
      <a href="#" class="c-pagination__link"><span class="u-hide-visually">Page </span>22</a>
    </li><!--
    --><li class="c-pagination__list__item">
      <a href="#" class="c-pagination__link"><span class="u-hide-visually">Page </span>23</a>
    </li><!--
    --><li class="c-pagination__list__item">
      <a href="#" class="c-pagination__link" rel="next">Next →<span class="u-hide-visually"> page</span></a>
    </li>
  </ul>
</nav>

CSS for the pagination component:

/* ============================================================================
   @COMPONENTS -> PAGINATION
   ========================================================================= */


/**
 * Pagination component rendered as an inline list.
 */


/**
 * Settings.
 */

// Colours
$c-pagination-border-color: darken(#eee, 4%);


.c-pagination {
  @include to-rem(padding, $spacing-quarter);
  border: 1px solid $c-pagination-border-color;
  border-radius: $border-radius;
}


  /**
   * The list `ul`, render inline.
   */

  .c-pagination__list {
    @extend %o-list-inline;
    @extend %o-list-inline--spacing-tiny;
    text-align: center;
  }


    /**
     * List items.
     */

    /**
     * Modifier: skip list item.
     *
     * Renders an ellipsis which breaks apart list items.
     */

    .c-pagination__list__item--skip:before {
      // This right spacing needs to equal the spacing of the list items
      @include to-rem(margin-right, $spacing-third);
      content: "\2026";
      display: inline-block;
      vertical-align: bottom;
      speak: none;
      color: $color-text-base;
    }


     /**
      * The links.
      */

     .c-pagination__link {
        @include to-rem(padding, $spacing-micro $spacing-third);
        display: inline-block;
        background-color: #eee;
        border: 1px solid $c-pagination-border-color;
        border-radius: $border-radius;


        /**
         * Hover and active states.
         */

        &:hover,
        &:focus,
        &.is-active {
          background-color: lighten($color-black, 4%);
          color: $color-white;
          text-decoration: none;
        }
     }

Using layout modules, objects, and utilities in components

Layout modules

As mentioned above your components should avoid having any CSS concerned with layout as that's the job of the layout modules. The most common layout modules you will need are the Grid and the Side-by-side layout modules. No matter how small your layout is, always use a layout module. The layout module classes should be applied directly to the components HTML.

So if we look at this UI:

alt text

We can see that we need some layout to render the circle containing the user's initials to the left of the user's information. We might create a component called User account submit button for this but we'd apply the layout module classes direct to the components HTML like so:

<button class="c-button-user-account" type="submit">
  <span class="l-side-by-side-alt">
    <span class="l-side-by-side-alt__left">
      <span class="c-button-user-account__avatar" aria-hidden="true">c</span>
    </span>
    <span class="l-side-by-side-alt__right">
      <strong class="c-button-user-account__name">Nike</strong>
      <span class="c-button-user-account__url">nike.createsend.com</span>
      <time class="c-button-user-account__last-access" datetime="2009-11-13">Last accessed Today</time>
    </span>
  </span>
  <span class="c-button-user-account__arrow"></span>
</button>

In summary, the rule is; never write any CSS concerned with layout for your components if it can be taken care of by one of the layout modules and apply the layout module classes direct to the components HTML.

Objects

Objects should be treated the same as layout modules in that they should always be used if they can. The difference is that objects should be @extended within the component partial to avoid having object classes littered in the components HTML. This allows components to be more easily updated and keeps a component self-contained.

The pagination component demo'd above is @extending the List inline object and one of it's modifiers, like so:

.c-pagination__list {
  @extend %o-list-inline;
  @extend %o-list-inline--spacing-tiny;
  text-align: center;
}

In summary, the rule is; if an object exists that gives you the CSS you need then use it in your component by @extending it's silent placeholder selector, if @extend isn't suitable then use the object's class direct in the components HTML.

Utilities

Utilities should be avoided in your components as they typically exist to apply styles to elements that don't already have any style hooks in place.

Ideally utilities should be @extended within the component partial. There are times when this isn't possible and it is OK to use a utility class directly in the HTML of the component. This technique should be used sparingly, and only when a component class doesn't already exist. The most common use case for this is when you want to hide an element but only visually. If we look at a snippet of the HTML from the pagination component demo'd above we can see this utility: .u-hide-visually being applied in the HTML:

<li class="c-pagination__list__item">
  <a href="#" class="c-pagination__link" rel="next">Next →<span class="u-hide-visually"> page</span></a>
</li>

One exception to using utilities sparingly on components is when you need to apply whitespace (margin) to the outside of a component, you apply this whitespace using the Spacing utility, this keeps components extremely Portable and robust and Free of constraints. So if we wanted to apply a bottom margin to the pagination component demo'd above we would apply a spacing utility like so:

<nav class="c-pagination  u-s-mb-base" role="navigation" aria-label="Pagination">

In summary, the rule is; never apply single line declaration utilities e.g. .u-float-right {float: right;} to a component unless it's the Spacing utility, the rest of the time only use utilities concerned with simple, universal patterns (multiline declarations) e.g. hide an element but only visually: .u-hide-visually, pin an element to all corners of it's parent: .u-position-pin-all, clear a float: .u-clear-fix, etc. And ideally @extend utilities via their silent placeholder selectors.

Portable and robust

Components should try their best to not be concerned or have any dependencies on ancestral context i.e. where they live in a UI. What this means is that components—if built well—can be moved to different parts of a UI without breaking, making them extremely portable and robust.

To demonstrate this, let's say there is a requirement to also feature the pagination component demo'd above in another part of the UI e.g. a dialog component. The dialog component has a lot less real estate for the pagination component to fit into meaning the component has to be modified in some way to accommodate this, basically we need a compact version.

We could apply these modifications by relying on the components ancestral context i.e. via the components parent element which in this case is the dialog component, so something like this:

CSS

.c-dialog .c-pagination {
  [...]
}

HTML

<div class="c-dialog">
  <nav class="c-pagination"> [...] </nav>
</div>

However this isn't the most optimal way of handling things as it's highly likely that having a compact version is something we're going to need in other parts of the UI—if not now most likely down the track—therefore it should exist within the pagination component itself not tied to another component.

The correct way to handle this is to use the concept of a BEM modifier like so:

CSS

.c-pagination--compact {
  [...]
}

HTML

<nav class="c-pagination c-pagination--compact">

Nested components

As shown in the example above components can exist within other components i.e. the pagination component can exist in the dialog component.

Nested components like this are perfectly fine. It is important though that components try their best not to be dependent on other components as covered above in the previous section.

However saying that this isn't always black and white (like with many things CSS related) and you will sometimes need to make some slight adjustments to a component when it's nested within another component but this should be scrutinised over to make sure it's the most optimal way of handling things.

The last thing you want is a setup where you have many cross dependencies across your components, as things can fast become a house of cards.

Here is one example of when you might need to make adjustments to a component when it's nested within another component; say you have a Button component which is nested within a Site header component, and by site header I mean the main header of the site template where you'll typically find things like the site logo, main navigation, global search, etc.

So we may have a CTA: Log In in the site header component which uses the button component but in this context the button component needs some slight adjustments. The first thing to consider is creating a BEM modifier for the button so that the modified version is available to be used anywhere in the UI, not just the site header. However if you feel a BEM modifier isn't the right choice because the adjustments are extremely specific to the site header then you will make the adjustments within the site header component partial.

When you do this you include a comment so that other developers are aware of the relationship between the components, like so:

.c-site-header {
  [...]


  /**
   * Extend `.c-button` in Components -> Button.
   */

  .c-button {
    @include to-rem(padding-left padding-right, 8);
    @include font-size($font-size-x-small);
  }

See here.

Free of constraints

Components should be free of widths, margins, and in most cases positioning. This allows components to be extremely portable as they can better adapt to the dimensions of an ancestral context.

Avoiding widths and margins is the most crucial here. If we use the pagination component demo'd above and add these styles to the base class:

.c-pagination {
  @include to-rem(width, 400);
  @include to-rem(margin-bottom, $spacing-base);
  [...]
}

We've now fixed this component to always have a rigid width of 400px and a bottom margin of what $spacing-base is, let's say 24px. This may be what you want when you first create the component but adding these default styles is shortsighted and greatly reduces it's reuse because when you need to reuse the component in a different part of the UI—or if the UI it will only ever exist in changes—then the component will most likely break and/or cause breakages to UI surrounding it.

So all components should be 100% fluid i.e. do not define fixed widths. A components width is always determined by the dimensions of an ancestral context.

When creating whitespace (margin) outside of a component e.g. a bottom margin of 24px, it's best not to bake this into the default component styles as demonstrated above, and instead apply it via one of the Spacing utility classes e.g.

<nav class="c-pagination  u-s-mb-base">

Encapsulation

Components should be encapsulated as much as possible, even if that means your CSS is not as DRY as you think it could be. The main aim is to prevent styles from leaking outside of the component, this isolation prevents avoidable complexity and results in higher code reuse.

Testing

An excellent way to test the overrall portability and robustness of your components is to move the component from where it's intended to live in the UI to another location in the DOM e.g. directly after the opening body element. The component should be the full width of it's parent element, so if that's the body element—and the body/html elements do not have a fixed width set on them—then your component should be the width of the viewport.

Or better still create a UI component library where all of your components are dumped into the one web page.

Namespacing

All component classes, settings, and filenames are prefixed with c- so that they're easily identifiable e.g.

  • .c-pagination
  • $c-pagination-foreground-color
  • _c-pagination.scss

Further reading

Make sure to read the documentation within each component Sass partial file as it will contain information about the specific component and it's implementation.