Page
Page composes a full application page: optional left and right gutters around a center content area, plus a footer. The gutters stay fixed while the center content scrolls with the page scrollbar; once you scroll past the page body the footer appears and everything scrolls up together. A landing variant swaps the gutters for stacked full-viewport PageSections.
// apps/docs/src/components/examples/page/default/react.tsx
import { Page, PageContent, PageFooter } from '@cloudvoyant/helical-react';
export default function ReactPageDefault() {
return (
<Page>
<PageContent className="flex items-center justify-center">
<div className="p-8 text-center">
<h1 className="text-xl font-semibold">Page content</h1>
<p className="mt-2 text-sm text-muted-foreground">The page body is at least one viewport tall.</p>
</div>
</PageContent>
<PageFooter className="border-t border-border bg-muted/40">
<p className="px-4 py-3 text-sm text-muted-foreground">Footer</p>
</PageFooter>
</Page>
);
}<!-- apps/docs/src/components/examples/page/default/svelte.svelte -->
<script lang="ts">
import { Page, PageContent, PageFooter } from '@cloudvoyant/helical-svelte';
</script>
<Page>
<PageContent class="flex items-center justify-center">
<div class="p-8 text-center">
<h1 class="text-xl font-semibold">Page content</h1>
<p class="mt-2 text-sm text-muted-foreground">The page body is at least one viewport tall.</p>
</div>
</PageContent>
<PageFooter class="border-t border-border bg-muted/40">
<p class="px-4 py-3 text-sm text-muted-foreground">Footer</p>
</PageFooter>
</Page>Examples
Footer
Gutter Scroll
// apps/docs/src/components/examples/page/gutter-scroll/react.tsx
import { Page, PageGutter, PageContent, Scroll } from '@cloudvoyant/helical-react';
const tags = Array.from({ length: 60 }).map((_, i, a) => `v1.2.0-beta.${a.length - i}`);
export default function ReactPageGutterScroll() {
return (
<Page>
<PageGutter side="left">
<Scroll variant="hidden" className="w-full border-r border-border bg-muted/40">
<div>
<p className="px-2 pb-2 text-xs font-semibold text-muted-foreground">Releases</p>
{tags.map((tag) => (
<div key={tag} className="border-t border-border px-2 py-1.5 text-sm text-muted-foreground">
{tag}
</div>
))}
</div>
</Scroll>
</PageGutter>
<PageContent className="flex items-center justify-center">
<div className="p-8 text-center">
<h1 className="text-xl font-semibold">Scrolling gutters</h1>
<p className="mt-2 text-sm text-muted-foreground">The gutter content scrolls independently via Scroll.</p>
</div>
</PageContent>
</Page>
);
}<!-- apps/docs/src/components/examples/page/gutter-scroll/svelte.svelte -->
<script lang="ts">
import { Page, PageGutter, PageContent, Scroll } from '@cloudvoyant/helical-svelte';
const tags = Array.from({ length: 60 }, (_, i) => `v1.2.0-beta.${60 - i}`);
</script>
<Page>
<PageGutter side="left">
<Scroll variant="hidden" class="w-full border-r border-border bg-muted/40">
<div>
<p class="px-2 pb-2 text-xs font-semibold text-muted-foreground">Releases</p>
{#each tags as tag (tag)}
<div class="border-t border-border px-2 py-1.5 text-sm text-muted-foreground">{tag}</div>
{/each}
</div>
</Scroll>
</PageGutter>
<PageContent class="flex items-center justify-center">
<div class="p-8 text-center">
<h1 class="text-xl font-semibold">Scrolling gutters</h1>
<p class="mt-2 text-sm text-muted-foreground">The gutter content scrolls independently via Scroll.</p>
</div>
</PageContent>
</Page>Gutters
// apps/docs/src/components/examples/page/gutters/react.tsx
import { Page, PageGutter, PageContent, PageFooter, Col } from '@cloudvoyant/helical-react';
export default function ReactPageGutters() {
return (
<Page>
<PageGutter side="left">
<Col className="w-full gap-2 border-r border-border bg-muted/40 p-3">
<p className="text-xs font-semibold text-muted-foreground">Left gutter</p>
<p className="text-sm">Sticky, non-scrolling.</p>
</Col>
</PageGutter>
<PageContent className="flex items-center justify-center">
<div className="p-8 text-center">
<h1 className="text-xl font-semibold">Center content</h1>
<p className="mt-2 text-sm text-muted-foreground">
Scroll down — the gutters stay fixed until the footer appears.
</p>
<div className="mx-auto mt-8 max-w-md space-y-4 text-left text-sm text-muted-foreground">
{Array.from({ length: 20 }).map((_, i) => (
<p key={i}>
Paragraph {i + 1} — enough content to make the page body taller than the viewport, so the sticky gutters
stay pinned while the center column scrolls.
</p>
))}
</div>
</div>
</PageContent>
<PageGutter side="right" align="center">
<Col className="w-full gap-2 border-l border-border bg-muted/40 p-3">
<p className="text-xs font-semibold text-muted-foreground">Right gutter</p>
<p className="text-sm">Aligned center.</p>
</Col>
</PageGutter>
<PageFooter className="border-t border-border bg-muted/40">
<p className="px-4 py-3 text-sm text-muted-foreground">Footer</p>
</PageFooter>
</Page>
);
}<!-- apps/docs/src/components/examples/page/gutters/svelte.svelte -->
<script lang="ts">
import { Page, PageGutter, PageContent, PageFooter, Col } from '@cloudvoyant/helical-svelte';
</script>
<Page>
<PageGutter side="left">
<Col class="w-full gap-2 border-r border-border bg-muted/40 p-3">
<p class="text-xs font-semibold text-muted-foreground">Left gutter</p>
<p class="text-sm">Sticky, non-scrolling.</p>
</Col>
</PageGutter>
<PageContent class="flex items-center justify-center">
<div class="p-8 text-center">
<h1 class="text-xl font-semibold">Center content</h1>
<p class="mt-2 text-sm text-muted-foreground">Scroll down — the gutters stay fixed until the footer appears.</p>
<div class="mx-auto mt-8 max-w-md space-y-4 text-left text-sm text-muted-foreground">
{#each Array.from({ length: 20 }) as _, i (i)}
<p>Paragraph {i + 1} — enough content to make the page body taller than the viewport, so the sticky gutters stay pinned while the center column scrolls.</p>
{/each}
</div>
</div>
</PageContent>
<PageGutter side="right" align="center">
<Col class="w-full gap-2 border-l border-border bg-muted/40 p-3">
<p class="text-xs font-semibold text-muted-foreground">Right gutter</p>
<p class="text-sm">Aligned center.</p>
</Col>
</PageGutter>
<PageFooter class="border-t border-border bg-muted/40">
<p class="px-4 py-3 text-sm text-muted-foreground">Footer</p>
</PageFooter>
</Page>Landing
// apps/docs/src/components/examples/page/landing/react.tsx
import { Page, PageSection, PageFooter } from '@cloudvoyant/helical-react';
export default function ReactPageLanding() {
return (
<Page variant="landing">
<PageSection className="flex items-center justify-center bg-muted/40">
<div className="p-8 text-center">
<h1 className="text-2xl font-semibold">Hero section</h1>
<p className="mt-2 text-sm text-muted-foreground">Each section fills the viewport.</p>
</div>
</PageSection>
<PageSection className="flex items-center justify-center">
<div className="p-8 text-center">
<h1 className="text-xl font-semibold">Features section</h1>
</div>
</PageSection>
<PageFooter className="border-t border-border bg-muted/40">
<p className="px-4 py-3 text-sm text-muted-foreground">Footer</p>
</PageFooter>
</Page>
);
}<!-- apps/docs/src/components/examples/page/landing/svelte.svelte -->
<script lang="ts">
import { Page, PageSection, PageFooter } from '@cloudvoyant/helical-svelte';
</script>
<Page variant="landing">
<PageSection class="flex items-center justify-center bg-muted/40">
<div class="p-8 text-center">
<h1 class="text-2xl font-semibold">Hero section</h1>
<p class="mt-2 text-sm text-muted-foreground">Each section fills the viewport.</p>
</div>
</PageSection>
<PageSection class="flex items-center justify-center">
<div class="p-8 text-center">
<h1 class="text-xl font-semibold">Features section</h1>
</div>
</PageSection>
<PageFooter class="border-t border-border bg-muted/40">
<p class="px-4 py-3 text-sm text-muted-foreground">Footer</p>
</PageFooter>
</Page>API Reference
Page
Renders a <div>. The default variant is a CSS grid (left content right / footer footer footer); the landing variant is a flex column.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant |
'default' | 'landing' |
'default' |
default lays out gutters + content + footer; landing stacks PageSections then the footer. |
PageGutter
A sticky, non-scrolling gutter that renders a <div> so it never creates duplicate landmarks — add role="complementary" and an aria-label when a gutter holds meaningful content. It shrinks at mid-width screens (md:w-56) and collapses to a thin p-0.5 sliver below md, where its content is hidden (hidden md:flex on the inner content wrapper). Vertical alignment of the gutter content is controlled by align.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
side |
'left' | 'right' |
'left' |
Which grid area the gutter occupies. |
align |
'start' | 'center' | 'end' |
'start' |
Vertical alignment of the gutter content. |
contentClassName / contentClass |
string |
— | Extra classes for the inner content wrapper (e.g. to change the hide-below-md behavior). |
PageContent
Renders <main> in the center grid area.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
className / class |
string |
— | Merged via cn with pageContentBase (min-w-0 [grid-area:content]). |
PageFooter
Renders <footer> in the footer grid area (default variant) or after the sections (landing).
Props
| Prop | Type | Default | Description |
|---|---|---|---|
className / class |
string |
— | Merged via cn with pageFooterBase (w-full shrink-0 [grid-area:footer]). |
PageSection
A full-viewport section for the landing variant. Renders <section>.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
className / class |
string |
— | Merged via cn with pageSectionBase (flex min-h-svh w-full flex-col). |
Accessibility
PageContent renders a native <main>, PageFooter a native <footer>, and PageSection a native <section>, so landmarks come from the elements themselves. A document should contain one <main> and one <footer> — these parts are single-use per document (the demos on this page render several for illustration, which is why they sit inside the docs shell’s own landmark). PageGutter renders a plain <div> (never a second complementary landmark) — add role="complementary" with an aria-label when a gutter carries meaningful content. The gutter scroll behavior is native position: sticky, so no JavaScript or keyboard handling is required.