Foundations

Layout

A layout has to work at more than one width. This page covers the model Apple uses to describe those widths, and the classes that respond to them.

Size classes

Apple describes every screen by a pair of size classes, horizontal and vertical, each either compact or regular. The horizontal one is what matters on the web.

ContextHorizontal size class
iPhone, portraitCompact
iPhone, landscapeCompact
iPad, full screenRegular
iPad, half or two-thirds Split ViewRegular
iPad, Slide Over or one-third Split ViewCompact

That last row is the reason this exists. An iPad app can be 320pt wide while running, because someone dragged another app alongside it. “iPad” is a range of widths, not a device.

So there is no iPad layout to design. There is what happens at regular width, what happens at compact, and the app moving between them without reloading.

The boundary is 768px, the web equivalent of Apple’s split around 507pt.

Only the horizontal size class is implemented. The vertical one has no useful web equivalent, since a browser has no home indicator or navigation bar to lay out around.

Split view

A split view is the structure iPad apps are built on: a sidebar for sections, optionally a supplementary list, and a content pane.

  • Ada LovelaceRe: analytical engine9:41 AM
  • Grace HopperCompiler notesYesterday
<div class="p-split-view">
<div class="p-split-view__sidebar">
  <nav class="p-sidebar p-sidebar-touch">…</nav>
</div>
<div class="p-split-view__content">
  <div class="p-pane-scroll">…</div>
</div>
</div>
ClassWhat it does
.p-split-viewTwo columns at regular width, one at compact
.p-split-view-tripleThree columns: sidebar, supplementary, content
.p-split-view-collapsedHides the sidebar at regular width, for the toggle every iPad app has
.p-split-view__sidebar320px, matching .p-sidebar-touch
.p-split-view__supplementary320px middle column
.p-split-view__contentFills the rest

At compact width the sidebar and supplementary panes are removed rather than squeezed, and the content pane fills. A collapsed split view has no navigation in it, so pair it with a tab bar or a back button.

Resize this page below 768px and the preview above drops to one column.

Adapting between the two

Several components come in a regular shape and a compact one. The pairs are:

RegularCompact
SidebarTab bar, or a pushed navigation stack
PopoverSheet
Split view, side by sideOne column with drill-down

.p-regular-only and .p-compact-only switch between them.

<nav class="p-sidebar p-sidebar-touch p-regular-only" aria-label="Sections">…</nav>
<nav class="p-tab-bar p-tab-bar-fixed p-compact-only" aria-label="Sections">…</nav>

Both are in the DOM and one is hidden, which costs some duplicate markup and needs no JavaScript. If you would rather render one or the other, the breakpoint is 768px and these classes are not required.

These utilities only ever hide. Nothing here sets an element visible, so a component keeps whatever display it defines for itself, whether that is block, flex, or grid. Showing by breakpoint would have to guess that value and would get it wrong for the tab bar.

Visible at regular width Visible at compact width

Containers and margins

iOS uses 16px content margins at compact width and 20px at regular. .p-container does that, and centres itself.

<div class="p-container">…</div>

.p-pane-scroll is the same margins on a column that scrolls independently, so a split view scrolls per pane rather than as one page.

Readable width

Body text stops being comfortable somewhere around 672px. On a 1024px iPad, a paragraph running the full width is a paragraph people lose their place in.

ClassUse
.p-readableCaps the text, so images and rules beside it can still go full width
.p-container-readableCaps the container, when everything inside should be measured
<article class="p-container">
  <img src="hero.jpg" alt="" />          <!-- full width -->
  <div class="p-readable">
    <p>…</p>                             <!-- capped at 672px -->
  </div>
</article>

Tokens

TokenDefault
--p-size-regular768px
--p-margin-compact16px
--p-margin-regular20px
--p-readable-width672px
--p-split-sidebar320px
--p-split-supplementary320px
--p-split-gap0px

--p-size-regular is documentation rather than a working value. A custom property cannot be used inside a media query, so the breakpoint is written literally in the queries. Changing one means changing the other.

Accessibility

An element hidden with .p-regular-only or .p-compact-only is display: none, so it leaves the accessibility tree as well as the page. That is what you want for a duplicate navigation: only one sidebar or tab bar is announced at a time.

Give the visible pair the same aria-label, so the section a person is in reads the same at either width.

A content pane with nothing selected is an empty state, not a blank column. On iPad the detail pane is visible before anything is chosen, and an empty column reads as a loading failure.

Not layout.css

layout.css is a v1 file that, despite the name, sets type sizes: .p-layout h1, .p-headline, .p-caption. It is unrelated to this page.

The classes here live in adaptive.css, imported by name:

import '@codedgar/puppertino/adaptive'

Both are in all three bundles, so if you are using one of those you already have this.

Copied