Creating a Sidebar in the ServiceNow Service Portal

Sandeep RanaSandeep Rana
8 min read

Introduction: Crafting the Perfect Sidebar in ServiceNow Service Portal

Welcome to the quirky world of the ServiceNow Service Portal! If your portal feels more like a chaotic thrift store than a sleek digital space, it’s time to talk sidebars. Imagine navigating through endless forms and requests, feeling lost like a toddler in a toy store—then, boom! A stylish sidebar swoops in to save the day.

In this blog, we’re not just creating a sidebar; we’re going from scratch to build an entire portal! We’ll cover how to set up your portal, design a page, choose a theme, create a header, and, of course, whip up that fabulous sidebar. So, grab your coffee (or snack—no judgment!), and let’s make your ServiceNow portal a little more fabulous together!

Prerequisites for Creating a Sidebar in the ServiceNow Service Portal

  1. Knowledge Of HTML, CSS, and Javascript

  2. Knowledge On Widget to Widget Communication

  3. Basic Knowledge About Portals

Let’s Get Started: Creating Your Sidebar in ServiceNow Service Portal

This section is divided into subsections, so you can easily look for the specific code you want for your portal.

Creating Portal and Pages

  1. Navigate To ServiceNow → Portal and create a new Portal.
    I have already created a Test Portal and I will be using it for this blog

    1. Similarly, we will create a new page from Service Portal → Pages.

      I have already created a Test Page, which I will use for this blog.

3. Link Your Portal with the Created Page

Creating a Custom Theme and Custom Header

  1. Navigate to Service Portal → Themes

  1. Open La Jolla Theme.

Right-click on the Top, select Insert and Stay to Copy the theme. Name it - La Jolla - Custom

  1. Navigate to Service Portal → Headers & Footers

  1. Open the Stock Header Record

  1. Clone it and Rename it as Customized_Stock_Header.

    Note: Remember this as we will be using later.

  2. Add Customized_Stock_Header in our La Jolla - Custom theme

  1. Add La Jolla - Custom theme on your Portal and save the record.

Create Sidebar

  1. Navigate to Service Portal → Widgets.

  1. Create a new Widget and name it Sidebar

  1. Copy and paste the following code to create a responsive sidebar.
<div>
  <aside class="sidebar" ng-class="isSidebarActive?'active':''" id="sidebar">
    <div class="sidebar-header">
      <!-- <img src="Reapers.png" alt="logo" /> -->
      <h2>Brand Name</h2>
      <button ng-click="close_sidebar()">
        Close
        <i class="fa fa-times" aria-hidden="true"></i>
      </button>
    </div>

    <ul class="sidebar-links">
      <h4>Incidents</h4>

      <li>
        <a href="javascript:void(0)">
          <i class="fa fa-user-circle" aria-hidden="true"></i>
          Assigned To Me
        </a>
      </li>

      <li>
        <a href="javascript:void(0)">
          <i class="fa fa-address-book" aria-hidden="true"></i>
          Assigned To Team
        </a>
      </li>

      <li>
        <a href="javascript:void(0)">
          <i class="fa fa-address-card-o" aria-hidden="true"></i>
          Created By Me
        </a>
      </li>

      <h4>HR Cases</h4>

      <li>
        <a href="javascript:void(0)">
          <i class="fa fa-user-circle" aria-hidden="true"></i>
          Assigned To Me
        </a>
      </li>

      <li>
        <a href="javascript:void(0)">
          <i class="fa fa-address-book" aria-hidden="true"></i>
          Assigned To Team
        </a>
      </li>

      <li>
        <a href="javascript:void(0)">
          <i class="fa fa-address-card-o" aria-hidden="true"></i>
          Created By Me
        </a>
      </li>

      <h4>Requests</h4>

      <li>
        <a href="javascript:void(0)">
          <i class="fa fa-user-circle" aria-hidden="true"></i>
          Assigned To Me
        </a>
      </li>

      <li>
        <a href="javascript:void(0)">
          <i class="fa fa-address-book" aria-hidden="true"></i>
          Assigned To Team
        </a>
      </li>

      <li>
        <a href="javascript:void(0)">
          <i class="fa fa-address-card-o" aria-hidden="true"></i>
          Created By Me
        </a>
      </li>
    </ul>
  </aside>
</div>
$color-primary: darken(#192a56, 0%);
$color-secondary: #8e44ad;

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.sidebar {
  position: absolute;
  top: 0;
  height: 100vh;
  background-color: $color-primary;
  width: 0%;
  overflow-y: scroll;
  scrollbar-width: none;
  color: white;
  transition: all 0.3s ease-in-out;
  z-index: 999;

  // &:hover {
  //   width: 20%;
  // }
}

.sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 20px 10px;

  button {
    border: 2px solid $color-secondary;
    background-color: transparent;
    color: $color-secondary;
    padding: 5px 10px;
    border-radius: 17px;
    transition: all 0.5s ease-in-out;

    &:hover {
      background-color: $color-secondary;
      color: white;
    }
  }
}

.sidebar-header img {
  width: 50%;
}

.sidebar-links {
  margin: 30px 0;
  h4 {
    padding: 0px 10px;
    margin: 20px 0;
    padding: 10px;
    border-bottom: 2px solid #ecf0f1;
  }

  li {
    list-style: none;
    margin: 10px 0;
    transition: all 0.3s ease-in-out;

    a {
      padding: 10px;
      display: flex;
      align-items: space-between;
      gap: 0 20px;
      color: white;
    }

    &:hover {
      background-color: $color-secondary;
    }
  }
}

.active {
  width: 30%;
  opacity: 1;
}

/* CSS For Mobile */
@media only screen and (max-width: 600px) {
  .active {
    width: 70%;
    height: 100%;
  }
}
// Client JavaScript

api.controller = function ($scope, $rootScope) {
  /* widget controller */

  var c = this;
  $scope.isSidebarActive = false;

  $scope.close_sidebar = function () {
    $scope.isSidebarActive = false;
  };

  $rootScope.$on("openSidebar", function (event, isSidebarActive) {
    $scope.isSidebarActive = isSidebarActive;
  });
};
  1. Now open the Customized_Stock_Header and modify the code.
<nav
  class="navbar-inverse"
  ng-class="::{'navbar':!isViewNative, 'is-native': isViewNative}"
  role="navigation"
  aria-label="${Primary}"
  false;
>
  <div ng-show="::!isViewNative" class="navbar-header">

<!-- ------------------ Code to Open Sidebar Starts ------------------- -->
    <button ng-click="open_sidebar()" class="navbar-brand bars">
      <i class="fa fa-bars" aria-hidden="true"></i>
    </button>
<!-- ------------------ Code to Open Sidebar Ends ------------------- -->
    <a
      class="navbar-brand"
      ng-if="::!portal.logo"
      href="?id={{::portal.homepage_dv}}"
      ><span ng-bind="portal.title"></span>
    </a>
    <a
      class="navbar-brand navbar-brand-logo"
      ng-if="::portal.logo"
      ng-href="?id={{::portal.homepage_dv}}"
      ng-click="collapse()"
      aria-label="{{::logoText}}"
    >
      <img
        ng-src="{{::portal.logo}}"
        title="{{::portal.title}}"
        alt="{{::portal.title}} ${Logo}"
      />
    </a>
    <button
      ng-if="data.hasMenuItems || (!user.logged_in && page.id != portal.login_page_dv && !data.hasLogin) || user.logged_in"
      type="button"
      class="navbar-toggle collapsed"
      data-toggle="collapse"
      data-placement="bottom"
      data-toggle-second="tooltip"
      data-original-title="{{::data.toggleMsg}}"
      data-target="#sp-nav-bar"
      aria-haspopup="menu"
    >
      <span class="sr-only">${Toggle navigation}</span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
    </button>
  </div>

  <div sp-navbar-toggle="" class="collapse navbar-right" id="sp-nav-bar">
    <!-- Include The Menu -->
    <sp-widget widget="::data.menu"></sp-widget>
    <!-- Language Selector Menu -->
    <sp-widget widget="::data.langSelector"></sp-widget>
    <ul
      ng-if="(!user.logged_in && page.id != portal.login_page_dv && !data.hasLogin)"
      class="nav navbar-nav"
      role="presentation"
    >
      <li role="presentation">
        <a href ng-click="::openLogin()">${Login}</a>
      </li>
    </ul>
    <ul ng-if="user.logged_in" class="nav navbar-nav">
      <!-- chat, avatar, and logout -->
      <li ng-if="::(data.connect_support_queue_id && !isAgentChatConfigured)">
        <a href ng-click="openPopUp()" role="button">${Live Chat}</a>
      </li>
      <li ng-if="showAvatar" class="hidden-xs hidden-sm dropdown">
        <a
          href
          class="toggle-dropdown"
          data-toggle="dropdown"
          aria-expanded="false"
          aria-label="{{::data.profileBtnMsg}}: {{::user.name}}"
          id="profile-dropdown"
          role="button"
          aria-haspopup="true"
        >
          <span class="navbar-avatar" aria-hidden="true"
            ><sn-avatar class="avatar-small-medium" primary="avatarProfile"
          /></span>
          <span class="visible-lg-inline">{{::user.name}}</span>
        </a>
        <ul class="dropdown-menu" aria-label="{{::data.profileBtnMsg}}">
          <li>
            <a tabindex="-1" ng-href="?id=user_profile&sys_id={{::user.sys_id}}"
              >${Profile}</a
            >
          </li>
          <li ng-if="::!(isViewNative || isViewNativeTablet)">
            <a tabindex="-1" href="{{::portal.logoutUrl}}">${Logout}</a>
          </li>
        </ul>
      </li>
      <li ng-if="showSMAvatar" class="visible-xs-block visible-sm-block">
        <a
          ng-href="?id=user_profile&sys_id={{::user.sys_id}}"
          ng-click="collapse()"
        >
          <span class="navbar-avatar"
            ><sn-avatar
              class="avatar-small-medium"
              primary="avatarProfile" /></span
          >{{::user.name}}</a
        >
      </li>
      <li
        ng-if="::!(isViewNative || isViewNativeTablet)"
        class="visible-xs-block visible-sm-block"
      >
        <a ng-href="{{::portal.logoutUrl}}" ng-click="collapse()">${Logout}</a>
      </li>
    </ul>
  </div>
</nav>

<!-- Calling Sidebar -->
<widget id="sidebar"></widget>
.navbar {
  width: 100%;
  transition: 250ms opacity ease-in-out;
  -webkit-transition: 250ms opacity ease-in-out;
  border: 0;
  border-bottom: 4px solid $sp-navbar-divider-color;
}

.bars {
  width: 50px;
  background-color: transparent;
  border: 2px solid $color-primary;
  display: inline-block;
}

/* For Mobile Screens */
// @media only screen and (max-width: 600px) {
//   .bars {
//     display: none;
//   }
// }

.navbar-fade {
  opacity: 0.4;
}

.navbar-inverse .navbar-toggle {
  border-color: rgba(255, 255, 255, 0.25);
}

header[role="banner"],
.nav > li > a {
  max-height: 60px;
}

.nav > li > a span.fa {
  margin-left: 0.2rem;
}

@media screen and (max-width: 992px) {
  .nav > li > a {
    padding-right: 0.5rem;
    padding-left: 0.5rem;
  }
}

.navbar-brand {
  max-height: 60px;
  padding: 0;
  padding-bottom: 0.5rem;
}

.navbar-brand img,
.navbar-brand span {
  margin-left: $sp-logo-margin-x;
  margin-right: $sp-logo-margin-x;
  margin-top: $sp-logo-margin-y;
  margin-bottom: $sp-logo-margin-y;
  display: block;
  max-height: $sp-logo-max-height;
  max-width: $sp-logo-max-width;
  position: relative;
  top: 50%;
  -webkit-transform: translateY(-50%);
  -ms-transform: translateY(-50%);
  transform: translateY(-50%);
  overflow: hidden;
}

@media screen and (max-width: 992px) {
  .navbar-brand img,
  .navbar-brand span {
    max-width: $sp-logo-mobile-max-width;
  }
}

.breadcrumb-container {
  background-color: $panel-bg;
}

/* for mobile app */
.navbar-inverse.is-native {
  background-color: #405060;
}

nav {
  margin-bottom: 0px;
  border-radius: 0px;

  .toggle-dropdown {
    height: 60px;
  }
}

.navbar-right {
  padding-right: 0px;
  padding-left: 7px;
}

.navbar-nav {
  margin: 0px;
}

// PRB711244: Dropdown menu is scrollable when too many items
.scrollable-dropdown {
  max-height: 80vh;
  overflow: auto;
  height: auto;
}

.is-native {
  .scrollable-dropdown {
    max-height: 100vh;
    overflow: scroll;
    height: auto;
  }
}

/* PRB923910: Fix for Service Portal - Header Poorly Aligned in Safari */
@media screen and (min-width: 993px) {
  .navbar-right {
    display: flex !important;
    height: auto !important;
  }
}

@media (max-width: 992px) {
  .navbar-toggle {
    display: block;
  }
  .navbar-header {
    float: none;
  }
  .navbar-left,
  .navbar-right {
    float: none !important;
  }
  .navbar-nav {
    float: none !important;
  }
  .navbar-nav > li {
    float: none;
  }
  .navbar-nav > li > a {
    padding-top: 1rem;
    padding-bottom: 1rem;
  }
}
function ($rootScope, $scope, snRecordWatcher, spUtil, $location, $uibModal, cabrillo, $timeout, $window, i18n) {

$scope.isSidebarActive = false;

$scope.open_sidebar = function(){
    $scope.isSidebarActive = true;
    $rootScope.$broadcast('openSidebar', true);
}


    $scope.logoText = i18n.format(i18n.getMessage('Go to {0} Homepage'), $scope.portal.title);
    $scope.collapse = function() {
        $rootScope.$emit('sp-navbar-collapse');
    }

    $scope.avatarProfile = {
        userID: $scope.user.sys_id,
        name: $scope.user.name,
        initials: $window.NOW.user_initials
    };

    if ($window.NOW.user_avatar) {
        $scope.avatarProfile.userImage = $window.NOW.user_avatar;
    }

    if (cabrillo.isNative()) {
        if ($window.innerWidth < 767) {
            $scope.isViewNative = true;
        } else {
            $scope.isViewNativeTablet = true;
        }
    }


    $scope.openPopUp = function() {
        var url = "$chat_support.do?queueID=" + $scope.data.connect_support_queue_id;
        var popup = window.open (url, "popup", "width=900, height=600");
    };

    $scope.openLogin = function () {
        $scope.modalInstance = $uibModal.open({
            templateUrl: 'modalLogin',
            scope: $scope
        });

        var pageRoot = angular.element('.sp-page-root');
        $scope.modalInstance.rendered.then(function() {
            var $uibModalStack = $injector.get('$uibModalStack');
            var modalObj = $uibModalStack.getTop();
            var modal = modalObj.value.modalDomEl;

            modal.attr('aria-label', modal.find('.panel-title').html());
            modal.attr('aria-modal', 'true');
            pageRoot.attr('aria-hidden', 'true');
        });

        $scope.modalInstance.closed.then(function() {
            pageRoot.attr('aria-hidden', 'false');
        });
    };

    var mdScreenSize = isMDScreenSize();
    $scope.showSMAvatar = isMDScreenSize();
    $scope.showAvatar = !isMDScreenSize();

    $scope.isAgentChatConfigured = g_has_agent_chat_config;

    angular.element($window).on('resize', function () {
        if(mdScreenSize !== isMDScreenSize() && (!$scope.showSMAvatar || !$scope.showAvatar)){
            $scope.showSMAvatar = true;
            $scope.showAvatar = true;
        }
    });


    function isMDScreenSize() {
        return $window.matchMedia('(max-width: 992px)').matches;
    }

    $rootScope.$on('sp.avatar_changed', function(evt, obj) {
        $scope.userID = "";
        $scope.newAvatarId = obj.newAvatarId;
        $timeout(function(){
            $scope.userID = $scope.user.sys_id;
            $("#profile-dropdown .sub-avatar").css("background-image", 'url("' + $scope.newAvatarId + '.iix?t=small")');
        });
    });

    $scope.isHomepage = function() {
        if (!$scope.page.id)
            return true;

        if ($scope.page.id == $scope.portal.homepage_dv)
            return true;

        return false;
    };

}
  1. And that’s it. Open the created Portal page and you will see the sidebar.

Result

Ending Note

Congratulations! You’ve built a complete ServiceNow Service Portal, complete with a fabulous sidebar that enhances navigation. Remember, the journey doesn’t end here—keep gathering feedback and refining your design to make the portal even better.

Thanks for following along! Happy building, and may your sidebar always lead the way!

Source Code

Download The Update Set From Github

0
Subscribe to my newsletter

Read articles from Sandeep Rana directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sandeep Rana
Sandeep Rana

I'm a dedicated ServiceNow Developer and Analyst with four years of experience. I previously worked at Deloitte and am currently with QBRAINX. My journey in technology started as a freelance web developer, where I developed a passion for creating user-friendly web solutions. In my current role, I specialize in various aspects of ServiceNow, including Portal design, Flow, Integration, Common Configuration, and HRSD modules. What truly excites me is experimenting with the amalgamation of web development and ServiceNow capabilities. My work allows me to blend creativity with technical prowess, ensuring the solutions I create are both functional and intuitive. I bridge the gap between complex technical concepts and user-friendly designs, striving for excellence in every project. Beyond my professional endeavors, I'm a lifelong learner, constantly exploring new technological horizons. My enthusiasm for innovation fuels my commitment to delivering high-quality results. If you share a passion for technology and innovation, I'd love to collaborate and create something extraordinary together. Let's connect!