Summary
Because mobileLayout() is false during SSR and only flips on the client (src/default-theme/globals.ts), the server always renders the desktop layout. On viewports ≤ 1100px, a full page load therefore paints both desktop sidebars — the sidenav <aside> from Layout.tsx and the table-of-contents <aside> from components/Article.jsx — which are then removed by JS once the signal flips. The result is a visible flash/fade of both sidebars on every mobile refresh.
Related to #152 (same mobileLayout machinery), but a separate symptom: #152 is about the mid-hydration flip crashing hydration; this one is a pure FOUC that persists even with #152's suggested fix applied — deferring the initial flip to the window load event actually makes the flash longer, since the stale desktop layout stays up until load.
What the user sees (≤ 1100px, hard reload)
- Sidenav:
Layout.tsx SSRs the desktop branch of the Show (<aside class={styles.sidenav}>). At ≤ 1100px, Layout.module.css styles .sidenav as position: fixed; z-index: 51; width: min(20rem, 70dvw); background: …; animation: contentHide 300ms ease-out forwards — those rules are written for the mobile Dialog.Content, but they match the SSR'd desktop aside too. So the aside briefly renders as an open drawer overlaying the page and plays the contentHide slide-out animation on load. When mobileLayout() later flips, the Show swaps to the (closed) Dialog branch and the element disappears from the DOM.
- Table of contents:
Article.jsx renders it behind <Show when={!mobileLayout() && …}>, and Article.module.css has no mobile rule for .aside at all — so the TOC renders in-flow at phone widths and vanishes when the signal flips.
At > 1100px nothing changes visually, so the bug only shows on narrow windows — easy to miss in desktop-first dev.
Reproduction
@kobalte/solidbase 0.6.9 (also current pkg.pr.new — Layout.tsx / Article.jsx unchanged), default theme, any docs page with a sidebar + TOC.
- Emulate iPhone SE (375×667) or any window ≤ 1100px and hard-reload.
- Observe the sidenav drawer flash + slide out, and the TOC render then disappear.
Suggested fix
Hide the SSR'd desktop elements with the same media query CSS already keys off, so they never paint at mobile widths and the later JS removal is invisible:
/* Layout.module.css */
@media screen and (max-width: 1100px) {
aside.sidenav {
display: none;
}
}
/* Article.module.css */
@media screen and (max-width: 1100px) {
.aside {
display: none;
}
}
The aside element selector is enough to keep the mobile drawer working — the drawer is Dialog.Content, which renders a div. (Alternatively give the desktop aside its own class so the two stop sharing .sidenav rules — that sharing is also what causes the stray contentHide animation in step 1 above.)
A deeper fix — same note as in #152 — would be to render markup that's valid for both widths and gate visibility entirely in CSS rather than swapping Show branches on a client-only signal; the CSS above stays within the current architecture.
I've verified the equivalent CSS downstream (in our docs app, layered above the theme styles): at 375×667 both asides are display: none from first paint, the drawer still opens fine, and desktop is unaffected. Happy to open a PR.
Summary
Because
mobileLayout()isfalseduring SSR and only flips on the client (src/default-theme/globals.ts), the server always renders the desktop layout. On viewports ≤ 1100px, a full page load therefore paints both desktop sidebars — the sidenav<aside>fromLayout.tsxand the table-of-contents<aside>fromcomponents/Article.jsx— which are then removed by JS once the signal flips. The result is a visible flash/fade of both sidebars on every mobile refresh.Related to #152 (same
mobileLayoutmachinery), but a separate symptom: #152 is about the mid-hydration flip crashing hydration; this one is a pure FOUC that persists even with #152's suggested fix applied — deferring the initial flip to the windowloadevent actually makes the flash longer, since the stale desktop layout stays up untilload.What the user sees (≤ 1100px, hard reload)
Layout.tsxSSRs the desktop branch of theShow(<aside class={styles.sidenav}>). At ≤ 1100px,Layout.module.cssstyles.sidenavasposition: fixed; z-index: 51; width: min(20rem, 70dvw); background: …; animation: contentHide 300ms ease-out forwards— those rules are written for the mobileDialog.Content, but they match the SSR'd desktop aside too. So the aside briefly renders as an open drawer overlaying the page and plays thecontentHideslide-out animation on load. WhenmobileLayout()later flips, theShowswaps to the (closed) Dialog branch and the element disappears from the DOM.Article.jsxrenders it behind<Show when={!mobileLayout() && …}>, andArticle.module.csshas no mobile rule for.asideat all — so the TOC renders in-flow at phone widths and vanishes when the signal flips.At > 1100px nothing changes visually, so the bug only shows on narrow windows — easy to miss in desktop-first dev.
Reproduction
@kobalte/solidbase0.6.9 (also currentpkg.pr.new—Layout.tsx/Article.jsxunchanged), default theme, any docs page with a sidebar + TOC.Suggested fix
Hide the SSR'd desktop elements with the same media query CSS already keys off, so they never paint at mobile widths and the later JS removal is invisible:
The
asideelement selector is enough to keep the mobile drawer working — the drawer isDialog.Content, which renders adiv. (Alternatively give the desktop aside its own class so the two stop sharing.sidenavrules — that sharing is also what causes the straycontentHideanimation in step 1 above.)A deeper fix — same note as in #152 — would be to render markup that's valid for both widths and gate visibility entirely in CSS rather than swapping
Showbranches on a client-only signal; the CSS above stays within the current architecture.I've verified the equivalent CSS downstream (in our docs app, layered above the theme styles): at 375×667 both asides are
display: nonefrom first paint, the drawer still opens fine, and desktop is unaffected. Happy to open a PR.