diff --git a/components/header-bar/src/notification-icon.js b/components/header-bar/src/notification-icon.js
index 6a2010030..285a84749 100755
--- a/components/header-bar/src/notification-icon.js
+++ b/components/header-bar/src/notification-icon.js
@@ -1,9 +1,11 @@
-import { colors, theme, spacers } from '@dhis2/ui-constants'
+import { colors, elevations, spacers } from '@dhis2/ui-constants'
import { IconMessages24, IconMail24 } from '@dhis2/ui-icons'
import PropTypes from 'prop-types'
-import React from 'react'
+import React, { useEffect, useState } from 'react'
import i18n from './locales/index.js'
+const BADGE_REVEAL_DURATION_MS = 3000
+
function icon(kind) {
if (kind === 'message') {
return
@@ -19,67 +21,123 @@ export const NotificationIcon = ({
dataTestId,
title,
'aria-label': ariaLabel,
-}) => (
-
- {icon(kind)}
+}) => {
+ const [badgeExpanded, setBadgeExpanded] = useState(count > 0)
+
+ // Briefly reveal the full counter on mount, then collapse it to a dot
+ useEffect(() => {
+ if (count === 0) {
+ return
+ }
+ const timer = setTimeout(
+ () => setBadgeExpanded(false),
+ BADGE_REVEAL_DURATION_MS
+ )
+ return () => clearTimeout(timer)
+ // count is intentionally omitted: this should only run once on mount
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
+
+ return (
+
+ {icon(kind)}
+
+ {count > 0 && (
+
+ {count > 99 ? '99+' : count}
+
+ )}
- {count > 0 && {count}}
+
-
-)
+ /* Dot state (default) */
+ min-width: ${spacers.dp8};
+ max-width: ${spacers.dp8};
+ height: ${spacers.dp8};
+ border-radius: ${spacers.dp4};
+ padding: 0;
+ color: transparent;
+ box-shadow: 0 0 0 2px #2c6693;
+
+ transition: max-width 150ms ease-in-out,
+ min-width 150ms ease-in-out, height 150ms ease-in-out,
+ border-radius 150ms ease-in-out,
+ padding 150ms ease-in-out, color 150ms ease-in-out,
+ box-shadow 150ms ease-in-out;
+ }
+
+ /* Full counter badge */
+ span.is-expanded,
+ a:hover span,
+ a:focus-visible span {
+ min-width: 18px;
+ max-width: 80px;
+ height: 18px;
+ border-radius: ${spacers.dp12};
+ padding: 0 ${spacers.dp4};
+ color: ${colors.teal800};
+ box-shadow: ${elevations.e100};
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ span {
+ transition: none;
+ }
+ }
+ `}
+
+ )
+}
NotificationIcon.propTypes = {
'aria-label': PropTypes.string.isRequired,