docs/solutions/developer-experience/2026-05-24-shadcn-app-shell-footer-visibility-needs-nearest-layout-group.md
Moving Plate's docs app toward the upstream shadcn shell means the layout owns the shared footer instead of individual pages. That exposed a footer visibility bug: docs pages should hide the layout footer, but the first implementation only copied the upstream body-level group-has selector and left the footer visible on /docs.
/ and /cn each rendered one footer as expected./docs rendered one visible footer even though the docs page contains data-slot="docs".visibleFooters: 1 for /docs where the expected value was 0.<footer className="group-has-[[data-slot=docs]]/body:hidden" />
In Plate's current app shell, that selector was too indirect to rely on as the only hiding rule during the migration.
Put the shell marker and footer in the same layout group:
<div
data-slot="layout"
className="group/layout relative z-10 flex min-h-svh flex-col bg-background"
>
<main className="flex min-h-0 flex-1 flex-col">{children}</main>
<SiteFooter />
</div>
Then hide the footer through the nearest layout group while keeping the body selector as a compatibility fallback:
<footer className="group-has-[[data-slot=docs]]/layout:hidden group-has-[[data-slot=docs]]/body:hidden" />
Browser smoke should assert both the shell and footer behavior:
const visibleFooters = await page.locator('footer:visible').count();
const shellCount = await page.locator('[data-slot="layout"]').count();
For Plate, / and /cn should have one visible footer, while /docs should have zero visible footers.
The layout group is the closest stable ancestor shared by the page content and the footer. When a descendant page renders data-slot="docs", Tailwind's group-has-.../layout variant can hide the sibling footer through that direct shell group.
The body-level selector is still harmless as a fallback, but it is not the only enforcement point. That matters during an app-shell migration, where the exact upstream body/provider stack is not fully adopted yet.