Replacing the three dots in the header with a clock

Ever since I removed the Compact Header module (deprecated) I’ve been missing the clock in the upper right corner. I loved that feature and my wife is mad it’s gone. :stuck_out_tongue:

Since I do not use custom themes and do not want to modify every users profile setting I tried to solve it like so:

configuration.yaml

frontend:
  extra_module_url:
    - /local/toolbar-clock.js

/www/toolbar-clock.js

customElements.whenDefined('mwc-icon-button').then(() => {

    const haButtonMenu = document.querySelector('home-assistant').shadowRoot
        .querySelector('home-assistant-main').shadowRoot
        .querySelector('ha-panel-lovelace').shadowRoot
        .querySelector('hui-root').shadowRoot
        .querySelector('ha-button-menu');

    haButtonMenu.updateComplete.then(() => {

        function haClock() {
            c.innerHTML = (new Date()).toLocaleTimeString(navigator.language, {
                hour: '2-digit',
                minute: '2-digit'
            });
        }

        const mdcButton = haButtonMenu.querySelector('mwc-icon-button').shadowRoot
            .querySelector('button')

        const haMenuSpan = haButtonMenu.querySelector('mwc-icon-button').shadowRoot
            .querySelector('span')

        // Adjust the CSS
        mdcButton.style.height = 'auto';
        mdcButton.style.width = 'auto';
        mdcButton.style.padding = '0px';

        // Insert the clock
        var c = document.createElement('span');
        haMenuSpan.parentNode.replaceChild(c, haMenuSpan);

        // Start the clock
        haClock();
        setInterval(function() {
            haClock();
        }, 1000);
    });
})

Copying the code from toolbar-clock.js into the Chrome console adds the clock every time, but when the code is added using the configuration.yaml file I get an error message most of the time (not always though).

It looks like the code runs to early, but even after playing around with whenDefined and updateComplete I just can’t get it to work reliable.

Uncaught (in promise) TypeError: Cannot read property 'shadowRoot' of null
at toolbar-clock.js:4

Any hints would be very much appreciated. :slight_smile:

5 Likes

I also miss the clock in the header. Have you come up with a reliable solution ?
Thanks :slight_smile: !

Not yet unfortunately. The code works when pasting into the web-browser console, but not when adding it to HA. :confused:

This is working for me in chrome.

I added the date and time:
image

For me it seems to work only when wrapping the whole code in a delay function like so:

setTimeout(function(){
 // code here
}, 3000);
3 Likes

Thanks, @NeoID !
If anyone else wants a 24-hour clock (like me). You can simply follow the top answer from here: https://stackoverflow.com/questions/22347521/change-time-format-to-24-hours-in-javascript

I don’t love the setTimeout (though you can easily lower the time, I use 500) - but it works. Does anyone know how to get around this?

Here’s is an updated version that supports core-2021.11.1:

setTimeout(function(){
    customElements.whenDefined('mwc-icon-button').then(() => {

        const haButtonMenu = document.querySelector('home-assistant').shadowRoot
            .querySelector('home-assistant-main').shadowRoot
            .querySelector('ha-panel-lovelace').shadowRoot
            .querySelector('hui-root').shadowRoot
            .querySelector('ha-button-menu');

        haButtonMenu.updateComplete.then(() => {

            function haClock() {
                c.innerHTML = (new Date()).toLocaleTimeString(navigator.language, {
                    hour: '2-digit',
                    minute: '2-digit'
                });
            }

            const mdcButton = haButtonMenu.querySelector('ha-icon-button').shadowRoot
                .querySelector('mwc-icon-button').shadowRoot
                .querySelector('button')

            const haMenuSpan = haButtonMenu.querySelector('ha-icon-button').shadowRoot
                .querySelector('mwc-icon-button').shadowRoot
                .querySelector('span')

            // Adjust the CSS
            mdcButton.style.height = 'auto';
            mdcButton.style.width = 'auto';
            mdcButton.style.padding = '0px';

            // Insert the clock
            var c = document.createElement('span');
            haMenuSpan.parentNode.replaceChild(c, haMenuSpan);

            // Start the clock
            haClock();
            setInterval(function() {
                haClock();
            }, 1000);
        });
    })
}, 3000);
3 Likes

This is great. My time stamp was text wrapping, so I just added:

            // Adjust the CSS
            mdcButton.style.height = 'auto';
            mdcButton.style.width = 'auto';
            mdcButton.style.padding = '0px';
            mdcButton.style.whiteSpace = "nowrap"

To the CSS. Thanks!

1 Like

Just what I was looking for. Thank you. Here’s how to show the date (substitute “toLocaleTimeString” with “toLocaleDateString” and then select from options described here: Intl.DateTimeFormat() constructor - JavaScript | MDN)

Screenshot 2022-05-28 121504

        haButtonMenu.updateComplete.then(() => {

            function haClock() {
                c.innerHTML = (new Date()).toLocaleTimeString(navigator.language, {
                    hour: '2-digit',
                    minute: '2-digit'
                    weekday: 'short'
                    month: 'short'
                    day: 'numeric'
                    year: 'numeric'
                });
            }

And for those who may be wondering how you edit the page if the three dots are missing, note that the date and time functionally replace the three dots, so that clicking on them recovers the edit menu.

1 Like

Thanks everyone that posted in this thread, this is working perfectly for me! Also for anyone as naive as myself that wants to do this remember that you have to restart HA for the changes to show up!

This works indeed, but only when accessing HA over a browser via the web URL. Anybody has an idea on how to show the clock instead of the dots when using the Companion app ?

The clock shows up for me with the iOS companion app (using Nabu Casa).

This hack just stopped working with HA Core Update 2024.3.0 when accessing HA over a browser. However, it continues to work in the iOS companion app.

I got it working again in 2024.3.0 on web with a few fixes.
But this will not work in the companion app.

setTimeout(function(){
    customElements.whenDefined('mwc-icon-button').then(() => {

        const haButtonMenu = document.querySelector('home-assistant').shadowRoot
            .querySelector('home-assistant-main').shadowRoot
            .querySelector('ha-drawer')
            .querySelector('ha-panel-lovelace').shadowRoot
            .querySelector('hui-root').shadowRoot
            .querySelector('.action-items');

            function haClock() {
                c.innerHTML = (new Date()).toLocaleTimeString(navigator.language, {
                    hour: '2-digit',
                    minute: '2-digit',
                    /*
                    weekday: 'short',
                    month: 'short',
                    day: 'numeric',
                    year: 'numeric'
                    */
                });
            }

            const mdcButton = haButtonMenu.querySelector('ha-icon-button[data-selector="EDIT_DASHBOARD"]').shadowRoot
                .querySelector('mwc-icon-button').shadowRoot
                .querySelector('button')

            const haMenuSpan = haButtonMenu.querySelector('ha-icon-button[data-selector="EDIT_DASHBOARD"]').shadowRoot
                .querySelector('mwc-icon-button').shadowRoot
                .querySelector('span')

            // Adjust the CSS
            mdcButton.style.height = 'auto';
            mdcButton.style.width = 'auto';
            mdcButton.style.padding = '0px';
            mdcButton.style.whiteSpace = "nowrap"

            // Insert the clock
            var c = document.createElement('span');
            haMenuSpan.parentNode.replaceChild(c, haMenuSpan);

            // Start the clock
            haClock();
            setInterval(function() {
                haClock();
            }, 1000);
        });
    }, 500);

Thanks, but this did not work for me. It behaved similarly to the old code in that it only shows up with narrow web windows and continues to work with the iOS app.

A completely different approach is to use card-mod to create a new theme.

See the posts by Duke_box here:

Install card-mod:

I used the Time & Date integration (but there are other approaches):

Here is what it looks like (in web browser):
Screenshot 2024-03-14 170236

It also works in the companion app (at least the iOS app).

Here is a theme I created (without borders) modifying Duke_box’s theme:

no_borders_day_date_time:
  card-mod-theme: no_borders_day_date_time
  ha-card-border-radius: 2px
  ha-card-border-color: rgba(0,0,0,0)
  ha-card-border-width: 0px
  primary-background-color: "#F3F3F3"

  card-mod-root-yaml: |
    .: |
      /* this hides the pencil icon & replaces it with the time when browser window is wide */
      ha-icon-button[data-selector="EDIT_DASHBOARD"] {
        color: transparent !important;
      }
      ha-icon-button[data-selector="EDIT_DASHBOARD"]::after {
        content: "{{ now().strftime('%A') }}" ", " "{{states('sensor.date_time')}}";
        color: var(--text-primary-color);
        visibility: visible;
        position: relative;
        font-size: 20px;
        top: 14px;
        right: 2px;

      }
      /* this hides the overflow icon & replaces it with the time when browser window is narrow or in edit mode or in the companion app */
      ha-button-menu {
        color: transparent !important;
      }
      ha-button-menu::before {
        content: "{{states('sensor.date_time')}}";
        color: var(--text-primary-color);
        visibility: visible;
        position: relative;
        font-size: 14px;
        top: 25px;
        white-space: nowrap;
      }

      ha-app-layout {
        --ha-card-border-width: 0px;
        --ha-card-border-radius: 2px
      }  
'''

Thank you for your sharing your solutions! @FortranFour, your card-mod approach looks good, however the clock in not updating automatically as we have {{states('sensor.date_time')}} rendered only while refreshing the page.

I ended mixing both approaches. In case anyone is interested, here is my (currently working) solution:

configuration.yaml

# Load frontend themes from the themes folder
frontend:
  themes: !include_dir_merge_named themes
  extra_module_url:
    - /local/custom_theme/time-display.js

themes/my_custom_clock_theme.yaml

my_custom_clock_theme:
  modes:
    dark:
      primary-color: "#03a9f4"
      secondary-color: "#ff9800"
    light:
      primary-color: "#03a9f4"
      secondary-color: "#ff9800"
  card-mod-theme: my_custom_clock_theme
  card-mod-root-yaml: |
    .: |
      ha-icon-button[data-selector="ASSIST"] {
        color: transparent !important;
      }
      ha-icon-button[data-selector="SEARCH"] {
        color: transparent !important;
      }
      ha-icon-button[data-selector="ASSIST"]::after {
        content: var(--clock-value, '');
        color: var(--text-primary-color);
        visibility: visible;
        position: relative;
        font-size: 20px;
        top: 14px;
        right: 2px;
      }
      ha-button-menu {
        color: transparent !important;
      }
      ha-button-menu::before {
        content: var(--clock-value, '');
        color: var(--text-primary-color);
        visibility: visible;
        position: relative;
        font-size: 14px;
        top: 25px;
        white-space: nowrap;
      }
      ha-app-layout {
        --ha-card-border-width: 0px;
        --ha-card-border-radius: 2px
      }

www/custom_theme/time-display.js

function updateTime(icon) {
    const timeString = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
    if(icon) {
        icon.style.setProperty('--clock-value', '"'+timeString+'"')
    }
}

async function waitForElement(selector) {
    while (true) {
        const element = document.querySelector(selector);
        if (element) {
            return element;
        }
        await new Promise(requestAnimationFrame);
    }
}

async function waitForShadowElement(root, selector) {
    while (true) {
        const element = root.querySelector(selector);
        if (element) {
            return element;
        }
        await new Promise(requestAnimationFrame);
    }
}

customElements.whenDefined('home-assistant').then(async () => {
    const ha = await waitForElement('home-assistant');
    const haMain = await waitForShadowElement(ha.shadowRoot, 'home-assistant-main');
    const haPanel = await waitForShadowElement(haMain.shadowRoot, 'ha-panel-lovelace');
    const huiRoot = await waitForShadowElement(haPanel.shadowRoot, 'hui-root');
    const assistIcon = await waitForShadowElement(huiRoot.shadowRoot, 'ha-icon-button[data-selector="ASSIST"]');

    if (assistIcon) {
        updateTime(assistIcon);
        setInterval(updateTime, 1000, assistIcon);
    }
});

To see the clock, make sure that you have selected the my_custom_clock_theme in your “Browser settings” (Click on your user → stay on the “General” tab → select the theme in the “Browser settings” section)

HTH!

2 Likes

Thanks for sharing your solution!

A small comment: if you want to replace the 3 vertical dots, change ha-icon-button[data-selector=“ASSIST”] to ha-button-menu in time-display.js

Sietse