Building a dashboard UI using grid and flex-box
Tags: css, dashboard, ui
Published: 2020-03-07 01:37:16

### What are we building
A mobile first dashboard layout.

### Tech stack used
HTML5, CSS3, Javascript (no frameworks for now).

### Theory behind
- For a long time, layouts have been created using hack-y solutions like `display: table`, `position: relative` and `floats`. CSS being a presentation layers needed a proper way for layouts, enter — Grid and Flex-box.
- CSS3 grid is a 2 dimensional grid based layout system, meaning it can control both columns and rows at the same time, while flex-box is a 1 dimensional layout solution, which focus only on one main axis.

Please refer the links below for a complete overview of features/properties of both grid and flex box:
1. [A Complete Guide to Flex-box](https://css-tricks.com/snippets/css/a-guide-to-flexbox/)
2. [A Complete Guide to Grid](https://css-tricks.com/snippets/css/complete-guide-grid/)

> Flex-box is used for layout of components, while grid is preferred for overall/larger scale layouts. As an example the overall container of the dashboard we are going to make, will be created by grid and the individual components like the menu items in aside component will be lay-ed out using flex-box

### Browser support
- [https://caniuse.com/#feat=css-grid](https://caniuse.com/#feat=css-grid)
- [https://caniuse.com/#feat=flexbox](https://caniuse.com/#feat=flexbox)

Before we begin, the final dashboard will look as below:
{% codepen https://codepen.io/kevjose/pen/YzXrobv %}

Interested enough?

### Let’s begin ..

#### Creating a blue-print with grid

- We will define the blue-print of our dashboard via some HTML5 semantic elements

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>A Dashboard using grid and flex-box</title>
  </head>
  <body>
    <header></header>
    <aside></aside>
    <main></main>
    <footer></footer>
  </body>
</html>
```
- With our basic blue-print ready, let’s add a layout to it using grid. The grid property is always assigned to the parent such that it affects it direct children, the same holds true with flex-box as well
- We will wrap the blueprint in a container so that we do not have to apply the grid property to the body tag

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Building dashboard with gird and flex</title>
    <style>
      body {
        margin: 0;
        padding: 0;
        color: white;
        box-sizing: border-box;
        font-family: monospace;
        font-size: 15px;
      }
      .grid-container {
        display: grid; /* applies grid layout affecting it's children */
        grid-template-columns: 240px 1fr; /* define the number and sizes of columns */
        grid-template-rows: 50px 1fr 50px; /* defines the number ans sizes of rows*/
        grid-template-areas:
          'aside header'
          'aside main'
          'aside footer'; /* assigns the labeled grid-area to the grid layout, note there has to be two columns and three rows as per the grid template rows/ columns defined */
        height: 100vh;
      }

      .header {
        grid-area: header; /* assigns the grid-area label*/
        background-color: whitesmoke;
      }

      .aside {
        grid-area: aside;
        background-color: darkblue;
      }

      .main {
        grid-area: main;
        background-color: white;
      }
      .footer {
        grid-area: footer;
        background-color: whitesmoke;
      }
    </style>
  </head>
  <body>
    <div class="grid-container">
      <header class="header"></header>
      <aside class="aside"></aside>
      <main class="main"></main>
      <footer class="footer"></footer>
    </div>
  </body>
</html>
```

- `display: grid;`, applies grid layout affecting it’s children
- `grid-template-columns`, defines the number and sizes of columns the grid has to be divide into
- `grid-template-rows`, defines the number and sizes of rows the grid has to be divided into
- `grid-template-areas`, assigns the labeled grid-area to the grid layout

> There has to be two columns and three rows as per the grid template rows/ columns defined.

#### Layout individual components of the grid using flex-box

Layout for header and footer

```html
<style>
  /* flexing header and footer*/
  .header,
  .footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    color: darkblue;
    padding: 0 15px;
  }
</style>

<div class="grid-container">
  <header class="header">
    <div class="header_search">Search...</div>
    <div class="header_avatar">Logout</div>
  </header>
  <aside class="aside"></aside>
  <main class="main"></main>
  <footer class="footer">
    <div class="footer_copyright">&copy;2020</div>
    <div class="footer_byline">Made with &hearts;</div>
  </footer>
</div>
```

- `flex` is assigned to the parent element and applied on it’s direct siblings
- `display: flex;`, flex-es the content within. This layout is one dimensional and by default the main-axis is row hence the content inside will be stacked row-wise. We can change this behaviour using the `flex-direction: column` property
- `align-items`, defines how items are placed across the cross axis (perpendicular to the main- axis), vertically in the present case
- `justify-content`, define the alignment across the main axis, horizontal in this case

Layout for aside navigation

```html
<style>
  /* flexing aside */
  .aside {
    display: flex;
    flex-direction: column;
  }

  .aside_list {
    padding: 0;
    margin-top: 85px;
    list-style-type: none;
  }

  .aside_list-item {
    padding: 20px 20px 20px 40px;
    color: #ddd;
  }

  .aside_list-item:hover {
    background-color: royalblue;
    cursor: pointer;
  }
</style>

<div class="grid-container">
  <header class="header">
    <div class="header_search">Search...</div>
    <div class="header_avatar">Logout</div>
  </header>
  <aside class="aside">
    <ul class="aside_list">
      <li class="aside_list-item">Menu item1</li>
      <li class="aside_list-item">Menu item2</li>
      <li class="aside_list-item">Menu item3</li>
      <li class="aside_list-item">Menu item4</li>
      <li class="aside_list-item">Menu item5</li>
    </ul>
  </aside>
  <main class="main"></main>
  <footer class="footer">
    <div class="footer_copyright">&copy;2020</div>
    <div class="footer_byline">Made with &hearts;</div>
  </footer>
</div>
```

- `flex-direction: column;`, has been used to stack items one below the other

Layout for main content area

```html
<style>
  /* Layout for main content overview  and its cards*/
  .main_overview {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    border-bottom: 1px solid lightgreen;
  }
  .overview_card {
    flex-basis: 250px;
    flex-grow: 1;
    margin: 10px 10px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 20px;
    /* background-color: seagreen; */
    height: 100px;
    border: 1px solid darkblue;
    border-radius: 4px;
    color: darkblue;
  }
</style>

<main class="main">
  <div class="main_overview">
    <div class="overview_card">
      <div class="overview_card-info">Overview</div>
      <div class="overview_card-icon">Card</div>
    </div>
    <div class="overview_card">
      <div class="overview_card-info">Overview</div>
      <div class="overview_card-icon">Card</div>
    </div>
    <div class="overview_card">
      <div class="overview_card-info">Overview</div>
      <div class="overview_card-icon">Card</div>
    </div>
    <div class="overview_card">
      <div class="overview_card-info">Overview</div>
      <div class="overview_card-icon">Card</div>
    </div>
  </div>
</main>
```

- `flex-basis`, defines the default size along main axis before the remaining space is distributed
- `flex-grow`, defines the proportion by which an item can grow with respect to other items. It accepts a unit-less value
- `flex-wrap: wrap|wrap-reverse`, by default flex tries to squish all items in one line, this property helps to bring items to next line

Layout for main cards section inside main (below overview)

```html
<style>
  /* Layout for main-cards section // below main_overview */
  .main_cards {
    margin: 10px;
    display: grid;
    grid-template-columns: 2fr 1fr;
    grid-template-rows: 200px 300px;
    grid-template-areas:
      'card1 card2'
      'card1 card3';
    grid-gap: 10px;
  }
  .card {
    padding: 20px;
    border: 1px solid tomato;
    border-radius: 4px;
    color: tomato;
  }

  .card:first-child {
    grid-area: card1;
  }
  .card:nth-child(2) {
    grid-area: card2;
  }
  .card:nth-child(3) {
    grid-area: card3;
  }
</style>

<main>
  <!-- below the main_overview section -->
  <div class="main_cards">
    <div class="card">Card</div>
    <div class="card">Card</div>
    <div class="card">Card</div>
  </div>
</main>
```

- We have made a two column, three div layout using the grid layout here.
- `grid-gap: 10px`, will provide 10px space between the rows and columns generated.

#### Making the layout responsive

- For simplicity, we will have only one breakpoint at 750px.
- We will follow a mobile first approach and write `media-queries` for style above 750px break-point

```html
<style>
  /* for mobile*/
  .grid-container {
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: 50px 1fr 50px;

    grid-template-areas:
      'header'
      'main'
      'footer';
    height: 100vh;
  }
  /* hide aside by default in mobile */
  .aside {
    display: flex;
    flex-direction: column;
    height: 100%;
    width: 240px;
    position: fixed;
    overflow-y: auto;
    z-index: 2;
    transform: translateX(
      -245px
    ); /* pushes the aide bar in the negative x axis ie off scren */
  }

  .aside.active {
    transform: translateX(0); /* brings the aside to view port from left */
  }

  .main_cards {
    margin: 10px;
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: 500px 200px 300px;
    grid-template-areas:
      'card1'
      'card2'
      'card3'; /* change the grid template to stack the main_cards one below the other */
    grid-gap: 10px;
  }

  /* style for above 750px */
  @media only screen and (min-width: 750px) {
    .grid-container {
      display: grid;
      grid-template-columns: 240px 1fr;
      grid-template-rows: 50px 1fr 50px;
      grid-template-areas:
        'aside header'
        'aside main'
        'aside footer';
      height: 100vh;
    }

    .aside {
      display: flex;
      flex-direction: column;
      position: relative;
      transform: translateX(0);
    }

    .main_cards {
      margin: 10px;
      display: grid;
      grid-template-columns: 2fr 1fr;
      grid-template-rows: 200px 300px;
      grid-template-areas:
        'card1 card2'
        'card1 card3';
      grid-gap: 10px;
    }
  }
</style>
<!-- no change in HTML markup for responsiveness -->
```

- aside (side bar) will be not visible in the mobile view, we can hide this using the `transform: translateX( -245px );`, this pushes the aide bar in the negative x axis ie off screen
- `transform: translateX(0);`, brings the aside to view port from left.
- aside will have a fixed position in the mobile view, such that the main content area can be scrolled while the aside remains fixed, it will be relative on above 750px breakpoint

To make the responsive layout user friendly, we need to add a toggle button for the aside (side navigation). We will be using some native javascript for toggling the class. Also we will add some markup for showing indicators for the toggle.

```html
<style>
  .menu-icon {
    position: fixed;
    display: flex;
    top: 2px;
    left: 8px;
    align-items: center;
    justify-content: center;
    z-index: 1;
    cursor: pointer;
    padding: 12px;
    color: black;
  }

  .header_search {
    margin-left: 24px;
  }

  .aside_close-icon {
    position: absolute;
    visibility: visible;
    top: 20px;
    right: 20px;
    cursor: pointer;
  }
  /* hide aside close button on screen above 750px */
  @media only screen and (min-width: 750px) {
    .aside_close-icon {
      display: none;
    }
  }
</style>

<div class="grid-container">
  <div class="menu-icon">
    <strong> &#9776;</strong>
  </div>
  <header class="header">
    <div class="header_search">Search...</div>
    <div class="header_avatar">Logout</div>
  </header>
  <aside class="aside">
    <div class="aside_close-icon">
      <strong>&times;</strong>
    </div>
    <ul class="aside_list">
      <li class="aside_list-item">Menu item1</li>
      <li class="aside_list-item">Menu item2</li>
      <li class="aside_list-item">Menu item3</li>
      <li class="aside_list-item">Menu item4</li>
      <li class="aside_list-item">Menu item5</li>
    </ul>
  </aside>
  <!-- main content and footer markup  -->
</div>

<script>
  // selecting elements invloved in toggling
  const menuIcon = document.querySelector('.menu-icon');
  const aside = document.querySelector('.aside');
  const asideClose = document.querySelector('.aside_close-icon');

  // toggle function
  function toggle(el, className) {
    if (el.classList.contains(className)) {
      el.classList.remove(className);
    } else {
      el.classList.add(className);
    }
  }

  // attach events to the elements to add/remove active class on click to execute the toggle function
  menuIcon.addEventListener('click', function() {
    toggle(aside, 'active');
  });

  asideClose.addEventListener('click', function() {
    toggle(aside, 'active');
  });
</script>
```

- We select the relevant elements used for toggling action via `document.querySelector` DOM api.
- The toggle function in the script tag add/remove the .active class, which translates the aside component to show in the viewport.

We now have made a responsive/squishy dashboard.

Attached is the code-pen for reference:
[https://codepen.io/kevjose/pen/YzXrobv](https://codepen.io/kevjose/pen/YzXrobv)

#### The above is just one of the many ways to make a dashboard ui.

Thats all for now. Cheers!