Skip to contentAsheeUIAsheeUI

Page

The application shell and its three parts, a header that can stick, a content area that fills the height, and a footer that closes the page.

A page is a shell and three parts, and every part is usable without the others. Page renders a full-height column that owns the theme background; PageHeader keeps the title and the actions within reach; PageContent is the main landmark that fills the height between them; PageFooter closes the page. None of them adds navigation, data or a breakpoint rule, so a page is the shell plus whatever you put inside it.

It is the only layout that is a whole document. Section is a band inside one and Container is a width around something, neither claims the viewport height, and neither is a landmark.

Usage

import {
  Card,
  Grid,
  Page,
  PageContent,
  PageFooter,
  PageHeader,
  Typography,
} from "@asheeui/web";

<Page>
  <PageHeader>
    <Typography role="heading-lg">Invoices</Typography>
  </PageHeader>
  <PageContent spacing="lg">
    <Grid columns={1} columnsMd={3} gap="md">
      <Card title="Sent" description="This month">348</Card>
    </Grid>
  </PageContent>
  <PageFooter>
    <Typography role="caption" tone="muted">Ashee Softworks</Typography>
  </PageFooter>
</Page>;

The four parts answer different questions, whether the part holds its content to a width, how wide that width may grow, what rhythm the content area claims, whether the bar stays put, and whether a separator is drawn, and each accepts only the answers it acts on. components.page states all five once, and a part reads the ones it owns.

The options belong to the part

Page accepts none of the five. That is the rule rather than an omission: a shell has no bar to keep at the top, no content rhythm to claim and no footer to separate, so every option belongs to the part that can act on it, and the types say so, rather than swallowing an option that would do nothing.

The consequence is that a misdirected option fails to compile instead of passing quietly. <PageHeader sticky> and <PageContent spacing="lg"> are options of the part that acts on them; <Page sticky>, <PageHeader spacing>, <PageContent sticky> and <PageFooter spacing> are in nobody's type. A configured components.page.spacing still reaches the content area, which is the part that owns the rhythm.

Stickiness belongs to the header, and so does the rule under it. The divider above the footer is a separate statement, which is why a page can keep a separator under the bar and none over the footer. Scroll the page with Sticky on and the bar holds the top of the window; the bar renders translucently over whatever passes beneath it, so the content is still legible as it goes.

Width and rhythm

contained with containerSize decides whether a part wraps its content in a Container, and the header, the content area and the footer each decide for themselves. A header that spans the window above a content area that is capped is a normal arrangement, and so is its opposite.

The content area is also the part that fills. The shell claims the height of the window and the header and the footer take their natural height, so PageContent takes what is left: a short page still ends with the footer at the bottom of the window instead of just under its last card.

spacing is the vertical padding the content area claims above its first block and below its last. It is padding rather than the gap between the blocks themselves: the area is a column and spacing moves it, while the rhythm between two cards in it is a Stack's. Keeping the two apart is what lets a page hold a rhythm in configuration while a single screen tightens its own.

Pick a token and the content moves under the bar. Turn Contained off and the column grows to the window width while the header keeps its own answer, the two parts are not coupled.

Props

The shell takes none of the five options and applies none of them, as described above: as is the only thing it acts on. The default is a div, and a page whose shell is the document body can render main here instead, though main is usually PageContent, which is where the landmark belongs.

The header takes, and acts on, sticky, contained, containerSize and divider. spacing is not among them: a bar has no vertical rhythm of its own, so the option is not in the type rather than accepted and ignored, <PageHeader spacing="xl"> is a compile error, and a configured components.page.spacing still reaches PageContent.

The content area takes, and acts on, spacing, contained and containerSize. sticky and divider belong to the parts above and below it and are not in its type. className styles the element the part renders, the main landmark by default, while innerClassName styles the column inside it, which is where the vertical padding and the flex layout live; that is the one to reach for when a screen wants its content laid out differently without losing the landmark's own classes.

The footer takes, and acts on, contained, containerSize and divider. Neither sticky nor spacing is in its type: a bar that followed the reader at the top would not be a footer, and its rhythm is its own padding rather than the content area's.

Every value here is registered by the component rather than left to the application: components.page states contained: true, containerSize: "lg", spacing: "md", sticky: true and divider: true, and the four parts read that one entry. So an application that wants a full-bleed shell, a wider column and a tighter rhythm states it once:

<AsheeUIProvider
  config={{
    components: {
      page: { contained: false, containerSize: "xl", spacing: "lg", sticky: false },
    },
  }}>
  <App />
</AsheeUIProvider>

There is no variant, radius or colour on the page: a shell is structure, and anything it paints beyond the theme background is a Section's job.

Accessibility

  • PageHeader renders a <header>, which is the banner landmark while it is not inside an article, aside, main, nav or section, which is the case in the shell, because Page is a div. as="div" removes the landmark and keeps the bar.
  • PageContent renders a <main>: the landmark a screen reader jumps to, and the target of a skip link. There is one per document, so a nested page uses as="section" or as="div" for the inner one.
  • PageFooter renders a <footer>, which is the contentinfo landmark under the same nesting rule as the header.
  • Stickiness is a positioning declaration, not a second copy of the bar: nothing leaves the document, and the tab order is the order you wrote.
  • The divider is a border, and no state is carried by it alone, a page whose header and content must be told apart for a reader who cannot see the rule relies on the headings, which is why each part takes a real element through as rather than a styled div.

The spacing scale, the width tokens and as are shared by every layout; see Layout and spacing, and for how the shells assemble a page from these parts, Page compositions.