In this new tutorial, we’ll create a responsive admin dashboard layout with CSS and a touch of JavaScript. To build it, we’ll borrow some ideas from the WordPress dashboard, such as its collapsible sidebar menu.
Throughout the tutorial we’ll face plenty of challenges, but ones which will give us good practice for enhancing our front-end skills.
Without further ado, let’s have a look at the final admin dashboard demo (hit the Collapse button at the foot of the sidebar to see the collapsible nav in action, and check out the full screen version to play with its responsiveness):
1. Begin With the Page Markup
To kick off the markup we’ll need we’ll start with an SVG, a header, and a section:
<svg style="display:none;">...</svg> <header class="page-header">...</header> <section class="page-content">...</section>
SVG Sprites
As you might imagine with any admin dashboard we’re going to need a bunch of icons. Thankfully, Envato Elements provides an ever-expanding collection of useful vector icons, so let’s take advantage of that library and download these Trade and Dashboard Icons.
Rather than including them directly in the page via an img
or svg
tag, let’s go one step further and create an SVG sprite. To do this, we’ll wrap all the icons in an SVG container. The container should be hidden, so we’ll apply display: none
to it. If we don’t hide it, a big empty area will appear at the top of the page.
Each icon will be placed inside a symbol
element with a unique ID and a viewBox
attribute which will depend on the icon size. We can then render the target icon whenever we need it by calling the use
element (I’ll show you how in a minute).
For now, let’s just become familiar with the markup needed for the SVG sprite:
<svg style="display:none;"> <symbol id="down" viewBox="0 0 16 16"> <polygon points="3.81 4.38 8 8.57 12.19 4.38 13.71 5.91 8 11.62 2.29 5.91 3.81 4.38" /> </symbol> <symbol id="users" viewBox="0 0 16 16"> <path d="M8,0a8,8,0,1,0,8,8A8,8,0,0,0,8,0ZM8,15a7,7,0,0,1-5.19-2.32,2.71,2.71,0,0,1,1.7-1,13.11,13.11,0,0,0,1.29-.28,2.32,2.32,0,0,0,.94-.34,1.17,1.17,0,0,0-.27-.7h0A3.61,3.61,0,0,1,5.15,7.49,3.18,3.18,0,0,1,8,4.07a3.18,3.18,0,0,1,2.86,3.42,3.6,3.6,0,0,1-1.32,2.88h0a1.13,1.13,0,0,0-.27.69,2.68,2.68,0,0,0,.93.31,10.81,10.81,0,0,0,1.28.23,2.63,2.63,0,0,1,1.78,1A7,7,0,0,1,8,15Z" /> </symbol> <!-- more symbols here --> </svg>
And really, that’s all we need to create our inline SVG sprite.
Header
Moving on with our admin dashboard layout, let’s look at the page header.
Within it, we’ll define a nav
element which will serve as the wrapper for the following elements:
- The logo
- The Collapse button that will toggle the menu on mobile screens
- The menu itself which will contain the menu links, two headings, and the collapse/expand button. It might be more semantically correct to have two individual menus and place the headings outside them, but you can approach things differently if you prefer.
Here’s how it’ll look like on wide screens (>767px):
The header structure:
<header class="page-header"> <nav> <a href="#0"> <img class="logo" src="logo.svg" alt="forecastr logo"> </a> <button class="toggle-mob-menu" aria-expanded="false" aria-label="open menu"> <svg width="20" height="20" aria-hidden="true"> <use xlink:href="#down"></use> </svg> </button> <ul class="admin-menu"> <li class="menu-heading"> <h3>Admin</h3> </li> <li> <a href="#0"> <svg> <use xlink:href="#pages"></use> </svg> <span>Pages</span> </a> </li> <!-- more list items here --> <li> <button class="collapse-btn" aria-expanded="true" aria-label="collapse menu"> <svg aria-hidden="true"> <use xlink:href="#collapse"></use> </svg> <span>Collapse</span> </button> </li> </ul> </nav> </header>
Notice two things in the code above:
- How we use the
use
element to reference the target icons. - The ARIA attributes (
aria-expanded
,aria-label
,aria-hidden
) that we add to the toggle buttons. These attributes will help us make the component a bit more accessible. Later, we’ll discuss how their values will be updated based on the button’s state.
Section
The section will contain two nested sections.
Section #1
Inside the first section, we’ll place the search form and some info (name, avatar, and notifications) about the current logged in user.
Here’s its appearance on wide screens (>767px):
The section structure:
<section class="search-and-user"> <form> <input type="search" placeholder="Search Pages..."> <button type="submit" aria-label="submit form"> <svg aria-hidden="true"> <use xlink:href="#search"></use> </svg> </button> </form> <div class="admin-profile"> <span class="greeting">...</span> <div class="notifications"> <span class="badge">...</span> <svg> <use xlink:href="#users"></use> </svg> </div> </div> </section>
Again, notice that we add some ARIA attributes to the submit button.
Section #2
Inside the second section, just for enriching the demo with some dummy content, we’ll place a bunch of article placeholders. These might typically contain tabular data, charts, or feeds of some kind.
Here’s its appearance on wide screens (>767px):
The section structure:
<section class="page-content"> <section class="grid"> <article></article> <article></article> <article></article> <article></article> <article></article> <article></article> <article></article> <article></article> </section> </section>
2. Define Some Basic Styles
With the markup for our admin dashboard ready, we’ll forge on with the CSS. The first step, as always, is to specify some CSS variables and common reset styles:
:root { --page-header-bgColor: #242e42; --page-header-bgColor-hover: #1d2636; --page-header-txtColor: #dde9f8; --page-header-headingColor: #7889a4; --page-header-width: 220px; --page-content-bgColor: #f0f1f6; --page-content-txtColor: #171616; --page-content-blockColor: #fff; --white: #fff; --black: #333; --blue: #00b9eb; --red: #ec1848; --border-radius: 4px; --box-shadow: 0 0 10px -2px rgba(0, 0, 0, 0.075); } * { padding: 0; margin: 0; box-sizing: border-box; } ul { list-style: none; } a, button { color: inherit; } a { text-decoration: none; } button { background: none; cursor: pointer; } input { -webkit-appearance: none; } button, input { border: none; } svg { display: block; } body { font: 16px/1.5 "Lato", sans-serif; }
Note: for simplicity I won’t walk through all the CSS rules in the tutorial. There are almost 400 lines of CSS here. If you want to, you can check them all by clicking the CSS tab of the demo project.
3. Define the Main Dashboard Styles
At this point, we’re ready to concentrate on the page styles.
Style the Header
The header will be a fixed position element. Its width will be 220px and its height equal to the viewport height. In case its contents exceed the viewport height, a vertical scrollbar will appear.
The nav
element will behave as a flex container with a minimum height of 100%. Remember that its direct children are three:
- the logo,
- the mobile menu toggle button,
- and the menu.
The toggle button will be visible only on small screens (<768px). Here are the styles we need:
/*CUSTOM VARIABLES HERE*/ .page-header { position: fixed; top: 0; left: 0; right: 0; bottom: 0; overflow: auto; padding-top: 20px; width: var(--page-header-width); color: var(--page-header-txtColor); background: var(--page-header-bgColor); } .page-header nav { display: flex; flex-direction: column; min-height: 100%; } .page-header .toggle-mob-menu { display: none; }
Tip: In case you prefer an absolutely positioned header that covers the full page height, add the following styles:
body { position: relative; } .page-header { position: absolute; top: 0; left: 0; height: 100%; /*Comment these styles*/ /*position: fixed; top: 0; left: 0; right: 0; bottom: 0; overflow: auto;*/ }
Menu Styles
The menu will serve as a flex container, and we’ll give it flex: 1
, so it’ll expand and cover the full parent height.
The last menu item will be given a margin-top: auto
because it should be positioned at the very bottom of the menu. This behavior will be clearer when the header scrollbar doesn’t appear. To test it, try to remove some menu items, or check the demo in a tall screen.
The links and the button inside the menu will also act as flex containers and their contents (text and icons) should be vertically aligned.
The menu headings will be a bit smaller compared to the other menu elements. In addition, we’ll increase the spacing between their characters.
Here’s a part of the menu styles:
/*CUSTOM VARIABLES HERE*/ .page-header .admin-menu { display: flex; flex-direction: column; flex-grow: 1; margin-top: 35px; } .page-header .admin-menu li:last-child { margin-top: auto; margin-bottom: 20px; } .page-header .admin-menu li > * { width: 100%; padding: 12px 15px; } .page-header .admin-menu a, .page-header .admin-menu button { display: flex; align-items: center; font-size: 0.9rem; transition: background 0.2s, color 0.2s; } .page-header .admin-menu .menu-heading h3 { text-transform: uppercase; letter-spacing: 0.15em; font-size: 12px; margin-top: 12px; color: var(--page-header-headingColor); }
Page Content Styles
Remember that the .page-content
section contains two sub-sections.
This section will be placed 220px away from the left side of the viewport. Plus, we’ll give it width: calc(100% - 220px)
. Note that its left
property value is equal to the header width.
Its styles:
/*CUSTOM VARIABLES HERE*/ .page-content { position: relative; left: var(--page-header-width); width: calc(100% - var(--page-header-width)); min-height: 100vh; padding: 30px; color: var(--page-content-txtColor); background: var(--page-content-bgColor); }
Search and User Styles
Also, remember that the .search-and-user
section contains two elements: the search form and the .admin-profile
.
To lay it out, we’ll use CSS Grid. The search form will cover the full available space and there will be a 50px gap between it and its sibling. Both siblings will be vertically aligned.
The submit button inside the form will be absolutely positioned. It will only contain a decorative icon, and we’ll therefore need an ARIA attribute to allow screenreaders to interpret it and thereby make it accessible.
The .admin-profile
, which contains two elements, will behave as a flex container with vertically centered content. The badge (counter) element will be absolutely positioned inside its parent with horizontally and vertically centered content.
Here’s a part of the required styles for this section:
/*CUSTOM VARIABLES HERE*/ .search-and-user { display: grid; grid-template-columns: 1fr auto; grid-column-gap: 50px; align-items: center; background: var(--page-content-bgColor); margin-bottom: 30px; } .search-and-user form { position: relative; } .search-and-user form button { position: absolute; top: 50%; right: 15px; transform: translateY(-50%); } .search-and-user .admin-profile { display: flex; align-items: center; } .search-and-user .admin-profile .notifications { position: relative; } .search-and-user .admin-profile .badge { display: flex; align-items: center; justify-content: center; position: absolute; top: -10px; right: -3px; width: 18px; height: 18px; border-radius: 50%; font-size: 10px; color: var(--white); background: var(--red); }
Grid Styles
To lay out the articles, we’ll take advantage of CSS grid. We’ll give all articles a fixed height of 300px. Apart from the first and last articles which will cover the full parent width, all the others will be part of a two-column layout.
The associated styles:
/*CUSTOM VARIABLES HERE*/ .page-content .grid { display: grid; grid-template-columns: repeat(2, 1fr); grid-gap: 30px; } .page-content .grid > article { display: flex; height: 300px; background: var(--page-content-blockColor); border-radius: var(--border-radius); box-shadow: var(--box-shadow); } .page-content .grid > article:first-child, .page-content .grid > article:last-child { grid-column: 1 / -1; }
4. Toggle Header
Each time we click on the collapse/expand button, the header state will change. If it’s expanded, it will collapse (leaving just icon variants of the menu items), and vice versa.
Keep in mind that this functionality will be available only on screens greater than 767px. For smaller screens, our header will have a different layout, which we’ll get to shortly.
During the collapsed state of the header, the body receives the collapsed
class. At that point, the following things happen:
- The header shrinks. Its width changes from 220px to 40px.
- In response to this, the
.page-content
section grows. Specifically, its width changes fromwidth: calc(100% - 220px)
towidth: calc(100% - 40px)
. In addition, itsleft
property value becomes 40px instead of 220px. - The logo, the menu headings, the menu links text, and the menu button text disappear.
- The
aria-expanded
andaria-label
attribute values of the toggle button are updated. Plus, its icon is rotated 180 degrees, so it looks like an expand icon.
Here’s the JavaScript code that implements this functionality:
const body = document.body; const collapseBtn = document.querySelector(".admin-menu button"); const collapsedClass = "collapsed"; collapseBtn.addEventListener("click", function() { this.getAttribute("aria-expanded") == "true" ? this.setAttribute("aria-expanded", "false") : this.setAttribute("aria-expanded", "true"); this.getAttribute("aria-label") == "collapse menu" ? this.setAttribute("aria-label", "expand menu") : this.setAttribute("aria-label", "collapse menu"); body.classList.toggle(collapsedClass); });
And all the associated styles:
/*CUSTOM VARIABLES HERE*/ @media screen and (min-width: 768px) { .collapsed .page-header { width: 40px; } .collapsed .page-header .admin-menu li > * { padding: 10px; } .collapsed .page-header .logo, .collapsed .page-header .admin-menu span, .collapsed .page-header .admin-menu .menu-heading { display: none; } .collapsed .page-header .admin-menu svg { margin-right: 0; } .collapsed .page-header .collapse-btn svg { transform: rotate(180deg); } .collapsed .page-content { left: 40px; width: calc(100% - 40px); } }
5. Show Tooltip on Admin Menu Items
Now let’s go one step further and add another new feature to the collapsible header.
As we discussed in the previous section, when the header becomes collapsed, the text of the menu links will disappear. This means, at that point, only the SVG icons will be visible. So, let’s display a tooltip that will give users a better understanding of what each link does.
To do so, each time a menu link (icon) is being hovered over, we’ll add the title
attribute to it, with the value being its plain text. But again, that should happen only when the header is collapsed and the window width is at least 768px.
Here’s the corresponding JavaScript:
const body = document.body; const menuLinks = document.querySelectorAll(".admin-menu a"); const collapsedClass = "collapsed"; for (const link of menuLinks) { link.addEventListener("mouseenter", function() { body.classList.contains(collapsedClass) && window.matchMedia("(min-width: 768px)").matches ? this.setAttribute("title", this.textContent) : this.removeAttribute("title"); }); }
6. Going Responsive
On screens up to 767px wide, our page will look like this:
That’s a big difference from our sidebar arrangement, right? Let’s highlight the most important differences compared to the desktop version:
- Both the header and
.page-content
haveposition: static
andwidth: 100%
. - The flex direction of the
nav
element changes fromcolumn
torow
. - The mobile menu toggle button becomes visible.
- The menu is absolutely positioned right underneath the header and initially hidden. It will become visible each time we click on the toggle button.
- The collapse/expand button and the
.greeting
element are hidden. - The
.search-and-user
section is absolutely positioned and placed right next to the mobile menu toggle button.
Below you can see a part of the responsive styles:
@media screen and (max-width: 767px) { .page-header, .page-content { position: static; width: 100%; } .page-header nav { flex-direction: row; } .page-header .toggle-mob-menu { display: block; } .page-header .admin-menu { position: absolute; left: 98px; top: 57px; margin-top: 0; z-index: 2; border-radius: var(--border-radius); background: var(--page-header-bgColor); visibility: hidden; opacity: 0; transform: scale(0.95); transition: all 0.2s; } .page-header .admin-menu li:last-child, .search-and-user .admin-profile .greeting { display: none; } .search-and-user { position: absolute; left: 131px; top: 10px; padding: 0; grid-column-gap: 5px; width: calc(100% - 141px); border-radius: var(--border-radius); background: transparent; } }
7. Toggle Mobile Menu
Each time we click on the toggle button, the menu state will change. If it’s expanded, it will collapse, and vice versa.
During the expanded state of the menu, the body receives the mob-menu-opened
class. At that point, the following things happen:
- The menu appears.
- The
aria-expanded
andaria-label
attribute values of the toggle button are updated. Plus, its icon is rotated 180 degrees, so it looks like an expand icon.
Here’s the required JavaScript code:
const body = document.body; const toggleMobileMenu = document.querySelector(".toggle-mob-menu"); toggleMobileMenu.addEventListener("click", function() { this.getAttribute("aria-expanded") == "true" ? this.setAttribute("aria-expanded", "false") : this.setAttribute("aria-expanded", "true"); this.getAttribute("aria-label") == "open menu" ? this.setAttribute("aria-label", "close menu") : this.setAttribute("aria-label", "open menu"); body.classList.toggle("mob-menu-opened"); });
And the associated CSS:
.page-header .toggle-mob-menu svg { transition: transform 0.2s; } .page-header .admin-menu { transition: all 0.2s; } .mob-menu-opened .toggle-mob-menu svg { transform: rotate(180deg); } .mob-menu-opened .page-header .admin-menu { transform: scale(1); visibility: visible; opacity: 1; }
Conclusion
That’s it folks! We successfully built a fully functional admin dashboard layout. You’ll be able to expand on this foundation to create all kinds of admin interfaces. Hopefully you enjoyed this journey as much as I did!
Just a last note. I’m certainly not an accessibility expert, yet I tried to make this component more accessible by adding some common ARIA attributes. During this process, I checked the WordPress and Codepen dashboards for reference. There might be additional ARIA attributes that could have been included in the code. For example, I excluded the aria-controls
attribute which is responsible for identifying the related content, but that was because Aria-Controls is Poop.
If I missed anything, or you think that some things should have been done differently, let me know in the comments below.
As always, thanks for reading!
More Admin Dashboard UI Resources
-
Bootstrap15 Feature-Packed Bootstrap Admin Templates
-
FusionChartsHow to Build a SaaS Dashboard in React With Google Sheets and FusionCharts
No comments:
Post a Comment