TL;DR
You can add a following CSS ruleset to override the behaviour:
.top-bar .-secondary .-link .indicator-badge:not(._regular) {
position: relative;
}
.top-bar .-secondary .-link[data-unread-count]:not([data-unread-count='0']) {
flex-direction: column;
flex-flow: column-reverse;
justify-content: flex-end;
}
If you need a quick way to insert the overrides, here is a small snippet:
((_w,d) => {
const style = d.createElement("style");
d.head.append(style);
const {sheet} = style;
if (!sheet) return;
sheet.insertRule(`
.top-bar .-secondary .-link .indicator-badge:not(._regular) {
position: relative;
}`);
sheet.insertRule(`
.top-bar .-secondary .-link[data-unread-count]:not([data-unread-count='0']) {
justify-content: flex-end;
flex-direction: column;
flex-flow: column-reverse;
}`);
})(window, document);
Long answer
To second Glorfindel, this is indeed non-trivial as those status badges are positioned via position
rule set to absolute
(a common technique of putting absolutely positioned children into a relatively positioned parent) which removes them from the normal document flow. Quoting MDN:
The element is removed from the normal document flow, and no space is created for the element in the page layout.
What you want requires the document flow to account for the width of both, so the first thing you need to do is to set position
to relative
which makes the badges, quoting MDN again:
positioned according to the normal flow of the document, and then offset relative to itself based on the values of top, right, bottom, and left.
Additionally, you'll need to adjust top
rule to somewhere around -20%
(and maybe set left
to the same value for good measure) as otherwise, the badges will stick to the bottom (there are other ways to align them, but since we are using relative
, we might as well harvest the benefits of top
).
The problem is that, of course, once you do that, the parent element's width will be expanded to accommodate the elements which is likely not what you wanted (although serviceable):

Flexbox to the rescue. If you take a close look, each of the menu items are flex containers by themselves with display
set to inline-flex
. Why not use that to our advantage? First, let's review how each is structured:
<a> <!-- flex container -->
<svg>icon</svg> <!-- flex-item -->
<span>number</span> <!-- flex-item -->
</a>
The container has the flex-direction
set to row
(as this is the default), thus making <svg>
and <span>
items align themselves horizontally. Let's change that to column
(assuming we removed the position: absolute
rule earlier):

Now we are getting somewhere. But we obviously want the badge to be displayed on top of the icon. Flexbox has us covered: we only need to change the flex-flow
rule to column-reverse
to invert positioning:

We also want the badges to slightly overlap the icon. It's a bit tricky, but here is where the position
rule comes handy: if we set it to relative
for the badges, we get the desired result:

Oh, no! Now we lost our neat vertical alignment of icons. Fear not as flexbox has all we need. We just need to adjust the justify-content
rule to flex-end
(as we inverted the item flow with column-reverse
) only for the items that have the badges displayed.
Note that we take advantage of the observation that menu items with activity badges have a data
attribute unread-count
:
.top-bar .-secondary .-link[data-unread-count]:not([data-unread-count='0']) {
justify-content: flex-end;
}
And voila, everything's neatly aligned and not overlapping:

The only small caveat is the positioning of very small values, but it's a separate topic:
