Some PDIs are currently unavailable, and PDI actions are paused. View the latest updates here. Read More

SandilyaSridhar
ServiceNow Employee

 

Implementation Playbook

How to Programmatically Toggle the Now Assist Chatbot in Employee Center

Add an open/close toggle directly in the EC header — using the platform's own internal event lifecycle. No OOB code modified.

Before you start: Apply all changes in a custom Update Set. Test in sub-production first. Changes go in the Employee Center Header (sp_header_footer).
1

How It Works

The toggle reuses the same internal mechanisms as the native FAB sparkle button (open) and chevron-down close button (close). No OOB code is modified.

Open Sequence

1
Unhide Wrapper
Set c.hideChat = false on the Dialog Widget's scope
2
Wait 300ms
FAB sparkle button needs time to render
3
Click FAB
Triggers CREATE_WINDOW + OPEN_WINDOW

Close Sequence

1
Find Close Button
Traverse shadow DOM to #close-window
2
Click It
Triggers CLOSE_WINDOW lifecycle
3
Event Fires
NOW_ASSIST_DIALOG#CLOSED syncs state
2

Events & DOM Structure

Events Reference

Event Direction Description
NOW_ASSIST_DIALOG#OPENED Output Fired when chat opens
NOW_ASSIST_DIALOG#CLOSED Output Fired when chat closes
NOW_ASSIST_DIALOG#PINNED Output Fired when chat is pinned/unpinned
SN_WINDOW#WINDOW_OPENED Internal Dispatched to content element on open
SN_WINDOW#WINDOW_CLOSED Internal Dispatched to content element on close
SN_WINDOW_MANAGER#OPEN_WINDOW Action Internal open action
SN_WINDOW_MANAGER#CLOSE_WINDOW Action Internal close action

Shadow DOM Hierarchy

now-assist-full-page-wrapper-app └─ shadowRoot └─ now-assist-dialog [opened] └─ shadowRoot ├─ sn-window-manager │ └─ shadowRoot │ └─ sn-window │ └─ shadowRoot │ └─ now-button#close-window CLOSE │ └─ shadowRoot → button └─ sn-nass-sp-sparkle-icon FAB └─ shadowRoot └─ now-button-circular └─ shadowRoot → button OPEN

Angular Wrapper

#sn-na-dw-wrapper is controlled by the Dialog Widget's controller. c.hideChat must be false before the FAB is clickable.

Wrapper HTML
 
<div class="chat-wrapper" id="sn-na-dw-wrapper"
     ng-class="{'hidden': c.hideChat, 'chat-pinned': !c.hideResizer}">
3

Console Testing Scripts

Paste into Chrome DevTools to validate before applying widget changes.

3.1 — Open

Console — Open
 
function openNowAssist() {
    // Unhide wrapper via Angular scope
    var wrapper = document.getElementById('sn-na-dw-wrapper');
    var s = angular.element(wrapper).scope();
    while (s && !(s.c && s.c.hasOwnProperty('hideChat'))) {
        s = s.$parent;
    }
    s.c.hideChat = false;
    if (!s.$$phase && !s.$root.$$phase) s.$apply();

    // Wait for FAB, then click
    setTimeout(function() {
        var app = document.querySelector('now-assist-full-page-wrapper-app');
        var dialog = app.shadowRoot.querySelector('now-assist-dialog');
        var sparkle = dialog.shadowRoot.querySelector('sn-nass-sp-sparkle-icon');
        var circular = sparkle.shadowRoot.querySelector('now-button-circular');
        circular.shadowRoot.querySelector('button').click();
        console.log('✓ Now Assist OPENED');
    }, 300);
}
openNowAssist();

3.2 — Close

Console — Close
 
function closeNowAssist() {
    var app = document.querySelector('now-assist-full-page-wrapper-app');
    var dialog = app.shadowRoot.querySelector('now-assist-dialog');
    var winMgr = dialog.shadowRoot.querySelector('sn-window-manager');
    var snWin = winMgr.shadowRoot.querySelector('sn-window');
    var closeBtn = snWin.shadowRoot.querySelector('now-button#close-window');
    closeBtn.shadowRoot.querySelector('button').click();
    console.log('✓ Now Assist CLOSED');
}
closeNowAssist();

3.3 — Diagnostic

Console — Diagnostic
 
var app = document.querySelector('now-assist-full-page-wrapper-app');
console.log('1. app:', !!app);
var dialog = app?.shadowRoot?.querySelector('now-assist-dialog');
console.log('2. dialog:', !!dialog, 'opened:', dialog?.hasAttribute('opened'));
var sparkle = dialog?.shadowRoot?.querySelector('sn-nass-sp-sparkle-icon');
console.log('3. sparkle:', !!sparkle);
var circular = sparkle?.shadowRoot?.querySelector('now-button-circular');
console.log('4. FAB btn:', !!circular?.shadowRoot?.querySelector('button'));
var wrapper = document.getElementById('sn-na-dw-wrapper');
var s = angular.element(wrapper).scope();
while (s && !(s.c?.hasOwnProperty('hideChat'))) { s = s.$parent; }
console.log('5. scope:', !!s, 'hideChat:', s?.c?.hideChat);
4

Widget Implementation

Three changes to the Employee Center Header (sp_header_footer).

Button is gated behind ng-if="data.showSparkle" — only renders when Now Assist is enabled on the portal.

4.1 — Client Script

Add after hideSearchWidgetForNASS, before c.evaluateExperienceFeedbackDrawerVisibility:

Client Script
 
/* ── Now Assist Header Toggle ── */
c.nowAssistOpen = false;

function getDialogScope() {
    var wrapper = document.getElementById('sn-na-dw-wrapper');
    if (!wrapper) return null;
    var s = angular.element(wrapper).scope();
    while (s && !(s.c && s.c.hasOwnProperty('hideChat'))) {
        s = s.$parent;
    }
    return s;
}

function safeApply(scope) {
    if (!scope.$$phase && !scope.$root.$$phase) scope.$apply();
}

function clickFab() {
    var app = document.querySelector('now-assist-full-page-wrapper-app');
    if (!app || !app.shadowRoot) return false;
    var dialog = app.shadowRoot.querySelector('now-assist-dialog');
    if (!dialog || !dialog.shadowRoot) return false;
    var sparkle = dialog.shadowRoot.querySelector('sn-nass-sp-sparkle-icon');
    if (!sparkle || !sparkle.shadowRoot) return false;
    var circular = sparkle.shadowRoot.querySelector('now-button-circular');
    if (!circular || !circular.shadowRoot) return false;
    var btn = circular.shadowRoot.querySelector('button');
    if (btn) { btn.click(); return true; }
    return false;
}

function clickCloseButton() {
    var app = document.querySelector('now-assist-full-page-wrapper-app');
    if (!app || !app.shadowRoot) return false;
    var dialog = app.shadowRoot.querySelector('now-assist-dialog');
    if (!dialog || !dialog.shadowRoot) return false;
    var winMgr = dialog.shadowRoot.querySelector('sn-window-manager');
    if (!winMgr || !winMgr.shadowRoot) return false;
    var snWin = winMgr.shadowRoot.querySelector('sn-window');
    if (!snWin || !snWin.shadowRoot) return false;
    var closeBtn = snWin.shadowRoot.querySelector('now-button#close-window');
    if (!closeBtn || !closeBtn.shadowRoot) return false;
    var btn = closeBtn.shadowRoot.querySelector('button');
    if (btn) { btn.click(); return true; }
    return false;
}

c.toggleNowAssist = function() {
    var dlgScope = getDialogScope();
    if (!dlgScope) return;

    if (c.nowAssistOpen) {
        clickCloseButton();
        c.nowAssistOpen = false;
    } else {
        dlgScope.c.hideChat = false;
        safeApply(dlgScope);
        $timeout(function() {
            clickFab();
            c.nowAssistOpen = true;
        }, 300);
    }
};

c._syncNowAssistState = function() {
    var app = document.querySelector('now-assist-full-page-wrapper-app');
    if (!app) return;
    app.addEventListener('NOW_ASSIST_DIALOG#OPENED', function() {
        c.nowAssistOpen = true;
        try { safeApply($scope); } catch (e) {}
    });
    app.addEventListener('NOW_ASSIST_DIALOG#CLOSED', function() {
        c.nowAssistOpen = false;
        try { safeApply($scope); } catch (e) {}
    });
};
$timeout(c._syncNowAssistState, 2000);
/* ── End Now Assist Header Toggle ── */

4.2 — Template (HTML)

Add before <!-- Esc Notifications Bell --> inside the desktop header-menu div:

Template
 
<!-- Now Assist Toggle -->
<div class="gt-menu-item na-header-toggle"
     ng-if="data.showSparkle">
  <a href="javascript&colon;void(0)"
     id="na-header-toggle-btn"
     ng-click="c.toggleNowAssist()"
     aria-label="{{c.nowAssistOpen
       ? 'Close Now Assist' : 'Open Now Assist'}}"
     data-toggle="tooltip"
     data-placement="bottom"
     data-original-title="{{c.nowAssistOpen
       ? 'Close Now Assist' : 'Open Now Assist'}}">
    <span class="na-toggle-icon" aria-hidden="true">
      <svg xmlns="http://www.w3.org/2000/svg"
           viewBox="0 0 24 24" width="16"
           height="16" fill="currentColor">
        <path d="M12.87 3.52c.2-.77 1.28-.77
          1.48 0l.5 1.9a3.3 3.3 0 0 0 2.33
          2.33l1.9.5c.77.2.77 1.28 0 1.48l-1.9
          .5a3.3 3.3 0 0 0-2.33 2.34l-.5
          1.89c-.2.77-1.28.77-1.48 0l-.5-1.9a3.3
          3.3 0 0 0-2.33-2.33l-1.9-.5c-.77-.2
          -.77-1.28 0-1.48l1.9-.5a3.3 3.3 0
          0 0 2.33-2.33l.5-1.9z"/>
      </svg>
    </span>
    <span ng-bind-html="'${Now Assist}'"
          aria-hidden="true"></span>
  </a>
</div>

4.3 — CSS (SCSS)

CSS
 
/* ── Now Assist Header Toggle ── */
.na-header-toggle {
  a {
    display: flex;
    align-items: center;
    gap: 6px;
  }
}
.na-toggle-icon {
  display: inline-flex;
  align-items: center;
  svg { fill: $navbar-inverse-link-color; }
}
.na-header-toggle a:hover .na-toggle-icon svg {
  fill: $navbar-inverse-link-hover-color;
}
/* ── End Now Assist Header Toggle ── */
5

Why This Is Safe

  • No OOB code modified — Dialog Widget, sn-window-manager, and all Now Experience components untouched.
  • Same code paths — Open uses FAB's native click. Close uses #close-window's native click.
  • Gated visibility — Only renders when Now Assist is enabled (ng-if="data.showSparkle").
  • Safe AngularsafeApply checks $$phase before $apply.
  • Upgrade resilient — If shadow DOM changes, toggle returns false without errors.
6

Troubleshooting

Symptom Resolution
Button not visible Verify data.showSparkle is true.
First click unresponsive Window not yet created. FAB click handles it. Increase $timeout to 500 if needed.
Open does nothing Run diagnostic (3.3). Verify each shadow DOM node is non-null.
Close does nothing #close-window only exists when chat is open.
$rootScope:inprog safeApply prevents this. Use exact code from 4.1.
nowWindowManager undefined Expected in Service Portal. Code doesn't depend on it.
Developed through iterative DOM analysis, event tracing, and sn-window-manager source review. All paths validated on a live instance.

Now Assist Self-Service · Employee Center · Service Portal

Version history
Last update:
‎04-14-2026 05:31 AM
Updated by:
Contributors