# Layout

> Part of mCSS (mcss.dev). Rendered page: https://mcss.dev/docs/layout

Everything you need to lay out a page: a [grid](#grid), a content [wrapper](#wrap), full-page [layout scaffolds](#layouts), and the [media queries](#media-queries) they respond to.

## Grid

<section class="docs_section prose">

<div class="docs_oversizedTable">

| File name         | Source            |
| ----------------- | ----------------- |
| `global.grid.css` | [Github][srcGrid] |

</div>

[srcGrid]: https://github.com/minimaldesign/mCSS/blob/main/src/styles/framework/global.grid.css

A lightweight, flexible grid system. It uses the [CSS grid property](https://developer.mozilla.org/en-US/docs/Web/CSS/grid) under the hood, but you set up your layouts directly in your HTML. (The implementation is heavily inspired by [Raster](https://github.com/rsms/raster).)

### Syntax

It's composed of 2 classes: `.grid` for the container, and `.grid_item` for each grid item.

The default grid gap is defined as a custom property in [`theme.default.ui.css`](/docs/default-theme#ui-tokens) and is responsive: `16px` (`--sm1`) on small screens, stepping up to `24px` (`--sm3`) at the `--md` breakpoint (768px). You can override `--grid-column-gap` and `--grid-row-gap` globally in your own theme, or on a specific `.grid` element. A `grid-flush` modifier is available if you don't want any gap.

Because `.grid` reads its gaps from those two custom properties, overriding a specific grid is just a matter of setting them on the element. Add a class of your own:

```css
.photoWall {
  --grid-column-gap: var(--xs2);
  --grid-row-gap: var(--xs2);
}
```

```html
<ul class="grid photoWall" col="2" col-lg="4">
  ...
</ul>
```

You define the number of columns of your grid with a custom `col` attribute and the position/width of your grid items with the custom `span` attribute.

The `span` property syntax follows these 5 patterns:

- `row`: the grid_item spans a full row.
- `3`: the grid_item starts in the next column and spans 3 columns.
- `3-5`: the grid_item starts in column 3 and ends in column 5.
- `3+5`: the grid_item starts in column 3 and spans 5 columns.
- `3..`: the grid_item starts in column 3 and spans the remainder of the row.

Both the `col` and `span` attributes have responsive versions, which use [the "mobile first" approach][1] of the `--*-n-above` media queries. The available tiers are **`-md`, `-lg`, and `-xl`** (`col-md`, `span-lg`, etc.); other suffixes like `col-sm` or `col-xxl` don't exist and are silently ignored. Grids support up to 24 columns. (The file is generated by `src/tools/generate.global.grid.cjs`; add a tier there if you need one.)

[1]: /docs/layout#recommended-responsive-setup

<Notice type="info" title="HTML Validation">
  The custom attribute will not validate. If this is important to you, you'll
  need to change the attributes to `data-col` and `data-span` in both the CSS
  and HTML. I personally prefer to use shorter, more legible attributes. Check
  this thread on Github if you'd like to read more than you've ever wanted to
  know about [custom attributes and
  validation](https://github.com/whatwg/html/issues/2271).
</Notice>

### Examples

#### Different types of spans

9 column grid, with all types of spans.

<div class="grid docs_grid" col="9">
  <div class="grid_item docs_grid_item" span="2"><span>2</span></div>
  <div class="grid_item docs_grid_item" span="4-5"><span>4-5</span></div>
  <div class="grid_item docs_grid_item" span="6..">6..</div>
  <div class="grid_item docs_grid_item" span="2-3"><span>2-3</span></div>
  <div class="grid_item docs_grid_item" span="5.."><span>5..</span></div>
  <div class="grid_item docs_grid_item" span="2+3"><span>2+3</span></div>
  <div class="grid_item docs_grid_item" span="6+2"><span>6+2</span></div>
  <div class="grid_item docs_grid_item" span="row"><span>row</span></div>
  <div class="grid_item docs_grid_item" span="4+2"><span>4+2</span></div>
</div>

```html
<div class="grid" col="9">
  <div class="grid_item" span="2">2</div>
  <div class="grid_item" span="4-5">4-5</div>
  <div class="grid_item" span="6..">6..</div>
  <div class="grid_item" span="2-3">2-3</div>
  <div class="grid_item" span="5..">5..</div>
  <div class="grid_item" span="2+3">2+3</div>
  <div class="grid_item" span="6+2">6+2</div>
  <div class="grid_item" span="row">row</div>
  <div class="grid_item" span="4+2">4+2</div>
</div>
```

#### Responsive grid

The grid layout follows a "mobile first" approach using `--*-n-above` media queries.

The following example goes from 1 → 4 → 9 → 12 columns. (Resize window to see it in action.)

<div class="grid docs_grid" col="1" col-md="4" col-lg="9" col-xl="12">
  <div class="grid_item docs_grid_item">1</div>
  <div class="grid_item docs_grid_item">2</div>
  <div class="grid_item docs_grid_item">3</div>
  <div class="grid_item docs_grid_item">4</div>
  <div class="grid_item docs_grid_item">5</div>
  <div class="grid_item docs_grid_item">6</div>
  <div class="grid_item docs_grid_item">7</div>
  <div class="grid_item docs_grid_item">8</div>
  <div class="grid_item docs_grid_item">9</div>
  <div class="grid_item docs_grid_item">10</div>
  <div class="grid_item docs_grid_item">11</div>
  <div class="grid_item docs_grid_item">12</div>
</div>

```html
<div class="grid" col="1" col-md="4" col-lg="9" col-xl="12">[…]</div>
```

## Wrap

<section class="docs_section prose">

<div class="docs_oversizedTable">

| File name         | Source            |
| ----------------- | ----------------- |
| `global.wrap.css` | [Github][srcWrap] |

</div>

[srcWrap]: https://github.com/minimaldesign/mCSS/blob/main/src/styles/framework/global.wrap.css

The `.wrap` global class is a simple content wrapper: it centers your content on the page and adds some left/right padding on small viewports.

The content column comes in three width presets, set with a modifier on the wrapper: `.wrap-sm`, `.wrap-md`, and `.wrap-lg`. A bare `.wrap` uses the `md` width (`minmax(0, 70ch)`). There is also a `.wrap_content-fullBleed` modifier for children that should extend to the full size of the viewport. See [demo](/demos/wrap).

### Syntax

```html
<div class="wrap">
  <section class="wrap_content">
    <!-- Reading width content (md preset) -->
  </section>
</div>

<div class="wrap wrap-lg">
  <section class="wrap_content">
    <!-- Wider content (up to 900px) -->
  </section>
</div>

<div class="wrap">
  <section class="wrap_content wrap_content-fullBleed">
    <!-- Viewport width content -->
  </section>
</div>
```

### Settings

Three responsive paddings and the content widths, set in `theme.default.ui.css`. Override them in your [theme](/docs/default-theme).

<div class="docs_oversizedTable">

| Token             | Value                  | Description                      |
| ----------------- | ---------------------- | -------------------------------- |
| `wrap-spacing`    | `var(--sm1)`           | default left/right padding       |
| `wrap-md-spacing` | `var(--sm3)`           | left/right padding tablet and up |
| `wrap-lg-spacing` | `0`                    | left/right padding laptop and up |
| `wrap-width-sm`   | `minmax(0, 55ch)`      | narrow column (`.wrap-sm`)       |
| `wrap-width-md`   | `minmax(0, 70ch)`      | reading column (`.wrap-md`)      |
| `wrap-width-lg`   | `minmax(0, 900px)`     | wide column (`.wrap-lg`)         |
| `wrap-width`      | `var(--wrap-width-md)` | width of a bare `.wrap`          |

</div>

The "tablet and up" and "laptop and up" paddings follow the [responsive setup](#recommended-responsive-setup) below.

## Layouts

<section class="docs_section prose">

<div class="docs_oversizedTable">

| File name           | Source              |
| ------------------- | ------------------- |
| `global.layout.css` | [Github][srcLayout] |

</div>

[srcLayout]: https://github.com/minimaldesign/mCSS/blob/main/src/styles/framework/global.layout.css

Page-level scaffolds: the skeleton between your `body` tag and your content. Every variant extends the same `.layout` base, an app shell with a header, a stretching content area, and a sticky footer.

### Syntax

```html
<body class="layout layout-sidebar">
  <header>…</header>
  <div class="layout_content">
    <aside class="layout_content_aside">…</aside>
    <main class="layout_content_main">…</main>
  </div>
  <footer>…</footer>
</body>
```

The header and footer are optional: a bare `.layout` with only `.layout_content` still gives you a full-height page (that's what `.layout-cover` relies on).

### The layouts

Each thumbnail links to a full-page demo built from labeled color blocks. Resize the demo window to see the breakpoints, sticky footer, and full-viewport behavior.

<div class="grid" col="2" col-lg="3">
  <div class="grid_item">
    <LayoutThumb variant="centered" />
  </div>
  <div class="grid_item">
    <LayoutThumb variant="sidebar" />
  </div>
  <div class="grid_item">
    <LayoutThumb variant="sidebar-end" />
  </div>
  <div class="grid_item">
    <LayoutThumb variant="docs" />
  </div>
  <div class="grid_item">
    <LayoutThumb variant="split" />
  </div>
  <div class="grid_item">
    <LayoutThumb variant="cover" />
  </div>
</div>

<div class="docs_oversizedTable">

| Class                 | What it's for                                                                                                                                                       |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.layout`             | The base class: header / content / footer with a sticky footer. Required by every variant, usable alone.                                                            |
| `.layout-centered`    | A single centered reading column (articles, blog posts, legal pages). Width capped at `--layout-content-width`.                                                     |
| `.layout-sidebar`     | Aside + main from the `--md` breakpoint, single column below it (aside follows source order).                                                                       |
| `.layout-sidebar-end` | Compose with `.layout-sidebar` to move the aside to the opposite side.                                                                                              |
| `.layout-docs`        | Documentation: nav aside + main + TOC aside (`.layout_content_aside-toc`). One column below `--md` with the asides hidden, nav + main from `--md`, TOC from `--lg`. |
| `.layout-split`       | Two equal full-height panes (the direct children of `.layout_content`), stacked below `--md`. Auth screens, image + content landings.                               |
| `.layout-cover`       | Full-viewport (`100dvh`) cover with content centered both ways. Landing, 404, coming-soon pages.                                                                    |

</div>

<Notice type="info" title="Hidden asides need a menu">
  `.layout-docs` hides both asides below the `--md` breakpoint. That's
  deliberate: docs navigation belongs in your header menu on small screens
  (that's how mcss.dev does it). So make sure everything in your asides is also
  reachable somewhere else, like the header menu. If it isn't, use
  `.layout-sidebar` instead: on small screens it keeps the aside on the page,
  stacked on top of the main content, instead of hiding it.
</Notice>

### Container queries

Every layout main column (`.layout_content_main`) is a named [size container](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries) (`container: main / inline-size`). You can use it in your container queries:

```css
@container main (width >= …);
```

### Settings

Column widths come from three tokens in `theme.default.ui.css`. `--layout-toc-width` accepts any grid track size; the `1fr` default lets the TOC resize to whatever space the main column doesn't use.

<div class="docs_oversizedTable">

| Token                    | Value   | Description                                          |
| ------------------------ | ------- | ---------------------------------------------------- |
| `--layout-content-width` | `70ch`  | main column cap (`.layout-centered`, `.layout-docs`) |
| `--layout-aside-width`   | `245px` | aside column (`.layout-sidebar`, `.layout-docs`)     |
| `--layout-toc-width`     | `1fr`   | TOC column (`.layout-docs`)                          |

</div>

Override them globally in your [theme](/docs/default-theme), or per-layout with a scoped rule. This very site uses `.layout-docs` and layers its own specifics on top ([source][srcSiteLayout]): a wider `77ch` reading column and sticky side navs.

[srcSiteLayout]: https://github.com/minimaldesign/mCSS/blob/main/src/styles/site/global.layout.css

## Prose

<section class="docs_section prose">

<div class="docs_oversizedTable">

| File name          | Source             |
| ------------------ | ------------------ |
| `global.prose.css` | [Github][srcProse] |

</div>

[srcProse]: https://github.com/minimaldesign/mCSS/blob/main/src/styles/framework/global.prose.css

The global `.prose` class can be used anywhere you need basic typography for long form text, like articles, blog posts, etc.

It makes spacing between paragraphs, lists, tables, etc. consistent. This spacing can be modified in your [theme](/docs/default-theme) via the `--prose-spacing` token.

The `.prose` class also adds extra styling for `ol`, `ul`, and `code` elements.

## Accessibility

<section class="docs_section prose">

<div class="docs_oversizedTable">

| File name         | Source            |
| ----------------- | ----------------- |
| `global.a11y.css` | [Github][srcA11y] |

</div>

[srcA11y]: https://github.com/minimaldesign/mCSS/blob/main/src/styles/framework/global.a11y.css

Accessibility specific styles, used across the framework and the [components](/components/start):

- **`:focus-visible`**: a unified keyboard focus ring on everything. Customizable in your theme via the `--focus-ring-color`, `--focus-ring-width`, and `--focus-ring-offset` tokens.
- **`.a11y-skipLink`**: skip-to-content link. It stays off-screen until focused.
- **`.a11y-srOnly`**: hides the element visually, but keeps it accessible to screen readers.

Place the skip link first in your `<body>`, pointing at the id of your main content:

```html
<body class="layout">
  <a class="a11y-skipLink" href="#main">Skip to content</a>
  <header>…</header>
  <main id="main">…</main>
</body>
```

## Animation

<section class="docs_section prose">

<div class="docs_oversizedTable">

| File name              | Source                 |
| ---------------------- | ---------------------- |
| `global.animation.css` | [Github][srcAnimation] |

</div>

[srcAnimation]: https://github.com/minimaldesign/mCSS/blob/main/src/styles/framework/global.animation.css

Basic fade in/out animations with flexible start/end opacity values. Each keyframe only pins one end of the fade; the other end is the element's own `opacity`:

| Keyframes             | What it does                          |
| --------------------- | ------------------------------------- |
| `fadeFromTransparent` | fades from 0 to the element's opacity |
| `fadeFromOpaque`      | fades from 1 to the element's opacity |
| `fadeToTransparent`   | fades from the element's opacity to 0 |
| `fadeToOpaque`        | fades from the element's opacity to 1 |

```css
.element {
  opacity: 0.8;
  animation: fadeToTransparent 1s forwards;
}
```

(More info about [forwards](/blog/css-animation-fill-modes).)

## Media queries

| File name                    | Source        |
| ---------------------------- | ------------- |
| `settings.media-queries.css` | [Github][src] |

[src]: https://github.com/minimaldesign/mCSS/blob/main/src/styles/framework/settings.media-queries.css

All the breakpoints, user preferences, and device capabilities, as named [custom media queries][custom] you can use anywhere in your CSS.

<Notice
  type="info"
  title="PostCSS needed for custom media queries"
  class="mt-sm3 mb-sm3"
>
  [Custom media queries][custom] are not yet available without a [PostCSS
  plugin][postcss]. If you can't or don't want to add a build step to your
  process, you'll have to hard-code your breakpoints. (You can still use nesting
  as in the example below though.)
</Notice>

### Viewport size

#### Dimensions

<div class="docs_oversizedTable">

| Token keyword | Dimension (px) |
| ------------- | -------------- |
| `xxs`         | 0-240          |
| `xs`          | 240-360        |
| `sm`          | 360-480        |
| `md`          | 480-768        |
| `lg`          | 768-1024       |
| `xl`          | 1024-1440      |
| `xxl`         | 1440-1920      |

</div>

#### Variations

Using the `md` dimension as an example:

<div class="docs_oversizedTable">

| Token variation | Matches                      |
| --------------- | ---------------------------- |
| `--md-only`     | exact range                  |
| `--md-n-above`  | range top and above          |
| `--md`          | shorthand for `--md-n-above` |
| `--md-n-below`  | range top and below          |
| `--md-phone`    | exact range in portrait only |

</div>

#### Examples

<div class="docs_oversizedTable">

| Custom media query        | Value                           |
| ------------------------- | ------------------------------- |
| `@media(--md-only) {}`    | `(480px <= width < 768px);`     |
| `@media(--md) {}`         | `(width >= 768px);`             |
| `@media(--md-n-above) {}` | `(width >= 768px);`             |
| `@media(--md-n-below) {}` | `(width < 768px);`              |
| `@media(--md-phone) {}`   | `(--md-only) and (--portrait);` |

</div>

The `-phone` variant is available from `xxs` through `lg` (there is no `xl-phone` or `xxl-phone`).

#### Recommended responsive setup

If you search online for the best approach to responsive design and setting up your breakpoints, you'll come across the technically true but useless "it depends" answer.

Unless you have a good reason not to, you should use a mobile-first approach. What that means is your design works great on small screens out of the box, without any media queries, and then you add your tweaks for larger sizes.

This is how the CSS of most responsive components should be set up:

```css
.exampleComponent {
  /* default mobile */
  @media (--lg) {
    /* responsive tweaks for desktop */
  }
}
```

Without [PostCSS plugins][postcss]:

```css
.exampleComponent {
  /* default mobile */
  @media (width >= 1024px) {
    /* responsive tweaks for desktop */
  }
}
```

### User preferences

These map to the `prefers-*` and related media features, so you can respect user settings without memorizing the syntax:

<div class="docs_oversizedTable">

| Custom media query | Matches                                         |
| ------------------ | ----------------------------------------------- |
| `--motionOK`       | `(prefers-reduced-motion: no-preference)`       |
| `--motionNotOK`    | `(prefers-reduced-motion: reduce)`              |
| `--opacityOK`      | `(prefers-reduced-transparency: no-preference)` |
| `--opacityNotOK`   | `(prefers-reduced-transparency: reduce)`        |
| `--useDataOK`      | `(prefers-reduced-data: no-preference)`         |
| `--useDataNotOK`   | `(prefers-reduced-data: reduce)`                |
| `--OSdark`         | `(prefers-color-scheme: dark)`                  |
| `--OSlight`        | `(prefers-color-scheme: light)`                 |
| `--highContrast`   | `(prefers-contrast: more)`                      |
| `--lowContrast`    | `(prefers-contrast: less)`                      |
| `--invertedColors` | `(inverted-colors: inverted)`                   |
| `--forcedColors`   | `(forced-colors: active)`                       |

</div>

### Device capabilities

<div class="docs_oversizedTable">

| Custom media query | Matches                                      |
| ------------------ | -------------------------------------------- |
| `--portrait`       | `(orientation: portrait)`                    |
| `--landscape`      | `(orientation: landscape)`                   |
| `--HDcolor`        | `(dynamic-range: high) or (color-gamut: p3)` |
| `--touch`          | `(hover: none) and (pointer: coarse)`        |
| `--stylus`         | `(hover: none) and (pointer: fine)`          |
| `--pointer`        | `(hover) and (pointer: coarse)`              |
| `--mouse`          | `(hover) and (pointer: fine)`                |

</div>

#### Example

These are used like any other custom media query, and they can be combined with each other or with a dimension token:

```css
.exampleComponent {
  @media (--landscape) and (--touch) {
    /* a phone or tablet held sideways */
  }
}
```

Without [PostCSS plugins][postcss], the same thing spelled out:

```css
.exampleComponent {
  @media (orientation: landscape) and ((hover: none) and (pointer: coarse)) {
    /* a phone or tablet held sideways */
  }
}
```

[custom]: https://drafts.csswg.org/mediaqueries-5/#custom-mq
[postcss]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-media
