Page Layout
Cordwainer Elements includes three layout elements that nest inside each other to build a page:
-
<cw-section>is one band of the page. It caps and centers the content’s width, adds space above and below, and can give the band a background that runs edge to edge. -
<cw-grid>lays its children out in columns. -
<cw-stack>spaces its children vertically with one consistent gap.
The usual shape is a section that holds a grid, and a stack in each grid cell: the section sets the band, the grid divides it, and the stacks arrange what’s in each cell. A section can also hold a stack directly, as a hero or call to action does.
These are elements rather than utility classes on purpose. Elements appear in the Custom Elements Manifest, so tools that build pages from it, such as component generators for Drupal Canvas, can offer them. Their options are plain attributes with a fixed set of values, which are easy to discover and to validate.
A Full Page
Here’s a compact page made of four sections: a narrow hero, a grid of cards, an alt section
with a main column and an aside, and a labeled call to action. The flush sections drop their
side gutter on narrow screens so their backgrounds and content run edge to edge.
Build pages from a few elements
Sections, grids and stacks nest to cover most content pages, with no breakpoints to tune.
Components
Accessible, themeable components built on web standards.
Tokens
Color, spacing and typography tokens you can override per theme.
Frameworks
Works anywhere HTML does, with generated wrappers for React and a manifest for page builders that read one.
Main content
Placeholder text for the main content of the page. Replace it with an article, a product description or anything else that needs room to breathe.
More placeholder text, in a second paragraph, shows the stack spacing the paragraphs apart.
Aside
A short note that sits beside the main content and stacks below it on narrow screens.
Ready to start?
Add the library to your project in a few minutes.
<cw-section width="narrow" flush> <cw-stack gap="medium" align="center"> <h2 class="page-no-margin">Build pages from a few elements</h2> <p class="page-no-margin">Sections, grids and stacks nest to cover most content pages, with no breakpoints to tune.</p> <div class="page-buttons"> <cw-button variant="primary">Get started</cw-button> <cw-button>Learn more</cw-button> </div> </cw-stack> </cw-section> <cw-section> <cw-grid columns="3" min-column-width="12rem" class="page-cards"> <cw-card> <cw-stack gap="small"> <h3 class="page-no-margin">Components</h3> <p class="page-no-margin">Accessible, themeable components built on web standards.</p> <div><cw-button size="small">Browse</cw-button></div> </cw-stack> </cw-card> <cw-card> <cw-stack gap="small"> <h3 class="page-no-margin">Tokens</h3> <p class="page-no-margin">Color, spacing and typography tokens you can override per theme.</p> <div><cw-button size="small">Explore</cw-button></div> </cw-stack> </cw-card> <cw-card> <cw-stack gap="small"> <h3 class="page-no-margin">Frameworks</h3> <p class="page-no-margin"> Works anywhere HTML does, with generated wrappers for React and a manifest for page builders that read one. </p> <div><cw-button size="small">Read more</cw-button></div> </cw-stack> </cw-card> </cw-grid> </cw-section> <cw-section surface="alt" flush> <cw-grid columns="2-1" gap="large"> <cw-stack gap="small"> <h3 class="page-no-margin">Main content</h3> <p class="page-no-margin"> Placeholder text for the main content of the page. Replace it with an article, a product description or anything else that needs room to breathe. </p> <p class="page-no-margin"> More placeholder text, in a second paragraph, shows the stack spacing the paragraphs apart. </p> </cw-stack> <cw-stack gap="small"> <h4 class="page-no-margin">Aside</h4> <p class="page-no-margin">A short note that sits beside the main content and stacks below it on narrow screens.</p> </cw-stack> </cw-grid> </cw-section> <cw-section surface="inverse" width="narrow" label="Call to action"> <cw-stack gap="small" align="center"> <h3 class="page-no-margin">Ready to start?</h3> <p class="page-no-margin">Add the library to your project in a few minutes.</p> <div><cw-button variant="primary">Install now</cw-button></div> </cw-stack> </cw-section> <style> .page-no-margin { margin: 0; } .page-buttons { display: flex; flex-wrap: wrap; gap: var(--cw-spacing-small); justify-content: center; } .page-cards cw-card::part(base) { height: 100%; } </style>
import CwButton from '@cordwainer/cw-elements/dist/react/button'; import CwCard from '@cordwainer/cw-elements/dist/react/card'; import CwGrid from '@cordwainer/cw-elements/dist/react/grid'; import CwSection from '@cordwainer/cw-elements/dist/react/section'; import CwStack from '@cordwainer/cw-elements/dist/react/stack'; const css = ` .page-no-margin { margin: 0; } .page-buttons { display: flex; flex-wrap: wrap; gap: var(--cw-spacing-small); justify-content: center; } .page-cards cw-card::part(base) { height: 100%; } `; const App = () => ( <> <CwSection width="narrow" flush> <CwStack gap="medium" align="center"> <h2 className="page-no-margin">Build pages from a few elements</h2> <p className="page-no-margin"> Sections, grids and stacks nest to cover most content pages, with no breakpoints to tune. </p> <div className="page-buttons"> <CwButton variant="primary">Get started</CwButton> <CwButton>Learn more</CwButton> </div> </CwStack> </CwSection> <CwSection> <CwGrid columns="3" minColumnWidth="12rem" className="page-cards"> <CwCard> <CwStack gap="small"> <h3 className="page-no-margin">Components</h3> <p className="page-no-margin">Accessible, themeable components built on web standards.</p> <div> <CwButton size="small">Browse</CwButton> </div> </CwStack> </CwCard> <CwCard> <CwStack gap="small"> <h3 className="page-no-margin">Tokens</h3> <p className="page-no-margin">Color, spacing and typography tokens you can override per theme.</p> <div> <CwButton size="small">Explore</CwButton> </div> </CwStack> </CwCard> <CwCard> <CwStack gap="small"> <h3 className="page-no-margin">Frameworks</h3> <p className="page-no-margin"> Works anywhere HTML does, with generated wrappers for React and a manifest for page builders that read one. </p> <div> <CwButton size="small">Read more</CwButton> </div> </CwStack> </CwCard> </CwGrid> </CwSection> <CwSection surface="alt" flush> <CwGrid columns="2-1" gap="large"> <CwStack gap="small"> <h3 className="page-no-margin">Main content</h3> <p className="page-no-margin"> Placeholder text for the main content of the page. Replace it with an article, a product description or anything else that needs room to breathe. </p> <p className="page-no-margin"> More placeholder text, in a second paragraph, shows the stack spacing the paragraphs apart. </p> </CwStack> <CwStack gap="small"> <h4 className="page-no-margin">Aside</h4> <p className="page-no-margin"> A short note that sits beside the main content and stacks below it on narrow screens. </p> </CwStack> </CwGrid> </CwSection> <CwSection surface="inverse" width="narrow" label="Call to action"> <CwStack gap="small" align="center"> <h3 className="page-no-margin">Ready to start?</h3> <p className="page-no-margin">Add the library to your project in a few minutes.</p> <div> <CwButton variant="primary">Install now</CwButton> </div> </CwStack> </CwSection> <style>{css}</style> </> );
Leave horizontal padding to your sections. If the container around them has its own side padding, a
section’s background stops short of the screen edge and flush can’t reach it. Give the page
container no side padding and let each section’s gutter provide it.
Choosing a Section Width
Use width to cap how wide a section’s content can grow. The background always spans the full
width, and the content is centered.
-
narrowis a comfortable reading measure. Use it for articles, heroes and calls to action. defaultsuits most content, including grids of cards.widegives dense layouts such as dashboards or galleries more room.fullremoves the cap, for content that should reach both edges.
The caps come from the --cw-layout-width-* layout tokens, so you
can change them for your whole site in one place. Use padding-block and gutter to
tune a section’s spacing, and surface to pick a background: default,
alt, primary or inverse.
Columns Without Breakpoints
A grid never looks at the screen size. Instead, columns="3" means “at most three columns.” When
a column would get narrower than min-column-width (15rem by default), the grid
drops a column on its own, going from three to two to one as it narrows. Lower
min-column-width for small items, or raise it for items that need room.
Use a ratio split such as 2-1, 1-2 or 1-2-1 when columns have
different roles, like main content beside an aside. Ratio columns keep their proportions until the grid is
narrower than --cw-grid-stack-below (40rem by default), then stack into a single
column. Because stacking never reorders anything, put the most important content first in the source.
Stacks in Cells and Cards
Put a <cw-stack> in each grid cell or card to space its heading, text and button
consistently. A stack adds its gap on top of its children’s own margins, so remove the margins from headings
and paragraphs when the gap should be the only space between them, as the example above does.
Grid cells stretch to the height of the tallest one in the row by default (align="stretch"), so
cards with different amounts of text still line up. A card needs height: 100% on its
base part to fill its cell, as in the example above. Set align="start" on the grid
to let each cell keep its natural height instead.
Spacing by Token Name
gap, padding-block and gutter take the name of a
spacing token, from 3x-small to 4x-large, instead of
a length. That keeps spacing consistent across a page and lets a theme rescale it. A value that isn’t a
token name falls back to the default and logs a warning in the console, so a typo never silently changes the
layout. The width caps and collapse thresholds are covered by the
layout tokens.
Responding to the Container
Each layout element responds to its own width, not the viewport’s. A grid in a narrow sidebar collapses to
fewer columns even on a wide screen, and flush is relative to the section’s width too. They’re
block elements that take the full width of their container, so use them in block contexts, such as page
bands, a card’s body or a stretched flex item, rather than inline or in a flex row where they’d shrink to
fit.
<div class="layout-sidebar"> <cw-grid columns="3" min-column-width="6rem"> <div class="layout-box">One</div> <div class="layout-box">Two</div> <div class="layout-box">Three</div> </cw-grid> </div> <style> .layout-sidebar { width: 14rem; max-width: 100%; padding: var(--cw-spacing-small); border: 1px dashed var(--cw-color-neutral-300); } .layout-box { padding: var(--cw-spacing-small); border-radius: var(--cw-border-radius-medium); background: var(--cw-color-primary-100); color: var(--cw-color-primary-800); font-weight: var(--cw-font-weight-semibold); } </style>
import CwGrid from '@cordwainer/cw-elements/dist/react/grid'; const css = ` .layout-sidebar { width: 14rem; max-width: 100%; padding: var(--cw-spacing-small); border: 1px dashed var(--cw-color-neutral-300); } .layout-box { padding: var(--cw-spacing-small); border-radius: var(--cw-border-radius-medium); background: var(--cw-color-primary-100); color: var(--cw-color-primary-800); font-weight: var(--cw-font-weight-semibold); } `; const App = () => ( <> <div className="layout-sidebar"> <CwGrid columns="3" minColumnWidth="6rem"> <div className="layout-box">One</div> <div className="layout-box">Two</div> <div className="layout-box">Three</div> </CwGrid> </div> <style>{css}</style> </> );
Accessibility
Layout never changes reading order. Grids and stacks collapse without reordering their children, so source order is what a screen reader, a keyboard user and a narrow screen all get. Write the content in the order it should be read.
Give a section a label to make it a named region landmark, so screen reader users can list a
page’s regions and jump between them. Use it sparingly, on a few substantial sections with a clear purpose
such as the call to action above. A section without a label is a plain wrapper and adds no
landmark, which keeps the page from filling up with unnamed regions.
Learn More
-
Section: width, padding, gutter,
flush, surfaces and landmarks. - Grid: equal columns, ratio splits, alignment and spanning.
- Stack: gap and alignment.
- Layout tokens and spacing tokens.