Skip to content

Latest commit

 

History

History
163 lines (132 loc) · 11.1 KB

File metadata and controls

163 lines (132 loc) · 11.1 KB
raw true
title Layouts
description Seventeen intrinsic layouts that respond to their container, one attribute vocabulary, and the responsive model behind them.
nav_group Guides
nav_order 3

Layouts

Layouts are Yeti's grammar. A layout is a class that arranges its own children and owns the space between them. Children never carry their own margins; the layout that holds them decides the gap.

Three tools, in this order

Yeti has three ways to make a page respond to its context, and they apply in a fixed order.

Intrinsic layouts come first. The seventeen on this page arrange their children by reading their own width, not the viewport's. A sidebar drops to a stack when it runs low on room, wherever on the page it sits and whatever else is happening at the edge of the browser window. Reach for one of these before reaching for anything else.

Container queries come second. They let a single component change shape based on the width of the box that holds it rather than the window: a card that goes from one column to two once its own container is wide enough, in a sidebar or in a full-width section alike. These arrive in phase 3. A Yeti layout may query itself and change its children, never itself; the thresholds are the width tokens' defaults, written as numbers because a container condition cannot read a token. When something must change its own shape, put it in a container and query that.

Media queries come last, if at all. They read the viewport itself, or a visitor's stated preferences: color scheme, reduced motion, print. Those are the right job for a media query. Layout is not, because a rule that switches at a viewport width breaks the moment its element moves into a narrower or wider container than the one it was tuned for.

Nothing under src/layouts/ contains a media query, and the validator refuses one there.

Why columns has a threshold, not a breakpoint

columns takes a data-threshold, not a breakpoint, and the difference shows as soon as the same markup moves.

Put a columns inside a sidebar's content side, and it never sees the full viewport: the sidebar has already taken some of the width for itself. The columns still switch to rows at their own threshold, measured against their own container, so they can be stacked as rows while the page around them is wide open. Put the identical columns markup in a full-width section instead, and it switches at a much wider viewport, because its container is wider. Same markup, same threshold, two different viewport widths, because a threshold reads the box the element is in, not the window.

<div class="sidebar" data-side="start" data-width="xs">
	<nav aria-label="Section">
		<a href="#">Overview</a>
	</nav>
	<div class="columns" data-threshold="sm">
		<section>
			<h2>Plan</h2>
			<p>Three equal columns once the content column is wide enough.</p>
		</section>
		<section>
			<h2>Build</h2>
			<p>Two rows once it is not, regardless of the viewport.</p>
		</section>
	</div>
</div>

Four across, two by two, one is the same question with an extra step, and the answer is grid with data-fold, not a longer chain of thresholds: data-min="xs" data-columns="4" data-fold halves the column count as its own content box narrows — four, then two, then one — and never passes through three. The no-query alternative is two columns nested inside a third:

<div class="columns" data-threshold="md">
	<div class="columns" data-threshold="sm"><div>One</div><div>Two</div></div>
	<div class="columns" data-threshold="sm"><div>Three</div><div>Four</div></div>
</div>

The responsive guide takes this idea through the whole framework.

The vocabulary

Every layout is configured with a small set of data-* attributes, drawn from a shared list of values.

Attribute Values Read by
data-gap none, xs, sm, md, lg, xl, 2xl, 3xl, plus a fluid pair of any two of those (xs-sm, xs-md, xs-lg, xs-xl, xs-2xl, xs-3xl, sm-md, sm-lg, sm-xl, sm-2xl, sm-3xl, md-lg, md-xl, md-2xl, md-3xl, lg-xl, lg-2xl, lg-3xl, xl-2xl, xl-3xl, 2xl-3xl) stack, cluster, sidebar, columns, cover, grid, scroller, overlay, box, center, icon
data-align start, center, end, stretch, baseline stack, cluster, sidebar, columns, icon
data-justify start, center, end, between, around, evenly cluster, columns
data-threshold xs, sm, md, lg, xl, 2xl columns
data-width xs, sm, md, lg, xl, 2xl sidebar, scroller
data-min none, xs, sm, md, lg, xl, 2xl grid
data-max xs, sm, md, lg, xl, 2xl center
data-ratio 1/1, 4/3, 3/2, 16/9, 21/9 frame
data-columns 1, 2, 3, 4, 5, 6 grid
data-side start, end sidebar
data-limit 2, 3, 4, 5 columns — the first N share a row; every later child takes a full row
data-align-self start, center, end, stretch, baseline a child of layer — places that child vertically
data-justify-self start, center, end, stretch a child of layer — places that child horizontally
data-span 1, 2, 3, 4, 5, 6 a child of columns — that child's share of the row
data-ranks 2, 3, 4, 5, 6 grid — how many parts each child has; lines up neighbours' parts across the row
data-fold boolean grid — the column count halves as the grid narrows, instead of stepping down one at a time
data-note boolean a child of breakout — a margin note beside the paragraph it follows
data-alternate boolean timeline — entries take alternate sides of a centred rail once it is wide enough

For the sizing attributes the mapping rule is always the same: a value is a token suffix. data-gap="lg" reads --yeti-space-lg; data-width="sm" reads --yeti-width-sm. The attribute names the property to set; the value names the step on Yeti's scale to set it to. The rest — data-align, data-justify, data-ratio, data-columns, data-side, data-limit — name a behaviour rather than a token; attributes.css maps each value to the CSS keyword it means.

Gap alone also takes a fluid pair. data-gap="sm-lg" does not jump between the two: it runs from the sm stop at the narrow end of the viewport to the lg stop at the wide end, the same way the type scale itself is fluid. none never anchors a pair, so any smaller of the remaining seven sized stops can pair with any larger one, which is what makes twenty-one pairs out of seven.

The seventeen

  • stack: stacks its children vertically with one consistent gap between them.
  • cluster: lays its children out in a row that wraps, keeping one gap between them on both axes.
  • sidebar: places a fixed-width sidebar beside flexible content, and stacks them when the content would drop below half the width.
  • columns: lays its children out as equal columns when the container is wider than a threshold, and as rows when it is not.
  • cover: fills at least the viewport's height and centers one child vertically, with optional content pinned above and below it.
  • grid: fits as many equal columns as the container allows at a minimum width, up to an optional maximum count.
  • frame: holds one child in a fixed aspect ratio, cropping media to fill it and centering anything else.
  • scroller: lays its children out in a single row that scrolls horizontally.
  • overlay: holds one child over the rest, centered, without pushing anything around; the parent is the box it covers.
  • box: pads its content on all sides, with an optional border.
  • center: centers a column of content horizontally, up to a maximum width, with gutters on narrow screens.
  • icon: sizes an inline SVG to the surrounding text and aligns it with the text beside it.
  • masonry: packs items of uneven height into columns with no gaps under the short ones.
  • breakout: keeps its children in a centered reading column with gutters, and lets any child carrying data-bleed span the full width.
  • layer: stacks its children in one box, later ones on top, with the box as tall as the tallest of them.
  • container: makes its box the thing a container query measures, so what is inside can respond to its width instead of the viewport's.
  • timeline: lays its entries along a rail with a marker each, on one side, or on alternate sides of a centred rail when it is wide.

Composing

None of these layouts do much alone. Nest a few and they add up to a page.

A card: a bordered box holds a stack, which separates a cropped photo, a heading, and a paragraph at its own gap. The last child is a cluster of links carrying data-split, so it settles at the bottom of the card once the stack has more height than its content needs.

<div class="box" data-border>
	<div class="stack" data-gap="sm">
		<div class="frame" data-ratio="4/3">
			<img src="trail.jpg" alt="A mountain trail at dawn, cropped to four by three">
		</div>
		<h3>Weekend in the hills</h3>
		<p>Six miles, one summit, and a view worth the early start.</p>
		<nav class="cluster" data-gap="sm" data-split aria-label="Card actions">
			<a href="#">Read more</a>
			<a href="#">Share</a>
		</nav>
	</div>
</div>

The most common compositions ship as recipes, one class each, and every recipe page shows the same result built from primitives so nothing is hidden: shell (page skeleton with a sticky footer), media (a figure beside text), hero (a split opening band). dist/yeti.css includes the recipes; a project that composes its own can import the dist/css/ files it wants and leave dist/css/recipes/ out.

A recipe still nests inside a primitive like anything else: a grid of three media items, each a figure and a caption.

<ul class="grid" data-min="sm" data-columns="3" role="list">
	<li class="media" data-width="xs">
		<img src="trail.jpg" alt="A mountain trail at dawn">
		<div>
			<h3>Weekend in the hills</h3>
			<p>Six miles, one summit, and a view worth the early start.</p>
		</div>
	</li>
	<li class="media" data-width="xs">
		<img src="ada.jpg" alt="Portrait of Ada Lovelace">
		<div>
			<h3>Ada Lovelace</h3>
			<p>Wrote the first published algorithm, for Babbage's Analytical Engine.</p>
		</div>
	</li>
	<li class="media" data-width="xs">
		<img src="peak.jpg" alt="A snow ridge at first light">
		<div>
			<h3>First light on the ridge</h3>
			<p>The cloud broke just after dawn, for about ten minutes.</p>
		</div>
	</li>
</ul>

Coming from version 6

The biggest habit to unlearn is thinking in breakpoints at all: Foundation 6's grid classes each encoded a viewport width chosen ahead of time, while Yeti's layouts read the width of the box they are placed in. The full map from every version 6 class to its Yeti equivalent is in the migration guide.