Modifying CSS Library Defaults in Ionic

I was using the Facebook Messenger app to send a message to a group of friends and even though I'd used the app hundreds of times, it dawned on me that the layout for how the messaging groups are displayed could easily be created with Ionic...just use the default Ionic styles but with a few minor tweeks.

And so this post is dedicated to how to modify a default CSS library minimalistically with a relatively small amount of work.

Although the example here is of the Ionic CSS library, this conversation could really be applied to any scenario in which you're trying to modify an existing library on an application.

And the reason why there is a conversation is because modifying CSS default classes when using a bootstrap library is a necessary action in creating a look and feel that's truly unique and "unlabelable"...representations that can't be attributed instantly to a specific library such as Bootstrap or Ionic. I think there's a certain appreciation when front-end developers don't just settle for the default Boostrap theme or the default Ionic styles.

It often just involves tweeking the buttons, changing the box shadows, or something as simple as changing the color scheme. There are no "bad" ways persay, unless you're using inline styles, but there are methods that are better in certain scenarios.

The four methods of making minimalistic changes to default CSS libraries I'll be going over are:

  1. Change SASS variables
  2. Override default styles with decorators (ACSS)
  3. Add a component to the default library
  4. Create class-based modules (OOCSS)

And in order to illustrate all four, I'll be looping back to the Facebook Groups example and describe how all four methods were used to achieve this style using the Ionic CSS library. As reference, below is the actual markup used and feel free to modify the codepen.

 <body ng-app="starter">

    <ion-pane class="background-color-grey">
      <ion-header-bar class="bar-positive>
        <h1 class="title">Ionic Facebook Groups</h1>
      </ion-header-bar>

      <ion-content ng-controller="MyController">

      <div class="row row-wrap">
        <div class="col col-50" ng-repeat="item in items">
          <div class="card card-facebook margin-none">
            <div class="item item-avatar-center">
              <img ng-src="{{ item.image }}" />
              <h2>{{ item.title }}</h2>
              <p>{{ item.date }}</p>
            </div>
            <div class="item item-divider">
              {{ item.people }}
            </div>
          </div>
        </div>
      </div>

      </ion-content>
    </ion-pane>
  </body>

1. Change the SASS variables (or whatever transcompiler you're using)

<ion-header-bar class="bar-positive>  
  ...
</ion-header-bar>  

The easiest way to achieve minor stylistic tweeks is to change the default SASS variables (if you're using Ionic or LESS if using Bootstrap), which in Ionic is located in the _variables.scss file. It's great for changing default colors, margins, padding, box shadows, etc. and those changes are instantly reflected in the app after re-compiling the Sass files.

The variables are pretty much self-explanatory such as $card-border-color or $item-reorder-icon-size but it does require some form of familiarity with the components (cards, items, lists, range, etc.) and it can get a little confusing with sheer number of variables. For example, Ionic's _variables.scss file is 760 lines of just variables.

/*
To customize the look and feel of Ionic, you can override the variables  
in ionic's _variables.scss file.

For example, you might change some of the default colors:

$light:                           #fff !default;
$stable:                          #f8f8f8 !default;
// Changed $positive from #387ef5 to #3B5998
$positive:                        #3B5998 !default;
$calm:                            #11c1f3 !default;
$balanced:                        #33cd5f !default;
$energized:                       #ffc900 !default;
$assertive:                       #ef473a !default;
$royal:                           #886aea !default;
$dark:                            #444 !default;
*/

In order to change the default header color, I merely changed $positive to #3B5998 (facebook blue) from #387ef5 (just blue). Achieving the same effect with the other methods:

  1. Change SASS variables
    • Great for this
  2. Override default styles with decorators
    • A good alternative
  3. Add a component to the default library
    • Overkill
  4. Create class-based modules
    • Slight overkill

2. Override default styles with decorators (ACSS)

<ion-pane class="background-color-grey">  
    ...
</ion-pane>  

If you're familiar with the Decorator Design Pattern, this is similar to decorating an object but using CSS classes in it's stead. The proper term, however, is Atomic CSS or (ACSS) and revolves around the principle that every selector should contain only one property. In order to decorate an element, you simply add selectors until you achieve the desired style.

It's an easy way to bloat your html template into an undreadable mess but it's a powerful concept for some use cases. For example (below), .row-wrap is a decorator that instructs elements of a row to wrap to the next available space if the maximum row space has been reached...useful if you're applying the Ionic Grid system (flexbox).

/* Decorators */
.background-color-grey {
  background-color: #F7F7F7;
}

.row-wrap {
  flex-wrap: wrap;
  display:-webkit-flex;
  -webkit-flex-flow: row wrap;
}

.margin-none {
  margin: 0px;
}

By the way, the Ionic Grid system makes full use of ACSS principles. It's used to customize the size of flexbox columns.

  • .col-10 : 10% of the parent's width
  • .col-25 : 25% of the parent's width
  • .col-50 : 50% of the parent's width
  • etc.

But as mentioned above, it's easy to get carried away with decorator classes and make the HTML markup unreadble. Take this snippet:

<div class="margin-none padding-horizontal-10 text-align-center border-blue etc......"></div>  

In order to changes the background of facebook grey example to grey, I've added .background-color-grey to <ionic-pane>. Achieving the same effect with the other methods:

  1. Change SASS variables
    • Unlikely to adjust everything we need
  2. Override default styles with decorators
    • Great for this
  3. Add a component to the default library
    • A good option if they'll be reused and are maybe something lacking from the current library
  4. Create class-based modules
    • Overkill

3. Add a component to the default library

<div class="item item-avatar-center">  
    ...
</div>  

The problem with this approach is that you're directly modifying the default CSS library and unless it's documented, it may become forgotten in the chaos of a 7,000 line long CSS file. But it's perfectly acceptable if it's being added as a reusable component and has variable dependencies from _variable.scss.

For example, Ionic list items come with two child classes item-avatar-left and item-avatar-right which orient their first child img element to the left or right, respectively. But in the case of the facebook group example, the first child element needs to be oriented in the center of the box with no text wrapping on either side. The most obvious option would be to use an Atomic CSS selector like margin-center with a single property of margin: 0px auto but the default of item-avatar-left is set to position: absolute and our new selector, margin-center, would not have achieve the desired centering.

If it's a component, we may want to reuse in the future, an alternative would be to add a new component called ionic-avatar-center to the Ionic default library and style it to our liking, making sure to reuse the dependent variables.

// Taken from Ionic source code - _items.scss - line 482
.item-avatar-right,
.item-avatar-right .item-content {
  padding-right: $item-avatar-width + ($item-padding * 2);
  min-height: $item-avatar-width + ($item-padding * 2);

  > img:first-child,
  .item-image {
    position: absolute;
    top: $item-padding;
    right: $item-padding;
    max-width: $item-avatar-width;
    max-height: $item-avatar-height;
    width: 100%;
    height: 100%;
    border-radius: $item-avatar-border-radius;
  }
}

// New component
.item-avatar-center,
.item-avatar-center .item-content {
  min-height: $item-avatar-width + ($item-padding * 2);

  > img:first-child,
  .item-image {
    margin: 10px auto;
    max-width: $item-avatar-width;
    max-height: $item-avatar-height;
    width: 100%;
    height: 100%;
    border-radius: $item-avatar-border-radius;
    display:block;
  }
}
}

Achieve the same effect with the other methods:

  1. Change SASS variables
    • Unlikely to adjust everything we need
  2. Override default styles with decorators
    • The html class attribute would become too bloated and unlikely to override defaults without several !importants
  3. Add a component to the default library
    • Great for this
  4. Create class-based modules
    • The default alternative

4. Create class-based modules (OOCSS)

<div class="card card-facebook">  
    ...
</div>  

One of the principles of OOCSS is that we should separate structure from skin...meaning that we tear classes that into two categories: 1) classes that modify the overal structure of the element (position, width, height, etc.) and 2) classes that modify the look of the element (box-shadow, color, font-size).

Another principle of OOCSS is that we should make classes agnostic to elements and by doing so, we are able to use specific classes for a variety of different element...all in the name of class reuse.

In recreating the facebook groups, I adhered to the first principle but ingnored the second to illustrate that sometimes making classes with one specific use-case is the desired method.

For example, Ionic cards are nearly identical to facebook group cards. They both have very similar box-shadow, border-radius, colors, and text-align and the differences are in mere pixels. Why not create a general subclass of cards that makes all these adjustments? And why care about their reuse (principle 2) and just select specific elements directly if I don't anticipate reusing it for another module? For example: .card-facebook h2 or .card-facebook p.

Ionic cards themselves are a subset of inset lists, structurally identical, but with a slightly different skin, a box-shadow. And these modules are opinionated. If you want an image to be an avatar (.item-avatar-left), it must be the first element. In the same vein, I will dicate that if you want a proper semantic title and text specifically for facebook cards, you must use an h2 and p, respectively.

/* Class Groups */
.card-facebook {
  border-radius: 6px;
  box-shadow: 0px 0.5px 1px rgba(0,0,0,0.2);
}

.card-facebook h2 {
  font-size: 15px;
  font-weight: bold;
  text-align: center;
}

.card-facebook p {
  color: #999;
  text-align: center;
}

.card-facebook .item-divider {
  font-size: 13px;
  font-weight: 300;
  text-align: left;
  border-top: 1px solid #F3F3F3;
  background-color: #FAFAFA;
}

In order to modify the 'skin' of the cards, I added .card-facebook to Achieve the same effect with the other methods:

  1. Change SASS variables
    • Unlikely to adjust everything we need
  2. Override default styles with decorators
    • The html class attribute would become too bloated
  3. Add a component to the default library
    • A worthy option but I don't plan on reusing it
  4. Create class-based modules
    • Great for this