StickyAside
Main column scrolls; aside sticks under the header until the section ends. Pure CSS, server components, no scroll hijacking.
Wraps: position: sticky
Step 1
Install the package
Add the UI kit to your app.
Step 2
Import the styles
Pull in the kit CSS once in your globals.
Step 3
Render a control
Use a primitive in your page.
Rendered example
Use it
import { StickyAside, Stack, Text } from "@zedtech/zedkit-ui";
import { GlassSurface } from "@zedtech/zedkit-ui/marketing";
<StickyAside side="right" ratio="1/3">
<StickyAside.Main>
<Stack gap="lg">{/* numbered steps */}</Stack>
</StickyAside.Main>
<StickyAside.Aside asChild>
<GlassSurface padding="md">
<Text>Rendered example</Text>
</GlassSurface>
</StickyAside.Aside>
</StickyAside>Doc page with summary card
Write the guide
Steps stay in the main column for reading order.
import { Card, CardContent, StickyAside, Text } from "@zedtech/zedkit-ui";
// Aside on the left from the md breakpoint; stack main first on phones.
<StickyAside side="left" from="md" mobile="stack-after">
<StickyAside.Main>
<Text>Form fields…</Text>
</StickyAside.Main>
<StickyAside.Aside scrollable>
<Card>
<CardContent>Order summary</CardContent>
</Card>
</StickyAside.Aside>
</StickyAside>Kit not installed yet?
This ships on npm only; there is no copy-source brick for it.
Install
Terminal pnpm add @zedtech/zedkit-ui @fontsource-variable/inter @fontsource-variable/space-groteskLoad the styles
Tailwind does not scan node_modules, so the @source line points it at the kit's class names. The path is relative to this file.
src/app/globals.cssReplace the contents@import "tailwindcss"; @import "@zedtech/zedkit-ui/styles.css"; @source "../../node_modules/@zedtech/zedkit-ui/src/**/*.{ts,tsx}"; @source not "../../node_modules/@zedtech/zedkit-ui/src/**/*.test.*";Pick a look
Set data-identity="glass" on <html> so the finished app matches the site. Fresh app: replace src/app/layout.tsx. Existing app: merge, and keep ThemeProvider and other providers.
src/app/layout.tsxReplace the contentsimport "./globals.css"; import "./fonts"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en" data-identity="glass"> <body>{children}</body> </html> ); }Load the fonts
The Glass look uses Inter and Space Grotesk; these imports self-host them.
src/app/fonts.tsCreate the fileimport "@fontsource-variable/inter"; import "@fontsource-variable/space-grotesk";Import and render
src/app/page.tsxReplace the contentsimport { StickyAside, Stack, Text } from "@zedtech/zedkit-ui"; import { GlassSurface } from "@zedtech/zedkit-ui/marketing"; export default function Page() { return ( <main className="px-page-gutter py-section-sm"> <StickyAside side="right" ratio="1/3"> <StickyAside.Main> <Stack gap="lg">{/* numbered steps */}</Stack> </StickyAside.Main> <StickyAside.Aside asChild> <GlassSurface padding="md"> <Text>Rendered example</Text> </GlassSurface> </StickyAside.Aside> </StickyAside> </main> ); }
Package version 2.0.1.
Props
Prop
Type
Show inherited HTML attributes
Standard attributes from the underlying element. See MDN.
Prop
Type
Accessibility
DOM order is always main then aside; use mobile="before" only when the aside should appear first visually on phones. Put headings and steps in the main column; label specimen previews (for example with role="group" and aria-label). In-section links in the main column get scroll-margin-top from --zk-pin-top. With scrollable, the aside scrolls internally — keep focusable controls reachable.
Troubleshooting
Aside never sticks — check that `data-from` matches your breakpoint, the section is tall enough to scroll, and no ancestor between this root and the viewport uses `overflow: hidden|auto|scroll` or a transform. Aside hides under the site header — set `--zk-sticky-top` (or `--zk-pin-top` globally) to the header offset. Columns too narrow — pass `ratio="1/2"` or `--zk-sticky-cols`. Aside taller than the window — pass `scrollable` so it keeps sticky with an internal scroll area. Snap fights a long section — parent sections that contain `StickyAside` disable scroll-snap when `SectionScroll` is active.
title: Keep a side panel in view description: StickyAside for steps plus a sticky preview — pure CSS, no scroll hijacking.
Every UI snippet on this page is type-checked exactly as written.
Who this is for
You ship docs, onboarding, or pricing pages where one column scrolls (steps, form fields) and a short preview or summary should stay visible until the reader leaves the section.
Prerequisites
- React 19 and
@zedtech/zedkit-uistyles imported (see Theme an admin app). - A fixed or sticky site header — set
--zk-sticky-topon the layout root if the default--zk-pin-topis too short.
Steps
1. Wrap main and aside
Put scrolling content in StickyAside.Main and the panel that should stick in StickyAside.Aside. DOM order stays main then aside for screen readers.
Step 1
Install the package
Add the UI kit to your app.
Step 2
Import the styles
Pull in the kit CSS once in your globals.
Step 3
Render a control
Use a primitive in your page.
Rendered example
import { Button, Card, CardContent, Heading, StickyAside, Stack, Text } from "@zedtech/zedkit-ui";
<StickyAside side="right">
<StickyAside.Main>{/* steps */}</StickyAside.Main>
<StickyAside.Aside asChild>
<Card>
<CardContent>
<Button>Save</Button>
</CardContent>
</Card>
</StickyAside.Aside>
</StickyAside>Visible result: on wide viewports the card stays under the header while you scroll the steps; on phones the columns stack (aside after main by default).
2. Tune columns and mobile order
Use ratio="1/3" or side="left" when the sticky column should sit on the other side. Pass mobile="stack-before" when the preview should appear above the steps on narrow screens without changing DOM order.
Write the guide
Steps stay in the main column for reading order.
import { Card, CardContent, StickyAside, Text } from "@zedtech/zedkit-ui";
<StickyAside side="left" from="md" ratio="2/5" scrollable>
<StickyAside.Main>…</StickyAside.Main>
<StickyAside.Aside>
<Card>
<CardContent>
<Text variant="muted">Summary</Text>
</CardContent>
</Card>
</StickyAside.Aside>
</StickyAside>Final result
One section scrolls natively: the aside uses position: sticky inside the grid and stops at the section edge. No JavaScript runs in the layout primitive.
Troubleshooting
Aside never sticks — A parent with overflow: hidden, auto, or scroll, or a transform, sits between the layout and the viewport. Move StickyAside to the normal page flow or remove that ancestor constraint.
Panel hides under the header — Set className="[--zk-sticky-top:7rem]" (or your header offset) on StickyAside.
Tall summary clipped — Pass scrollable so the aside keeps sticky with an internal scroll area.
Next steps
- Home Get started uses the same pattern for install steps beside the rendered Save example.
- For cross-fading scenes pinned to the viewport, use Pin a stage and change scene as the page scrolls instead.