Skip to content

Duplicate Order 1: When My History Sidebar Vanished at lg

Adityo Guni Waluyo

My chat history sidebar vanished at the lg breakpoint: a CSS order tie-break and a details element forced into flex duty, two roots in one commit.

TL;DR

A missing sidebar at lg breakpoints came down to two bugs from a mobile accordion refactor. Equal flex order values let the center wrapper win the source-order tie-break, fixed with an explicit lg:order-2. A details element can't flex-size children in Chromium, so it became state-driven with aria-expanded instead.

That afternoon I opened the chat page at a 1024-pixel viewport, and the history column that usually sits on the left was gone. The center wrapper stretched out alone, as if the column never existed. No console error, nothing missing from the DOM; it just was not where it belonged.

My first guess pointed at size: maybe the 320px column collided with the remaining space, or some overflow clipped it. Both wrong. There were two root causes, and both were born from the mobile accordion refactor I shipped just before.

Equal order values, source order decides

The center wrapper only carried xl:order-2, while the sidebar sits at one via its order-1 class. So at the lg breakpoint, two flex items shared the value 1. The MDN page for the order property explains that items are sorted by ascending order value and then by source order, with 0 as the default [5]. For exact ties, the Ordering flex items guide says the group is laid out per source order [6].

I had written the center wrapper first in the JSX, so it won the tie-break. On screens wide enough for lg but not yet xl, the wrapper took the full row and the sidebar was pushed right, out of the viewport. The fix is one class:

// before: only xl has an explicit order
<div className="order-1 flex min-w-0 flex-1 flex-col ... xl:order-2">

// after: lg is explicit too
<div className="order-1 flex min-w-0 flex-1 flex-col ... lg:order-2 xl:order-2">

One note worth keeping from the same page: order only changes visual order, not logical or tab order [5]. Rearranging columns with order is safe for the eye, but it never substitutes for a sensible DOM structure.

details used outside its contract

The second root is more interesting. The mobile accordion from the previous commit was built on a details element with the class lg:contents, so on desktop the disclosure would vanish and its children would join the column layout. It turned out that in Chromium, children inside details ignore flex sizing. That is my empirical finding from debugging, and the commit records it bluntly: a details cannot act as a proper flex container in Chromium.

The official contract is exactly that: details is a disclosure widget whose contents are only visible when toggled into an open state [7]. For a plain accordion it is a fine choice; the trouble started when I forced it to be a desktop layout container. The fix steps out of details entirely: plain React state with an aria-expanded button on mobile, and a wrapper div that swaps classes:

<button onClick={() => setHistoryOpen((v) => !v)}
  aria-expanded={historyOpen}
  className="... lg:hidden">
  {ta("historyTitle")}
</button>
<div className={cn("min-h-0 flex-1 flex-col lg:flex",
  historyOpen ? "flex" : "hidden")}>
  <ChatThreadPanel ... />
</div>

The column also gets a definite lg:h-[85vh] so the timeline can fill the space and scroll inside its own column instead of dragging the page height along.

Two hours of class wrestling taught me one thing: before blaming numbers like 320px, check the small contracts nobody thinks about, from order tie-breaks to elements used outside their purpose. In the same sidebar I had also moved the chat mode toggle to the top, which left behind a related layout lesson.

Sources

Related articles