Skip to content

fix: hide default theme sidebar at mobile widths - #177

Merged
rsbh merged 1 commit into
mainfrom
fix/default-theme-mobile-layout
Aug 31, 2026
Merged

fix: hide default theme sidebar at mobile widths#177
rsbh merged 1 commit into
mainfrom
fix/default-theme-mobile-layout

Conversation

@rsbh

@rsbh rsbh commented Aug 31, 2026

Copy link
Copy Markdown
Member

Summary

At phone widths the default theme rendered its full 262px desktop sidebar. That pushed the page content into the leftover space, which was narrower than the content's min-content width, so the layout overflowed the viewport and body text was clipped. Because the overflow also carried the mobile header's controls out of reach, the hamburger could not be tapped and mobile navigation was effectively dead.

Both symptoms come from one cause. The fix is a one-line selector change, plus a comment.

 @media (max-width: 768px) {
-  .sidebar {
+  /* Must repeat the `.layout aside.sidebar` selector from the base rule above:
+     a bare `.sidebar` (0,1,0) loses to that rule's (0,2,1) specificity, and a
+     media query adds no specificity of its own, so the sidebar stays visible */
+  .layout aside.sidebar {
     display: none;
   }

Bug A — desktop sidebar rendered at mobile widths

Layout.module.css declares the sidebar with a deliberately over-qualified selector, because it has to outrank Apsara's .root[data-open] rules and bundler CSS order is not guaranteed:

/* line 12 */
.layout aside.sidebar { display: flex; /* … */ }   /* specificity (0,2,1) */

The mobile block tried to undo that with a bare .sidebar — specificity (0,1,0). A media query adds no specificity of its own, so the base rule kept winning and the sidebar stayed display: flex. The rest of the block worked fine (.mobileHeader { display: flex } took effect), which is what made this look like a media-query problem rather than a specificity one.

Escalation measured at a real 390px viewport:

media-query selector specificity sidebar hidden?
.sidebar (previous) 0,1,0 no
.layout .sidebar 0,2,0 no
.layout aside.sidebar 0,2,1 yes

Bug B — horizontal overflow: same root cause, no separate fix needed

Investigated on its own terms and measured rather than assumed. The finding is that there is no independent overflow bug — it is Bug A's arithmetic.

At a real 390px viewport, before the change:

innerWidth=390  documentElement.scrollWidth=463
aside.sidebar   width=262
.mainArea       width=128  scrollWidth=201   <- content min-content width exceeds the box

262px of sidebar leaves 128px for .mainArea, which is narrower than the content's min-content width (headings, code lines, table cells), so the flex row is forced out to 463px and the page overflows by 73px. Hiding the sidebar returns the full 390px to .mainArea and the overflow disappears — no containment rule, no overflow-x: hidden, nothing masked.

A sweep of 4 pages x 12 widths confirms it. documentElement.scrollWidth vs innerWidth:

width /docs before /docs/guides/installation before after (both)
320 464 (overflow) 463 (overflow) 320
360 464 (overflow) 463 (overflow) 360
390 464 (overflow) 463 (overflow) 390
414 464 (overflow) 463 (overflow) 414
500 500 ok 500 ok 500
640 / 767 / 768 / 769 / 900 / 1024 / 1440 ok ok ok

After the change, scrollWidth === innerWidth at every width from 320 to 1440 on /docs, /docs/guides/installation, /docs/guides/quickstart and /apis. /quickstart and /apis never overflowed even before, because their content has a narrower min-content width — consistent with the explanation above.

A measurement caveat worth recording

The overflow reproduction is sensitive to how the viewport is created, and one common method reports it wrongly.

Chrome headless on macOS enforces a minimum window width of ~500 CSS px. --headless --screenshot --window-size=390,844 therefore writes a 390px-wide PNG that is a left-crop of a 500px-wide render — the image is the size you asked for, the layout is not. Verified with a probe page:

  • probe reports innerWidth=500 while the PNG is 390px wide
  • a linear-gradient 50% split lands at x=250 in the image (midpoint of 500, not of 390)

That artifact inflates the symptoms: at an effective 500px render cropped to 390px, the mobile header's search / theme / hamburger sit at x≈420-480 and fall outside the crop, and body text appears clipped mid-word. Both look like page overflow but are the crop. Note the table above: at a true 500px viewport the page does not overflow even before the fix.

All measurements and screenshots in this PR were taken through the Chrome DevTools Protocol using Emulation.setDeviceMetricsOverride + Page.captureScreenshot, which sets a genuine viewport. Validated against the same probe: reports innerWidth=390, the gradient split lands at x=195, a 390px box fills the width exactly, and a deliberately-500px box still registers as overflow — so the tool shows real overflow when real overflow exists.

Verification

check result
bun run build:cli exit 0
bun test 314 pass, 0 fail
bunx biome lint src/themes/default/ 2 warnings before, 2 warnings after — no new warnings (both pre-existing, in Layout.tsx)
overflow sweep, 4 pages x 12 widths (320-1440) no overflow at any width
desktop 1440x900, /docs and /docs/guides/installation screenshots byte-identical (matching md5) before and after

Screenshot observations at a true 390x844

Before/docs/guides/installation: the 262px sidebar occupies two-thirds of the screen; content is crushed into ~128px, wrapping "Requirement" to "Req / uirem / ent" and "Node.js" to "Nod / e.js"; the page scrolls horizontally to 463px.

After — same page: sidebar gone, content uses the full width, the System Requirements table renders in two clean columns, code blocks fit, and the mobile header shows logo + search + theme switcher + hamburger.

After/docs: the strings the original report saw truncated are now complete — "bold italic text" renders in full and "Internal link" / "External link" are no longer cut mid-word.

Hamburger — clicked at a true 390px viewport: aria-expanded flips to true, #mobile-menu goes to data-open="true" / display: block at 390x796 with 5 nav links, and the icon swaps to the close X. Mobile navigation works.

Desktop 1440x900 — before and after are the same image (md5 8ad71b25… for /docs/guides/installation, 8d5068f4… for /docs). Sidebar present, content laid out normally, no regression.

Scope

Default theme only — one CSS file, 4 insertions / 1 deletion. The paper theme has no media queries at all and no mobile layout; that is a much larger piece of work and is untouched here. No dependencies added, no unrelated lint or type errors touched.

🤖 Generated with Claude Code

The default theme's mobile breakpoint never hid the desktop sidebar, so at
phone widths the full 262px sidebar rendered and squeezed the page content
into whatever was left.

Root cause is CSS specificity. The base rule is written as
`.layout aside.sidebar { display: flex; ... }` — specificity (0,2,1) — with a
comment explaining it has to outrank Apsara's `.root[data-open]` rules. The
`@media (max-width: 768px)` block tried to undo it with a bare `.sidebar`
(0,1,0). A media query contributes no specificity of its own, so the base rule
kept winning and the sidebar stayed `display: flex`. Escalating the override to
match the base rule's selector fixes it; measured at a real 390px viewport,
`.sidebar` and `.layout .sidebar` both fail and `.layout aside.sidebar` works.

This also resolves the horizontal overflow seen at the same widths. With the
sidebar occupying 262px of a 390px viewport, the remaining 128px was narrower
than the content's min-content width, so the flex row was forced wider than the
viewport and body text was clipped. There is no separate overflow bug: sweeping
/docs, /docs/guides/installation, /docs/guides/quickstart and /apis across
320-1440px, `documentElement.scrollWidth` exceeded `innerWidth` only below
500px and only before this change; afterwards it equals `innerWidth` at every
width tested. With the layout no longer overflowing, the mobile header's
search, theme switcher and hamburger button are reachable again, so the mobile
menu can actually be opened.

Desktop is unaffected — the rule lives inside the mobile media query, and
1440x900 screenshots of /docs and /docs/guides/installation are byte-identical
before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chronicle Ready Ready Preview Aug 31, 2026 6:09am

@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 59 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e2c5b069-c7dd-4086-b9ca-3ee0be084b07

📥 Commits

Reviewing files that changed from the base of the PR and between a26adf4 and 5044189.

📒 Files selected for processing (1)
  • packages/chronicle/src/themes/default/Layout.module.css

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@rsbh
rsbh merged commit 646bfaf into main Aug 31, 2026
9 checks passed
@rsbh
rsbh deleted the fix/default-theme-mobile-layout branch August 31, 2026 06:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants