Learn how to master Ionic Framework

In our previous post Ionic 4 vs Ionic 3 — What you need to know about Ionic 4 we surfaced the main differences between Ionic 3 and Ionic 4.

Then, when Ionic 5 was released, we created the post What's new in Ionic 5 - Migration and Free Starter where we explain how to take advantage of the new benefits from Ionic 5.

In this series of posts we are going to go deeper on the structure and core concepts of Ionic apps and explore more advanced topics, so if you are new to Ionic, I strongly recommend that you first read our Introduction to Ionic tutorial.

Although there were some important changes, the big picture hasn't changed much from Ionic 4 to Ionic 5, so you shouldn't be afraid to make the change.

Having said that, it's also worth mentioning that you may have to invest some time getting used to new development habits introduced in Ionic 4. For me these were a bit shocking at first, but now that I see the benefits of the new approach I'm starting to enjoy them (by the way, that was the main reason I started writing this post, I feel some of the new concepts introduced in ionic 4 - and still present in Ionic 5 - need to be explained so that developers don't freak out and get intimidated).

Without further ado, we are planning three posts for this series covering:

  1. Part 1 - Web Components? Shadow DOM? CSS Variables? Stencil? Understanding the new components architecture presented in Ionic 4
    • Covering everything from styling and customizing Ionic components, to building custom web components with Stencil and then use them in your Ionic projects (or any other framework, as web components are framework agnostic!).
  2. Part 2 - Ionic Navigation in depth
    • From Ionic 4 onwards, the routing is handled by the Angular router (if you are building an ionic angular app), this has many advantages like URL discoverability, route resolves, route guards and the ability to lazy load modules. However, there are also some user perceived performance gotchas that need to be tackled.
  3. Part 3 - The state of PWA in Ionic 4
    • PWA's are the big bet of Ionic. I agree on this and believe the near future will be all about progressive web apps as they present significant benefits to most app use cases. Ionic 4 is a great tool to build PWA's, but there are still some issues to be polished.

At IonicThemes we are big fans of Learning by example, that's why all our ionic tutorials include a complete and free ionic code example that you can reuse in your ionic projects. We strive to create the best content for Ionic Framework, both tutorials and templates to help the Ionic community succeed. We work towards empowering developers to achieve a free life through coding. That's what motivates us at IonicThemes everyday. Hope we can help you creating better apps with our detailed and professional templates, crafted with love and dedication.

A big step forward towards the future of the web: Web Components

Yes, I know, there's a lot of buzz around this lately. I believe Web Components represent a tipping point and as I mentioned before there may be some developers that experience the transition refusing and resisting instead of embracing the improvements.
I say this because I experienced it myself when I started playing around with the new Ionic 4 components.

The big thing about web components is encapsulation, this has a lot of benefits, but also enforce you to follow a more strict interface, leaving behind the anarchy and flexibility of Ionic 3 non web component elements.

Shadow DOM is for you to blame

You may be wondering what is Shadow DOM? Shadow DOM defines how to use encapsulated styles and markup within a web component. Before this approach, we relied on the global nature of HTML, CSS, and JS.

Non-encapsulation, disguised as flexibility, enabled us to ‘easily' style stuff by just specifying the correct css selectors. A major drawback from non-encapsulation is that CSS specificity becomes a huge issue (putting !important everywhere), style selectors grow out of control, and performance can suffer. The list goes on.

We need to learn how to play by the Ionic 4 new rules

Take this ionic 4 web component:

<ion-button class="host-element">
  <span class="host-child-element">span</span>

We can change and add new styles to both the host element and the child elements you explicitly define inside the host element. So far nothing different.

In this case, <ion-button> is the host element, and the <span> explicitly defined inside is the host child element. These elements are governed by the global nature of CSS and you can change or add new styles the way you are used to.

However, as the <ion-button> is a web component, that html we typed isn't exactly what gets injected into the DOM tree. This is what we see when inspecting the DOM:

<ion-button class="host-element">
	#shadow-root (open)
	<button type="button" class="button-native">
	  <span class="button-inner">
	    <slot name="icon-only"></slot>
	    <slot name="start"></slot>
	      -> span
	    <slot name="end"></slot>
	<span class="host-child-element">span</span>

Inside the shadow-root node we see a <style> element and a <button> element. These are the inner workings of the <ion-button> web component.

Every element inside the shadow-root is governed by the encapsulated scope within it. This means we can't add new styles to those elements and we can't modify styles directly on those elements. We will only be able to change properties (styles) that the web component defined through css 4 variables.

You can think of this as an API to interact with the inner structure of a web component. For example, if we look at the source code of the <ion-button> we get to see the different properties of the <button type="button" class="button-native"> inside the shadow-root that are defined with css 4 variables:

.button-native {

	height: var(--height);
	transition: var(--transition);
	border-width: var(--border-width);
	border-style: var(--border-style);
	border-color: var(--border-color);
	background: var(--background);
	box-shadow: var(--box-shadow);
	opacity: var(--opacity);


	line-height: 1;
	cursor: pointer;


Considering the group of elements inside the shadow-root, in our example <button type="button" class="button-native"> and it's childs (<span class="button-inner">, etc), all the properties of those elements defined using css 4 variables are part of that ‘public API' that defines the limited stuff we can change about the inners of that web component. If an element inside the shadow-root doesn't have some property defined with css 4 variables, then there's no way for us to modify that property.

In the example above height, border, background, opacity properties of the <button type="button" class="button-native"> are part of the ‘public API' and we can adjust its values, but line-height and cursor properties are not part, and we won't be able to adjust its values.

Go ahead and play around with this embedded snippet to see the explanation in action.

While I was writing this I realized it is very difficult to explain because there is so much technical terminology involved. I really hope the example I set up helped illustrating the situation, but if you don't find it clear enough please show me how I can improve the explanation in the comments section below.

Need help styling your Ionic mobile app? At IonicThemes we want to help you. That's why we teamed up with design experts to assure top quality in our Ionic Templates. Use our ready-made Ionic apps as a base and bump your Ionic css styling skills!

Gotcha: What's the difference between CSS 4 variables and Sass variables?

In the past versions of Ionic, the theming and styling of Ionic depended mostly on SASS, where we would define .scss files that would be compiled down into standard CSS at runtime. Ionic 4 will still be making use of SASS variables, but also CSS 4 variables.

One of the key differences between using something like SASS variables and CSS4 variables is that SASS is a preprocessor while CSS4 variables make use of standard CSS that is supported by browsers. Think of Sass like TypeScript, the TypeScript and Sass we write doesn't actually run in browsers; it gets compiled down to JavaScript and CSS respectively when you build your app.

The key benefit of using CSS4 variables is that they can be modified at runtime. Since CSS4 variables are supported natively by the browser, there is no compile step required to get the variables working. What this means, is that you could just open up the browser debugger, change some variables, and see the changes take effect in your application immediately (this is something you won't be able to do with Sass variables as Sass involves a compiler before generating the css code that the browser understands).

This also means that you can change from Javascript these CSS 4 variables dynamically in your Ionic framework app. This would make it quite easy to build functionality like a dark/light theme switcher, where a user could toggle a switch and instantly change the theme of the entire application.

Styling Ionic 4 components

If you experience frustration when styling Ionic 4 components, that's because you are not used to having rules on what you can and cannot style. Just try to change your mental map, follow the new rules, and it will be straightforward.

Ionic Platform specific styles

Adding platform specific styles to your app is another common use case that changed in Ionic 4. You may want platform-specific styles for the whole ionic app or just for certain ionic pages. Let's see how to achieve this.

The first scenario is easy; you just need to add those platform-specific styles inside the src/global.scss file and you are done.

html.md {
  color: red;

html.ios {
  color: blue;

On the other hand, if you want platform-specific styles just for certain pages, due to encapsulation, you can't target the html node from the page. Fortunately, Angular has a way to apply styles based on some condition outside of a component's view. To achieve this, you need to use the :host-context() pseudo-class selector on your page stylesheet, for example in app/home/home.page.scss file:

@import "~@ionic/angular/dist/themes/ionic.theme.default.md";
@import "~@ionic/angular/dist/themes/ionic.theme.default.ios";

:host-context(.ios) {
  ion-grid {
    height: calc(100vh - #{$toolbar-ios-height});

:host-context(.md) {
  ion-grid {
    height: calc(100vh - #{$toolbar-md-height});

In this example I go a bit further and also import @ionic/angular theme variables to use them inside my platform specific-styles.

Customizing Ionic 4 components

Sometimes we need to change the layout or some UX behaviour that goes beyond simple CSS styling adjustments.

In my experience, I found two alternatives. Either I clone the original Ionic component and adjust the code to my needs or I wrap the original Ionic component with a custom element that enhances the ionic component the way I need.

The first alternative will give you more flexibility as you would be able to deeply modify the code and behaviour of the component. You may also find it not that trivial as the original Ionic component may rely and depend on other Ionic components, services and utils to work. Also, if you follow this approach, you may miss future updates/improvements that the Ionic team does to the component you cloned.

The second alternative is not that flexible but you won't get dirty changing Ionic Framework components that already work. This is the approach we are going to explore in this ionic 4 tutorial.

Adding new functionalities to existing Ionic components

I love the <ion-img> component, and especially the new IntersectionObserver feature the Ionic Team added to it, which enables loading the image only when it's in the viewport.

One thing I don't like is that before the image loads the user can experience a flick.

We found this easy to fix by using some css techniques to ensure the image will always respect some aspect ratio preventing the content that's below the image to jump while the image is loading.

This technique is something I have used a lot in all the templates we do at IonicThemes, so it's a great example to illustrate how to enhance an existing Ionic Component without modifying the inner web component.

What are we going build in this Ionic 4 example?

The idea is to build a simple wrapper component with two inputs. The src that we will pass it through directly to the <ion-img> component, and the ratio. We are going to use both ratio width and height in combination with some css to achieve the desired behaviour.

This is the proposed markup for the wrapper component:

<c-preload-image [ratio]=”{w: 2, h: 1}” [src]="'https://placeimg.com/200/100/any'">

We will also add a spinner to show the user while we load the image. This way we will improve the user experience of the <ion-image> component.

The wrapper component

The idea is to add functionality to an existing Ionic component (the <ion-img> component). For this purpose we are going to follow this css technique to ensure the image conserves a defined aspect ratio all the time to prevent content jumping around while images load.

We achieve that by calculating the aspect ratio height and set the padding-bottom value to that value:

import { Component, Input, ElementRef, Renderer2, ViewEncapsulation, ViewChild, OnChanges, PLATFORM_ID, Inject } from '@angular/core';

  selector: 'preload-image',
  templateUrl: './preload-image.component.html',
  styleUrls: [
  encapsulation: ViewEncapsulation.None
export class PreloadImageComponent implements OnChanges {
  _src = '';
  _ratio: { w: number, h: number };

    private _elementRef: ElementRef,
    private _renderer: Renderer2
  ) {}

  @Input() set src(val: string) {
    this._src = (val !== undefined && val !== null) ? val : '';

  @Input() set ratio(ratio: { w: number, h: number }) {
    this._ratio = ratio || {w: 1, h: 1};

  ngOnChanges() {
    const ratio_height = (this._ratio.h / this._ratio.w * 100) + '%';
    // Conserve aspect ratio (see: https://stackoverflow.com/a/10441480/1116959)
    this._renderer.setStyle(this._elementRef.nativeElement, 'padding', '0px 0px ' + ratio_height + ' 0px');

  _update() {

  _loaded(isLoaded: boolean) {
    if (isLoaded) {
      setTimeout(() => {
        this._renderer.addClass(this._elementRef.nativeElement, 'img-loaded');
      }, 500);
    } else {
      this._renderer.removeClass(this._elementRef.nativeElement, 'img-loaded');

Finally, we also need to get notified when the <ion-img> finish loading in order to hide the spinner. This is easy because the <ion-img> component exposes the ionImgDidLoad() method to handle that:

<ion-spinner class="spinner"></ion-spinner>
<ion-img [src]="_src" (ionImgDidLoad)="_loaded(true)"></ion-img>

Finally some css where we apply the css technique mentioned before and some styles to properly show and hide loading indicators while the image loads:

$white: #FFFFFF;
$grey: #9e9e9e;

$preload-image-bg: rgba(darken($white, 10), .25);

$spinner-size: 28px;
$spinner-color: $grey;

preload-image {
  display: block;
  background-color: $preload-image-bg;
  overflow: hidden;
  position: relative;
  width: 100%;

  .spinner {
    position: absolute;
    top: calc(50% - #{ ($spinner-size/2) });
    left: calc(50% - #{ ($spinner-size/2) });
    width: $spinner-size;
    height: $spinner-size;
    font-size: $spinner-size;
    line-height: $spinner-size;
    color: $spinner-color;

  ion-img {
    position: absolute;
    top: 0;
    left: 0;
    transition: visibility 0s linear, opacity .5s linear;
    opacity: 0;
    visibility: hidden;
    width: 100%;
    height: 100%;

  &.img-loaded {
    background-color: transparent;
    border: 0;

    ion-img {
      opacity: 1;
      visibility: visible;

    .spinner {
      display: none;
      visibility: hidden;

Let's see how our Ionic 4 example looks like

Awesome! We improved the user experience by preventing content jumping around the page and by showing a loading indicator while the image loads.

Getting started with Stencil

If you want to dive into even more advanced stuff, then follow me while we build a custom web component with Stencil.

Stencil is a web component compiler for building fast, reusable UI components. It was built and is maintained by the Ionic Framework Team. Compared to alternatives like Polymer, Stencil is not a framework nor a library; it's just a compiler that turns classes with decorators into standards-based Web Components. This means that you can generate a collection of stencil components and use them in Angular, React, Vue or Polymer without any problem.

While it's still on early stages, I really like the lightweight and straightforward approach of Stencil.

Creating a Web Component with Stencil

In this ionic 4 tutorial I want to explain how to build a simple web component using Stencil to show you how easy is to build, distribute, and integrate stencil web components into a front-end framework.

I really like the idea and possibilities of multi-colored SVG icons, and Stencil is the perfect tool to create one. So, let's go ahead with this stencil web component example so you can get the chance to see more examples about web components, shadow DOM, and CSS 4 Variables to reinforce what we have learned so far.

Icon images vs Font icons vs SVG icons

Long gone are the times of using images and CSS sprites to make icons for the web. With the popularity of web fonts, the icon fonts have turned into the main solution for displaying icons in web projects.

Because fonts are vectors is that you don't have to worry about resolution. They derived the same benefit from the same CSS properties as text. As a result, you have full control over color, size and styles. You can add effects, transforms, and decorations such as rotations, shadows or underlines.

You should also know that Icon fonts aren't perfect, that's why a growing number of people prefer using inline SVG images.

Yet, there's one thing that remains absolutely impossible with icon fonts: multicolor support. Only SVG can do this.

I think this is very important because icons and illustrations in general are super powerful micro-interactions, and if we can enhance them, then we will see gains in our user experience.

The problem

We need multi-color support for our icons to increase expressiveness and improve our user experience. A great example of multi-colored icons (and inspiration for this example) is the Iconic icon set.

Another great project that inspired me is Ionicons - created by the Ionic Framework team - as they introduced the ‘icons as web components' concept.

An advantage to the font icon file is that all of the icons are in one file. A disadvantage to the font icon file is that all of the icons are in one file. Moreover, large font files negatively affect a webpage's time to first paint.

Most of the time, it might be smarter to ask for few svgs utilizing the web component method, although if a website page needs to demonstrate numerous icons at once, the font icon might be a better choice.

The solution

Let's combine the benefits of both projects and create a multi-color svg icon web component using Stencil.

Stencil has a barebones starter project that will help you getting started building web components in a breeze. This stencil starter includes all the boilerplate, project structure and major configs you will need.

This is the multi-color svg icon web component we are going to build using Stencil:

Building a multi-color SVG icon web component

I started by cloning the Stencil component starter repo:

git clone https://github.com/ionic-team/stencil-component-starter.git c-coffee-icon
cd c-coffee-icon
git remote rm origin

I also added @stencil/sass as I wanted to use Sass in this example.

npm install @stencil/sass --save-dev

After installing the dependency you need to add the sass plugin in the stencil.config.js file.

import { Config } from '@stencil/core';
import { sass } from '@stencil/sass';

export const config: Config = {
  namespace: 'c-coffee-icon',
      type: 'dist'
      type: 'www',
      serviceWorker: null
  plugins: [

Building the Stencil web component

In order to achieve multi-color SVG's, we need to rely on composing our SVG with multiple paths in order to color each part of the SVG independently. This awesome post helped me grasping the technique above.

After that, coding the component was straightforward:

import { Component } from '@stencil/core';

  tag: 'c-coffee-icon',
  styleUrl: 'c-coffee-icon.scss',
  shadow: true
export class CustomCoffeeIconComponent {
  render() {
    return ([
      <svg xmlns="http://www.w3.org/2000/svg" class="hidden">
        <symbol id="icon-coffee" viewBox="0 0 20 20">
          <path fill="var(--handle-color)" d="..."/>
          <rect fill="var(--cup-color)" x="1" y="7" width="15" height="12" rx="3" ry="3"/>
          <path fill="var(--smoke-color)" d="..."/>
          <path fill="var(--smoke-color)" d="..."/>
          <path fill="var(--smoke-color)" d="..."/>
      <svg class="coffee-icon" aria-hidden="true">
        <use href="#icon-coffee" />

As I mentioned before I also used css 4 variables in order to enable adjusting the component from outside the shadow root content (explanation of these concepts were mentioned earlier in this post).

:host {
  --fallback-color: #000000;
  --handle-color: #c13127;
  --cup-color: #ef5b49;
  --smoke-color: #cacaea;

  display: inline-block;

.hidden {
  display: none;
  visibility: hidden;

.coffee-icon {
  width: 100px;
  height: 100px;
  fill: var(--fallback-color);

Notice that we need to define the css 4 variables under the :host selector to enable adjusting the properties from outside the shadow DOM. If we define the css4 variables under the .coffee-icon selector, then the variables will work but won't be possible to adjust them from outside as they will be defined inside the encapsulated scope of the shadow DOM.

Let's reinforce this: every property you want to allow to be modified must be defined under the :host selector.

Testing the Stencil Web Component

Add a simple index.html file inside the src folder to test the component:

<!DOCTYPE html>
    <title>My App</title>
    <script src="build/c-coffee-icon.js"></script>

And then run npm start to start the dev server on http://localhost:3333.

Distributing your Stencil Web Component

Luckily, Stencil has documentation about distributing the component. However, most of the job required for distribution is already done in the stencil component starter we are using.

Besides that basic configuration to bundle and prepare our web component to be used as a javascript module, we need to publish it to a software registry. Among the most used ones it's NPM.

It's very easy to publish open source software to NPM; I just followed some easy steps detailed in this guide.

You can find the published stencil web component we just built here: https://www.npmjs.com/package/c-coffee-icon

How to use our new Stencil Web Component in our Ionic app?

Stencil has good documentation on how to use our standard web components with major front-end frameworks. As we are using @ionic/angular for this Ionic with Stencil example, let's see what we need to do in order to use our web component inside an Angular project.

First, we need to install the c-coffee-icon web component module we published to npm in the previous step. So we will go back to our Ionic app and run npm install c-coffee-icon --save

Then, we need to include the CUSTOM_ELEMENTS_SCHEMA in the modules that use our custom web component. We also need to define our custom element by calling the defineCustomElements() function exposed by the module generated by Stencil (the one we just installed using npm).

I went a step further and created a dedicated module to include all custom web components that we may need in our Ionic 4 app:

import { APP_INITIALIZER, ModuleWithProviders, CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { appInitialize } from './app-initialize';
import { CoffeeIconComponent } from './coffee-icon/coffee-icon.component';

  imports: [
  declarations: [
  exports: [
  entryComponents: [],
export class WebComponentsModule {
  static forRoot(): ModuleWithProviders {
    return {
      ngModule: WebComponentsModule,
      providers: [
          provide: APP_INITIALIZER,
          useFactory: appInitialize,
          multi: true

As I mentioned before, a component collection built with Stencil includes a main function that is used to load all the components in the collection. That function is called defineCustomElements() and it needs to be called once during the bootstrapping of your ionic angular application. One convenient place to do this is in a custom APP_INITIALIZER function that is guaranteed to be executed when the application is initialized:

import { defineCustomElements } from 'c-coffee-icon';

export function appInitialize() {
  return () => {
    const win = window as any;
    if (typeof win !== 'undefined') {
      // Define all of our custom elements

Just remember to include this module in your main app.module.ts and call the forRoot() function to ensure the APP_INITIALIZER function gets invoked:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule, RouteReuseStrategy, Routes } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { WebComponentsModule } from './web-components/web-components.module';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

  declarations: [AppComponent],
  entryComponents: [],
  imports: [
  providers: [
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  bootstrap: [AppComponent]
export class AppModule {}

Finally, I looked at the Ionic source code and noticed they created Angular Component proxies for each ionic web component inside their ionic/core module. These are very simple Angular Components with almost no logic at all:

import 'c-coffee-icon';
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';

  selector: 'c-coffee-icon',
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  template: '<ng-content></ng-content>'
export class CoffeeIconComponent {

The advantage of adding Angular proxy components is that by doing so we allow developers to get references to components in an idiomatic way:

@ViewChild(CoffeeIconComponent) coffeeIcon: CoffeeIconComponent;

Instead of:

@ViewChild('id') coffeeIcon: ElementRef;

Proxies also help to provide TS typings and AOT checks at compiler time.

By using proxies you also avoid including the CUSTOM_ELEMENTS_SCHEMA in every module that uses our custom web component (<c-coffee-icon>). By defining a proxy Angular Component wrapper we just need to include the CUSTOM_ELEMENTS_SCHEMA in the web-components.module.ts. This way, whenever we use the <c-coffee-icon> in our Ionic/Angular app, we will be using the Angular component wrapper instead thus, no need to include the CUSTOM_ELEMENTS_SCHEMA multiple times.

Testing the custom Web Component inside our Ionic app

Just serve the Ionic 4 app as you would normally do by running ionic serve

Yeeey, we have our custom web component built with Stencil working like a charm inside our Ionic Framework app!

Let's ensure our custom web components shadow DOM encapsulation works correctly. Go ahead and try to re-define the css 4 variables we set for the component in the home.page.scss file and let's see what happens:

c-coffee-icon {
  --handle-color: #1f2bac;
  --cup-color: #2f3fff;
  --smoke-color: #a5acbd;

Ionic 4 tutorial conclusions

If you followed me up until the end of this complete ionic 4 tutorial, then you are a step closer to master Ionic 4 web and mobile app development. Just start exploring and trying stuff using the tools and techniques we just learnt.

Go ahead and download the ionic 4 example app that we built in this tutorial and see the code we used to style and customize an Ionic 4 component. Go a step forward and build your own web component using Stencil and use it inside your Ionic app.

Did you know that we recently released Ionic 5 Full Starter App? It's an ionic 5 template that you can use to jump start your Ionic app development and save yourself hundreds of hours of design and development.

It is also a PWA and has a score of 100 in lighthouse. Try it on your phone as a PWA to see the magic!

As I mentioned at the beginning of this ionic 4 tutorial, this post is part of a series of three posts. The upcoming parts will be about:

  • Part 2 - Ionic Routing and Navigation in depth
    As you have probably heard, from Ionic 4 onwards the routing is handled by the Angular router. This has many advantages like URL discoverability, route resolves, route guards and the ability to lazy load modules. However, there are also some user perceived performance gotchas that need to be tackled.
  • Part 3 - Building a PWA with Ionic Framework
    PWA's are the big bet of Ionic Framework. I agree on this and believe the near future will be all about progressive web apps as they present significant benefits to most app use cases. Ionic is a great tool to build PWA's, but there are still some issues to be polished. Let's build together a PWA with Ionic!

It was really a pleasure to share my knowledge with you, I hope you feel the same and please let me know your feedback in the comments below.

Hope you now feel confident to start building your own custom web components with Stencil. Soon we will be releasing more Ionic tutorials so subscribe to our newsletter to keep learning Ionic Framework! See you in the next ionic tutorial.