Skip to content

[2.x] fix: stop the header overlapping itself when it runs out of room - #4906

Merged
imorland merged 1 commit into
2.xfrom
im/header-overflow
Aug 5, 2026
Merged

[2.x] fix: stop the header overlapping itself when it runs out of room#4906
imorland merged 1 commit into
2.xfrom
im/header-overflow

Conversation

@imorland

@imorland imorland commented Aug 5, 2026

Copy link
Copy Markdown
Member

The problem

On a tablet-width screen the header has no way to give up space. Navigation, logo and session controls each hold their full width, so once an extension adds a few links the row overflows and controls paint over one another.

Reported on an iPad in portrait (820px): the username was cut mid-word, and with fof/links installed the nav pushed the session controls off the edge entirely.

Why it happened

Three separate causes, all needed fixing:

1. The container wasn't using the viewport. .container was pinned to the width that opened each breakpoint — width: @screen-tablet is literally 768px for everything from 768 to 991px. At 820px the layout stayed 768px wide with 52px of screen unused; at 991px, 223px unused. The bands above 1100px were already fixed this way in earlier work; this extends the same min(…, 100%) clamp down to tablet and desktop.

2. Nothing could flex. .Header-primary declared no flex behaviour at all, so it could neither shrink nor grow. .Header-secondary used margin-left: auto — and auto margins consume free space before flex distributes any, so all slack pooled into a single conspicuous gap mid-row rather than being usable.

3. Nothing ever moved out of the way. Navigation now collapses whatever doesn't fit into a menu at the end of the row, via a new OverflowingList component.

Search is now a button

Since search moved into a modal, the header control has been a readonly text field that cannot be typed into. Every path through it — click, Enter, the drawer — blurs the field and opens SearchModal.

That cost the header a text field's width (195px at desktop) for something that only ever behaved as a button, and announced itself to assistive technology as a textbox that refuses input.

It is now a <button> carrying the same icon-and-label markup as the notification and message controls, so it collapses to its icon in the header and keeps its label in the drawer. The locale selector gains an icon for the same reason — something has to identify it once the label is hidden.

Testing

countItemsThatFit is extracted from the component so the arithmetic can be tested directly: boundaries where an item exactly fits, reserving room for the toggle that is about to appear, and the monotonicity that stops the row oscillating between two states.

52 new tests, 373 passing overall.

OverflowingList measures real layout, which jsdom cannot provide — so the component tests cover the markup either side of the decision, and the sums are covered on their own.

Verified

Measured in Chrome at 768 / 820 / 900 / 991 / 992 / 1100 / 1400 / 1920px, logged in and out: no overlap at any width, items collapse only when they genuinely do not fit and return when space allows, and the row settles in one pass with no flicker. Live resizing recalculates without a reload. The drawer renders everything, since a vertical column has no width pressure.

Notes for reviewers

  • OverflowingList is a new public component. Extensions will subclass it, so the API is worth scrutiny mid-RC.
  • .Search-input is now a <button>, not an <input>. Extensions styling or extending it may need adjusting — fof/ui-kit scopes to its own .UiKit-Search and is unaffected.
  • Keyboard and focus order through the overflow menu are not covered by tests — jsdom has no focus model worth trusting here. Verified by hand; a Playwright suite would be the proper answer and is out of scope.
  • The container change affects every page at 768–1099px, not just the header.

On a tablet-width screen the header had no way to give up space. The
navigation, the logo and the session controls each held their full width,
so once an extension added a few links the row simply overflowed and the
controls painted over each other — a username cut mid-word on an iPad in
portrait, or nav links sitting on top of the search icon.

Three things were behind it:

- `.container` was pinned to the width that opened each breakpoint, so at
  820px the layout was still 768px wide and 52px of screen went unused.
  The bands above 1100px were already fixed this way; this extends the same
  clamp down to tablet and desktop.
- `.Header-primary` declared no flex behaviour at all, so it could neither
  shrink nor grow, and `.Header-secondary` used an auto margin — which takes
  free space before flex distributes any, pooling all the slack into one gap
  mid-row.
- Nothing ever moved out of the way. Navigation now collapses what does not
  fit into a menu at the end of the row, via a new `OverflowingList`.

The header search made this worse than it needed to be. Since search moved
into a modal it has been a `readonly` text field that cannot be typed into —
reserving a text field's width for a control that only ever behaved as a
button, and announcing itself to assistive technology as a textbox that
refuses input. It is now a button, with the same icon-and-label markup as
the notification and message controls, so it collapses to its icon in the
header and keeps its label in the drawer. The locale selector gains an icon
for the same reason.

The fitting arithmetic lives in `countItemsThatFit` rather than inside the
component, so the awkward cases can be tested directly: boundaries where an
item exactly fits, keeping room for the toggle that is about to appear, and
the monotonicity that stops the row oscillating between two states.

`OverflowingList` measures real layout, which jsdom cannot provide, so the
component tests cover the markup either side of the decision and the sums
are covered on their own.
@imorland
imorland requested a review from a team as a code owner August 5, 2026 23:09
@imorland imorland added this to the 2.0.0-rc.6 milestone Aug 5, 2026
@imorland
imorland merged commit 343dad7 into 2.x Aug 5, 2026
25 checks passed
@imorland
imorland deleted the im/header-overflow branch August 5, 2026 23:20
@zDaleZ

zDaleZ commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

About Search is now a button:
Though the previous search bar didn't show search texts very often, it actually does that when you close search modal with some text. If current behavior is expected, it actually doesn't matter.

However, we lost the cancel button, so after we cancel search, page continue shows us search results, unless we click a discussion in list and back...It's necessary to ensure we still able to cancel search session. @imorland

@imorland

imorland commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

About Search is now a button: Though the previous search bar didn't show search texts very often, it actually does that when you close search modal with some text. If current behavior is expected, it actually doesn't matter.

However, we lost the cancel button, so after we cancel search, page continue shows us search results, unless we click a discussion in list and back...It's necessary to ensure we still able to cancel search session. @imorland

Complete oversight on my part here! I do think a button is the way to go here, especially for space saving and aesthetics, but for sure the cancel flow needs some attention!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants