Marigold
v18.0.0-beta.3
Marigold
v18.0.0-beta.3
Component Principles
Accessibilityupdated
Token Overviewnew
Typographyalpha
Elevationupdated
Layoutsupdated
Responsive Designnew
Iconographynew
Form Fields
Spacingalpha
Foundations

Layouts

How layout and composition are handled.

When you build a page in Marigold, it lives inside the AppShell, the fixed frame that every page shares, in a Page that holds the content. Inside that frame, you divide the content into Panels and arrange it with primitives. Rooms, then furniture.

AppShellSidebarTopbarPanel

Panel

Panel is a titled surface that renders a visible page section with a title, body, and optional actions. It is the canonical way to divide a page in Marigold.

Most pages are made of one or more Panels. A small view like a settings tab is often a single Panel. A denser view like a dashboard or a detail page is typically several Panels, each one naming a self-contained part of the page. Reaching for Panel is the default. Reaching for something else should be deliberate.

Panel also works outside the AppShell. A login form wrapped in a Panel gives the same titled surface you would use inside an app, so the opinion "use a Panel" holds on pre-auth screens too.

Panel owns the surface, the title, and the body wrapper, not the arrangement inside it. Content layout is the primitives' job, and they work the same way wherever you use them. A common shape is a Stack of fields with an Inline of buttons at the bottom, but any primitive composition is valid.

See the Panel component docs for props, variants, and sizing.

Layout primitives

Layout primitives are invisible positioning components that arrange and space whatever content is placed inside them. Stack flows content vertically, Inline flows horizontally, Columns splits proportionally, and so on.

Without them, web layout is a combination of <div> elements with flex or grid utility classes. That works for a single container, but it scales badly. The moment a section needs a vertical stack containing a row of buttons, wrappers start nesting and each one carries its own class string:

<div className="flex flex-col gap-4">
  <h3>Title</h3>
  <p>Body text…</p>
  <div className="flex justify-end gap-2">
    <button>Cancel</button>
    <button>Save</button>
  </div>
</div>

Nothing is broken here, but reading it means parsing every class string to reconstruct the intent. Repeat the pattern across a codebase and layout decisions end up scattered through utility classes, reinvented slightly differently on every page. Marigold replaces this with named layout components:

<Stack space="regular">
  <h3>Title</h3>
  <p>Body text…</p>
  <Inline space="small" alignX="right">
    <button>Cancel</button>
    <button>Save</button>
  </Inline>
</Stack>

Each wrapper states what it is rather than how it is configured. You can scan the tree and see the layout flow without reading a single class name. This gives you:

  • Named intent. The JSX tells you the layout pattern (Stack, Inline, Columns) instead of encoding it in class strings the reader has to decode.
  • One canonical pattern. The same shape looks the same everywhere. Teams don't reinvent a vertical stack in four slightly different ways.
  • Composability. A Button drops into any layout without carrying layout opinions of its own.

For these benefits to hold, components must never set their own outer margin. Adding margin to a component causes it to affect, or be affected by, elements outside its boundary, breaking the modularity and composition assumptions that make components reusable in the first place.

Whitespace, positioning, and arrangement are always the responsibility of a parent. A button does not know what sits next to it. A Stack decides the gap between the button and its siblings. This keeps components portable and whitespace predictable across pages.

regular
regular

Spacing between elements is expressed with semantic tokens like regular or group. See the Spacing page for the full vocabulary.

Choosing the right primitive

Most layouts can be built from more than one combination of primitives. Each primitive, though, is built around a specific situation, and reaching for the one that fits most directly keeps pages consistent across a codebase. The table names that situation for each primitive, with a typical example.

ComponentReach for it when…Example
Asidetwo elements pair side-by-side where one is fixed-width and the other fills the restmedia-object: thumbnail + details Stack
Aspecta media element must keep a specific aspect ratio regardless of container widthvideo or iframe embeds, image thumbnails inside Tiles
Centercontent needs horizontal centering, optionally capped at a max-widthauth screens, empty-state wrappers
Columnsa fixed number of regions need explicit proportional widths and should collapse to one column below a breakpointlabel + input rows, sidebar + main page splits
Containercontent needs a max-width optimized for reading, so long-form text keeps a comfortable line lengtharticle bodies, docs pages, editorial prose
Gridspecific children must occupy or span named zones, making the layout structure itself the contractbento dashboards, detail pages with named regions
Inlinemultiple elements need to sit side-by-side and wrap to a new line when space runs outaction row of buttons, often with a Split between groups
Insetcontent inside a sealed surface needs explicit, controllable interior paddinginterior padding for Tray, Panel, and other surfaces
Scrollablecontent may overflow its bounded height or width and needs controlled scrollingfixed-height table with a sticky header
Splitsiblings inside an existing Inline or Stack need to be pushed to opposite endsinside Inline, between a title and its actions
Stackmultiple elements need to flow top-to-bottom with a consistent gapskeleton of most forms and page sections
Tilesa homogeneous set of items of unknown count should fill the row at a minimum child width and reflow automaticallycard grids, dashboard metric grids

Reach for a layout component only with two or more children

A layout component's whole job is to arrange elements in relation to one another. Just like Inline and Stack express the named intent "lay these out horizontally" or "stack these vertically", that intent only exists once there is more than one thing to arrange. A layout component therefore earns its place only when it arranges two or more children. With a single child there is nothing to arrange, so render the element directly instead of wrapping it.

// Don't: a layout component around a single child adds no intent
<Stack>
  <Card>…</Card>
</Stack>

// Do: render the element directly
<Card>…</Card>

This keeps the JSX tree honest: every layout component in the markup signals a real arrangement decision, not incidental nesting.

Padding and centering wrappers are different

Components whose intent is to wrap a single element — Inset for padding and Center for centering — legitimately take one child. The two-or-more rule applies to arrangement components like Stack, Inline, Columns, Grid and Tiles, where a single child carries no arrangement.

Table is for data, not layout

Table must never be used as a layout grid. It expresses tabular data with rows and columns of related values, not a way to align arbitrary elements. For arranging UI, reach for a layout component instead: form fields belong in Stack, Inline or Columns, never in Table.Cell, since putting inputs in table cells to line them up breaks the named-intent model and harms accessibility. See the Table component for its legitimate use, and the Forms pattern for laying out fields.

Last update: 11 days ago

Elevation

How surfaces and shadows work together to create visual depth and hierarchy

Responsive Design

How Marigold handles breakpoints, responsive values, and the minimum supported screen width.

On this page

PanelLayout primitivesChoosing the right primitiveReach for a layout component only with two or more children