Designing for Mobile, Tablet, and PWA in Ionic 3 – What I learned.

In Ionic 3 by semeLeave a Comment

Ionic 3 has been around for a while, and a lot has been done in making Ionic work not just in a mobile-centric manner but for large display devices from tablets to laptops, many thanks to features like split-pane and ion-grid. And while they do lot in speeding up your work, there’s a lot I learned in know where/when best to use them, and when/where best to add your own approach.

TL:DR Summary:
On a high level, use ion-grid for layout. On a granular level, fine tune on a most-use-case basis using breakpoint-sass

I’ll be using ionic code samples that you can paste in any default ionic page so it’ll be easy to understand the approach.

The Why.

I created Foodica, an Ionic 1 (now updated to 3.x) template that dedicated tablet/mobile views for every page, switchable via a config setting. This was before we had split-panes or similar, and having done it a long time ago an upgrade to Ionic 3 was in order. The goal I set for myself was to have a single codebase that could work for mobile, tablets, and Laptop displays (PWA, anyone?)

The Blueprint

To best illustrate this, we’re going to achieve the following layout for a product detail with one codebase across various device sizes:

We have a sidebar, a slider, product description, and related items.

Setting up

Create a new Ionic app using the command in your terminal. We’ll be using the sidemenu template

ionic start io-grid sidemenu --type=ionic-angular

Next, head to io-grid/src/app/app.html. We should have something like this:

<ion-menu [content]="content">
  <ion-header>
    <ion-toolbar>
      <ion-title>Menu</ion-title>
    </ion-toolbar>
  </ion-header>

  <ion-content>
    <ion-list>
      <button menuClose ion-item *ngFor="let p of pages" (click)="openPage(p)">
        {{p.title}}
      </button>
    </ion-list>
  </ion-content>

</ion-menu>

<!-- Disable swipe-to-go-back because it's poor UX to combine STGB with side menus -->
<ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>

To change this to a side pane, we’re going to wrap this entire markup with ion-split-pane while adding the main attribute to our ion-nav.

Copy/Paste the following into your app.html:

<ion-split-pane>
    <ion-menu [content]="content">
      <ion-header>
        <ion-toolbar>
          <ion-title>Menu</ion-title>
        </ion-toolbar>
      </ion-header>
    
      <ion-content>
        <ion-list>
          <button menuClose ion-item *ngFor="let p of pages" (click)="openPage(p)">
            {{p.title}}
          </button>
        </ion-list>
      </ion-content>
    
    </ion-menu>
    
    <!-- Disable swipe-to-go-back because it's poor UX to combine STGB with side menus -->
    <ion-nav [root]="rootPage" main #content swipeBackEnabled="false"></ion-nav>
</ion-split-pane>

And like that, after running ionic serve, we have a sidebar to acts according to how big the viewport is:

Now, to the grid

Open your io-grid/src/pages/home.html . We’re going to be adding snippets piece by piece, starting with our slider

<ion-header>
  <ion-navbar>
    <button ion-button menuToggle>
      <ion-icon name="menu"></ion-icon>
    </button>
    <ion-title>Home</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-grid>
      <ion-row>
        <ion-col col-xl-8>
          <ion-slides [pager] ="false" [slidesPerView]="1">
            <ion-slide>
                <img src="http://placehold.it/700x400/333333/ffffff" alt="">
            </ion-slide>
            <ion-slide>
                <img src="http://placehold.it/700x400/333333/ffffff" alt="">
            </ion-slide>
            <ion-slide>
                <img src="http://placehold.it/700x400/333333/ffffff" alt="">
            </ion-slide>
          </ion-slides>
        </ion-col>
     </ion-row>
 </ion-grid>
</ion-content>

notice we’re using col-xl-8, which means that column would take up 8 columns in the grid when at the xl size (when min-width is 1200px, for a full breakdown view the full gridsize). The col-sm makes sure all columns stack vertically:

If we add another column of col-xl-4 containing our product details in the same ion-row, we’ll be able to effectively manage the layout when the display size hits the xl.

<ion-header>
  <ion-navbar>
    <button ion-button menuToggle>
      <ion-icon name="menu"></ion-icon>
    </button>
    <ion-title>Home</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-grid>
      <ion-row>
        <!-- First column - holds our slider -->
        <ion-col col-sm col-xl-8>
          <ion-slides [pager] ="false" [slidesPerView]="1">
            <ion-slide>
                <img src="http://placehold.it/700x400/333333/ffffff" alt="">
            </ion-slide>
            <ion-slide>
                <img src="http://placehold.it/700x400/333333/ffffff" alt="">
            </ion-slide>
            <ion-slide>
                <img src="http://placehold.it/700x400/333333/ffffff" alt="">
            </ion-slide>
          </ion-slides>
        </ion-col>

        <!-- Second column - holds our product Details -->
        <ion-col col-sm col-xl-4>
          <div>
            <h1>
              <span class="secondary-font">$25</span>
              | Samsbury Recipie
            </h1>
            <button ion-button round>Add to Cart</button>
          </div>
        </ion-col>
     </ion-row>
 </ion-grid>
</ion-content>

Now this is simple for a two column layout, but we also want the product description for stretch the full width in the same ion-row that holds the slider and the product details, while stacking vertically for smaller devices. That’s what makes ion-grid so flexible.

Observe the code below, then copy into your home.html

<ion-header>
  <ion-navbar>
    <button ion-button menuToggle>
      <ion-icon name="menu"></ion-icon>
    </button>
    <ion-title>Home</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-grid>
      <ion-row>
        <!-- First column - holds our slider -->
        <ion-col col-sm col-xl-8>
          <ion-slides [pager] ="false" [slidesPerView]="1">
            <ion-slide>
                <img src="http://placehold.it/700x400/333333/ffffff" alt="">
            </ion-slide>
            <ion-slide>
                <img src="http://placehold.it/700x400/333333/ffffff" alt="">
            </ion-slide>
            <ion-slide>
                <img src="http://placehold.it/700x400/333333/ffffff" alt="">
            </ion-slide>
          </ion-slides>
        </ion-col>

        <!-- Second column - holds our product Details -->
        <ion-col col-sm col-xl-4>
          <div>
            <h1>
              <span class="secondary-font">$25</span>
              | Samsbury Recipie
            </h1>
            <button ion-button round>Add to Cart</button>
          </div>
        </ion-col>

        <!-- Third column. Won't take up a third column, but will stack below the slider and product details -->
        <ion-col col-12 col-md-8 col-lg-8 col-xl-12>
        <div class="product-content primary-font">
          <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
            incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
            exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
            irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
            pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
            deserunt mollit anim id est laborum.</p>
          </div>
      </ion-col>
     </ion-row>
 </ion-grid>
</ion-content>

Results so far:

The last thing we want to add are the related items list. simple pictures that will have many on large screens but stack vertically/horizontally even on the small phone size. We’ll place these in a separate ion-row

<ion-header>
  <ion-navbar>
    <button ion-button menuToggle>
      <ion-icon name="menu"></ion-icon>
    </button>
    <ion-title>Home</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-grid>
      <ion-row>
        <!-- First column - holds our slider -->
        <ion-col col-12 col-sm col-xl-8>
          <ion-slides [pager] ="false" [slidesPerView]="1">
            <ion-slide>
                <img src="http://placehold.it/700x400/333333/ffffff" alt="">
            </ion-slide>
            <ion-slide>
                <img src="http://placehold.it/700x400/333333/ffffff" alt="">
            </ion-slide>
            <ion-slide>
                <img src="http://placehold.it/700x400/333333/ffffff" alt="">
            </ion-slide>
          </ion-slides>
        </ion-col>

        <!-- Second column - holds our product Details -->
        <ion-col col-sm col-xl-4>
          <div>
            <h1>
              <span class="secondary-font">$25</span>
              | Samsbury Recipie
            </h1>
            <button ion-button round>Add to Cart</button>
          </div>
        </ion-col>

        <!-- Third column. Won't take up a third column, but will stack below the slider and product details -->
        <ion-col col-12 col-md-8 col-lg-8 col-xl-12>
        <div class="product-content primary-font">
          <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
            incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
            exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
            irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
            pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
            deserunt mollit anim id est laborum.</p>
          </div>
      </ion-col>

      <ion-row>
        <ion-col col-12>
          <h1>Related Items</h1>
        </ion-col>
        <ion-col col-md-12>
          <ion-row class="">
            <ion-col col-6 col-sm-4 col-md-4 col-lg-2 col-xl-2>
              <img src="http://placehold.it/300x300/444444/fffffff" alt="">
            </ion-col>
            <ion-col col-6 col-sm-4 col-md-4 col-lg-2 col-xl-2>
              <img src="http://placehold.it/300x300/444444/fffffff" alt="">
            </ion-col>
            <ion-col col-6 col-sm-4 col-md-4 col-lg-2 col-xl-2>
              <img src="http://placehold.it/300x300/444444/fffffff" alt="">
            </ion-col>
            <ion-col col-6 col-sm-4 col-md-4 col-lg-2 col-xl-2>
              <img src="http://placehold.it/300x300/444444/fffffff" alt="">
            </ion-col>
            <ion-col col-6 col-sm-4 col-md-4 col-lg-2 col-xl-2>
              <img src="http://placehold.it/300x300/444444/fffffff" alt="">
            </ion-col>
            <ion-col col-6 col-sm-4 col-md-4 col-lg-2 col-xl-2>
              <img src="http://placehold.it/300x300/444444/fffffff" alt="">
            </ion-col>
          </ion-row>
        </ion-col>
      </ion-row>

     </ion-row>
 </ion-grid>
</ion-content>

End Result

Adding finer touches.

We’ve done quite a bit with the ion-grid, but when you need to fine tune based some larger or smaller sizes, you need really simple, organized media queries, that’s where breakpoint comes in.

Break-what?

Breakpoint makes writing media queries in sass super simple, which is very handy for examples:

  • Maintaining a decent typography size across multiple screen sizes/type
  • Make mobile/tablet-specific styles.

To illustrate this, we’re going to setup breakpoint-sass and use it in our build.

First, run npm install breakpoint-sass

npm install breakpoint-sass --save

Edit your io-grid/src/app/app.scss to the following code:

// http://ionicframework.com/docs/theming/


// App Global Sass
// --------------------------------------------------
// Put style rules here that you want to apply globally. These
// styles are for the entire app and not just one component.
// Additionally, this file can be also used as an entry point
// to import other Sass files to be included in the output CSS.
//
// Shared Sass variables, which can be used to adjust Ionic's
// default Sass variables, belong in "theme/variables.scss".
//
// To declare rules for a specific mode, create a child rule
// for the .md, .ios, or .wp mode classes. The mode class is
// automatically applied to the <body> element in the app.


@import '../node_modules/breakpoint-sass/stylesheets/breakpoint';

// Device Sizes
$mobile : max-width 414px;
$tablet : min-width 1010px;

@import "../pages/home/home";

Let’s stop and explain what’s going on:

  • The first line (@import….) is importing the sass file with all of breakpoint’s features
  • Breakpoint takes single values (eg: 414px, 768px, min-width is assumed). Breakpoint also takes pair arguments as well (max-width 414px). Here, the last two lines are sass variables that define the size dimensions we want to target. we will be using these variables when calling breakpoint in our styles

Using Breakpoint

Open our io-grid/src/pages/home/home.scss and add the following:

page-home {
  .content-padding {
    @include breakpoint($mobile){
      padding: 0;
    }
    @include breakpoint($tablet){
      padding: 16px;
    }
  }
}

Here, we’re stating that the class .content-padding should have no padding on mobile, but 16px padding on tablet sizes, based on the media width variables we defined in app.scss

in your io-grid/src/pages/home.html<code>, remove (if any) the <code>padding directive in your ion-content and add the .content-padding class to the topmost ion-grid element:

<ion-header>
  <ion-navbar>
    <button ion-button menuToggle>
      <ion-icon name="menu"></ion-icon>
    </button>
    <ion-title>Home</ion-title>
  </ion-navbar>
</ion-header>

<ion-content>
  <ion-grid class="content-padding">
      <ion-row>
        <!-- First column - holds our slider -->
        <ion-col col-12 col-sm col-xl-8>
          <ion-slides [pager] ="false" [slidesPerView]="1">
            <ion-slide>
                <img src="http://placehold.it/700x400/333333/ffffff" alt="">
            </ion-slide>
            <ion-slide>
                <img src="http://placehold.it/700x400/333333/ffffff" alt="">
            </ion-slide>
            <ion-slide>
                <img src="http://placehold.it/700x400/333333/ffffff" alt="">
            </ion-slide>
          </ion-slides>
        </ion-col>

        <!-- Second column - holds our product Details -->
        <ion-col col-sm col-xl-4>
          <div>
            <h1>
              <span class="secondary-font">$25</span>
              | Samsbury Recipie
            </h1>
            <button ion-button round>Add to Cart</button>
          </div>
        </ion-col>

        <!-- Third column. Won't take up a third column, but will stack below the slider and product details -->
        <ion-col col-12 col-md-8 col-lg-8 col-xl-12>
        <div class="product-content primary-font">
          <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
            incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
            exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
            irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
            pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
            deserunt mollit anim id est laborum.</p>
          </div>
      </ion-col>

      <ion-row>
        <ion-col col-12>
          <h1>Related Items</h1>
        </ion-col>
        <ion-col col-md-12>
          <ion-row class="">
            <ion-col col-6 col-sm-4 col-md-4 col-lg-2 col-xl-2>
              <img src="http://placehold.it/300x300/444444/fffffff" alt="">
            </ion-col>
            <ion-col col-6 col-sm-4 col-md-4 col-lg-2 col-xl-2>
              <img src="http://placehold.it/300x300/444444/fffffff" alt="">
            </ion-col>
            <ion-col col-6 col-sm-4 col-md-4 col-lg-2 col-xl-2>
              <img src="http://placehold.it/300x300/444444/fffffff" alt="">
            </ion-col>
            <ion-col col-6 col-sm-4 col-md-4 col-lg-2 col-xl-2>
              <img src="http://placehold.it/300x300/444444/fffffff" alt="">
            </ion-col>
            <ion-col col-6 col-sm-4 col-md-4 col-lg-2 col-xl-2>
              <img src="http://placehold.it/300x300/444444/fffffff" alt="">
            </ion-col>
            <ion-col col-6 col-sm-4 col-md-4 col-lg-2 col-xl-2>
              <img src="http://placehold.it/300x300/444444/fffffff" alt="">
            </ion-col>
          </ion-row>
        </ion-col>
      </ion-row>

     </ion-row>
 </ion-grid>
</ion-content>

Now refresh in the browser to see the change. Notice the padding on tablet sizes disappear in mobile view.

So, with a decent amount of pre-planning, we can fine tune on a granular level how we want to style our app on mobile/tablet, while leaving the layout structure to ion-grid. To give you a glimpse of the opportunities, have a look our latest template Foodica (Currently in progress)  , (now available on Ionic Market) in action below:

 

I’ll be checking the comments regularly, so questions are welcome.