Building a dashboard UI using grid and flex-box
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
andfloats
. 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:
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
Before we begin, the final dashboard will look as below: {% codepen 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
<!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
<!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 childrengrid-template-columns
, defines the number and sizes of columns the grid has to be divide intogrid-template-rows
, defines the number and sizes of rows the grid has to be divided intogrid-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
<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">©2020</div>
<div class="footer_byline">Made with ♥</div>
</footer>
</div>
flex
is assigned to the parent element and applied on it’s direct siblingsdisplay: 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 theflex-direction: column
propertyalign-items
, defines how items are placed across the cross axis (perpendicular to the main- axis), vertically in the present casejustify-content
, define the alignment across the main axis, horizontal in this case
Layout for aside navigation
<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">©2020</div>
<div class="footer_byline">Made with ♥</div>
</footer>
</div>
flex-direction: column;
, has been used to stack items one below the other
Layout for main content area
<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 distributedflex-grow
, defines the proportion by which an item can grow with respect to other items. It accepts a unit-less valueflex-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)
<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
<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.
<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> ☰</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>×</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
The above is just one of the many ways to make a dashboard ui.
Thats all for now. Cheers!