Skip to main content

Layout

Since September 2023, all “evergreen” browsers (Chrome, Edge, Firefox, Safari) have capability for the new CSS Container Queries (see Can I use Container Queries). Baselayer 3.2.x has container query powered layouts to control flex, grid, and hidden utility classes.

Blocks

  • block — make an inline element behave as a block element
  • inline-block — to enable block-like settings on an inline element (width, height, margins, paddings)
  • inline-flex, flex — see flex layouts
  • grid — see grid layouts

Dimensions

Content wrappers

Baselayer’s wrapper classes add a constrained layout width, inline margin (x-axis) auto centering, and side edge whitespace when the viewport width is at or narrower then the wrapper width.

Baselayer’s wrapper classes were previously known as container classes, copying the name from other CSS frameworks such as Bootstrap. But with Baselayer 3.2.x switching from @media queries to @container queries, a context-container was required for parent elements of container query controlled layout systems. Therefore, the container class is now specifically used for that purpose. See container query powered layouts.

The centered layout wrapper is set up as follows:

In variables.css:

:root {
  --w-xl: 1600px;

  --sp-2: clamp(1rem, 0.5rem + 1.25vw, 1.5rem);
}

/*
Container query context
*/
.container {
  --sp-2: clamp(1rem, 0.5rem + 1.25cqi, 1.5rem);  
}

For viewport widths below --w-max side spacing is provided by --sp-2. This adds a negative space (whitespace) right and left of the wrapper, to prevent text being difficult to read when up against the sides of the viewport. --sp-2 involes viewport widths (vw), or container query widths (cqi) if the wrapper is places inside a container.

In layout.css:

.wrapper {
  --w-max: var(--w-xl);
  width: min(100% - (var(--sp-2) * 2), var(--w-max));
  margin-inline: auto;
}

There are several wrapper width modifiers, same as the width modifiers — see below.

Widths

320px intervals. w- classes will have widths 100% until their max width. wrapper- classes will have widths (100% minus side space) until their max width.

The difference between width utilities and wrapper utilities is that wrappers have x-axis side padding and x-axis centering (see above).

  • wrapper-xs / w-xs — max width 320px
  • wrapper-sm / w-sm — max width 640px
  • wrapper-md / w-md — max width 960px
  • wrapper-lg / w-lg — max width 1280px
  • wrapper-xl / w-xl — max width 1600px
  • wrapper — max width 1600px

Both the -xs, -sm, -md, -lg, -xl wrapper and width utilities have their widths set using:

width: min(100%, {variable});

This means they will responsively expand to width 100% within the space available, until they max out at their set width variable.

Three more:

  • w-100% — width 100%
  • w-100vw — width 100vw
  • w-max-100vw — max-width 100vw

Heights

  • h-100% — height 100%, e.g. for making cards equal to the height of their wrapper
  • h-100dvh — height 100dvh, e.g. for making “full cover” panels
  • h-max-100dvh — max-height 100dvh, e.g. for tall sidebars (use h-max-100dvh with overflow-y)

Baselayer uses 100dvh (dynamic viewport height) that gives a different viewport height for some devices — i.e. it compensates for the scroll-retracting interface toolbars on iOS Safari.

Box

  • box — expands an inner element using inset: 0 to fill the size of its wrapper (you must put relative on a box wrapper). Useful for setting up a panel (e.g. hero or card) background image with text overlay.

Positions

  • relative
  • absolute
  • sticky
  • top — top: 0
  • right — right: 0
  • bottom — bottom: 0
  • left — left: 0
  • z-1 — z-index: 1
  • z-2 — z-index: 2
  • z-3 — z-index: 3

For centering and middling, you also need flex.

Example:

Top
Right
Bottom
Left
Mentered
and middled
<div class="relative">

  <div class="absolute top">
    Top
  </div>

  <div class="absolute right">
    Right
  </div>
  
  <div class="absolute bottom">
    Bottom
  </div>
  
  <div class="absolute left">
    Left
  </div>
  
  <div class="absolute box flex flex-center flex-middle">
    Centered and middled
  </div>

</div>

Container query powered layouts

Since Baselyer 3.2.x, flex, grid, and invisibility (hidden) utilities have @container variants that only work if they are wrapped in a container class, that provides the container query context:

.container {
  container-type: inline-size;
}

This container class does not constrain the outer element’s width in any way. You will need to control the widths of your content with e.g. wrapper or width utilities, or by placing the container within a grid cell, etc.

There are three @container variants for flex, grid, and hidden classes corresponding to prefix widths:

  • sm: (640px)
  • md: (960px)
  • lg: (1280px)
Layout classes with sm:, md:, and lg: prefixes do not work unless they are inside a container.

You can place the container class on e.g.:

  • The <body> tag (so that the container query will behave the same as a media query)
  • Semantic layout sections, such as your page <header>, <main>, or <footer>
  • <div>’s of course
  • Nested within other container elements (e.g. so you can have sidebars or arrays of cards within your <main>)
  • And more.

Supporting older browsers

What if you want to use Baselayer’s flex, grid and hidden clases but you really prefer media queries, or if you need to support pre-container query browsers?

Then all you need to do is find every instance of @container in layout.css and replace it with @media, and this will convert everything to media queries. (And then the container class will be redundant — you won’t need it.)

Container query demos

This entire documentation template is built using a full-width container query powered grid that takes effect when the viewport is at or above Baselayer’s md container query breakpoint (default ≥ 960px). Above md, the top navigation bar becomes the sidebar in column 1, with everything else in column 2.

The “everything else” area is organized in another grid within the same container context as the outer grid above, that becomes a two column grid when that container’s width is at lg or above (≥ 1280px). Then the table of contents (TOC) block is placed in column 2 of this inner grid (that appear to be the third column in the overall page layout).

So, the page layout deonstrates two grid layouts, one nested inside the other, and both are inside one outer container — they share the same container query context.

The main content (everything else below the title) is organised within a content-grid (that is not powered by a container query).

Also, there There are small demos of container query layouts on this page below, demonstrating flex, grid, and hidden classes all set within their own containers. Some of those demos have an x-axis resizer, so that you can test them out and see how they work. The resizer is indicated by a dashed border and a resizer symbol in the bottom right corner. Example resizer without a container query demo inside:

Resize symbol ↘

Flex layouts

Flexbox utilities for simple layout, menubars, pagination lists, cards, etc.

  • inline-flex — inline flexbox at all viewport widths
  • flex — flexbox at all viewport widths

Flex gaps

gap-* — adds a horizontal and vertical gap (same as for grid layouts):

  • gap-1--sp-1
  • gap-2--sp-2
  • gap-3--sp-3
  • gap-4--sp-4

Flex modifiers

  • X-axis: flex-start / flex-center / flex-end
  • Y-axis: flex-top / flex-middle / flex-bottom
  • flex-wrap — gives you flex-wrap: wrap
  • flex-column — gives you flex-direction: column
  • flex-space-between — gives you justify-content: space-between
  • flex-grow-equal — makes grid item expand so that they occupy an equal fraction of the total width (or height, if used with flex-column)
  • flex-grow-auto — makes grid item expand so that they occupy an unequal fraction of the total width (or height, if used with flex-column): each expanding as required by their respective content.

Note: the gaps have the same spacing CSS variables as margins and paddings.

Flex-item grow

  • grow — gives you flex-grow: 1
(no grow)
grow
<div class="flex">
  <div></div>
  <div class="grow"></div>
</div>

Flex and container queries

If you wrap the following in a container class, they will take effect at the following container widths up:

  • sm:flex — flexbox at container width 640px and up
  • md:flex — flexbox at container width 960px and up
  • lg:flex — flexbox at container width 1280px and up

Example using sm:flex (container query breakpoint width 640px):

<div class="container">
  <nav class="sm:flex gap-1 flex-end">
    <a class="my-1 btn block" href="">Home</a>
    <a class="my-1 btn block" href="">About</a>
    <a class="my-1 btn block" href="">Blog</a>
    <a class="my-1 btn block" href="">Contact</a>
  </nav>
</div>

In the example above, each button has a y-axis (block axis) margin, so that they still have whitepace gaps below the sm:flex breakpoint width.

Grid layouts

Setting up a grid

Controlling tracks at grid wrapper level:

  • Grid — the grid class initializes the CSS grid. It only adds display: grid — it doesn’t provide information about how many columns you want, or what their widths will be. To control columns, use equal- classes on the grid wrapper, or control the position of grid items.
  • Gap (optional) — adds vertical and horizontal whitespace (a.k.a. gutters) along internal grid tracks. See grid gaps.
  • Equal width grid cell control (optional)equal-*-cols etc. specifies how many columns your layout has (2, 3, or 4), where each column width is equalized.
  • Dense packing (optional)grid-dense can be used as a quick way to reorder grid items: packing so later items into earlier empty cells if there’s enough space for them. There is a dense packing example below, after where we have described per-item control.

Having 2, 3, or 4 CSS grid columns covers most use cases for the traditional 12 column grid system in webpage design. The Baselayer 3 grid can do all that and so much more.

1
2
3
<div class="grid equal-3-cols">
  <div></div>
  <div></div>
  <div></div>
</div>

If this is all you do to set up a grid, each grid item will automatically span one grid cell. If you have more grid items than set columns, the excess will wrap onto new row(s).

1
2
3
4
5
<div class="grid equal-3-cols">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

Grid gaps

  • gap-* — adds a horizontal and vertical gap between grid cells, using the same spacing variables as for margins and paddings:
    • gap-1--sp-1
    • gap-2--sp-2
    • gap-3--sp-3
    • gap-4--sp-4

These same gap-* classes are used for flex layouts.

1
2
3
4
5
<div class="grid equal-3-cols gap-2">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

Controlling grid items

Controlling positioning and spanning at per-grid-item level:

  • Spanning (optional)col-span-* and row-span-*etc. – spanning 2, 3, or 4 columns or rows.
  • Positioning (optional)col-* and row-* etc. – for positioning each grid item over the grid cells.

CSS grid positions grid items automatically on the available grid cells — so an item will be placed on the next available cell until required to begin again on the next (i.e. new) row. You can use this automatic positioning to your advantage, allowing CSS grid to presume where you want your next item to be. As in the following simple example:

1
columns 2 to 3
<div class="grid equal-3-cols gap-2">
  <div></div>
  <div class="col-span-2"></div>
</div>

The col-span- and row-span- spanning classes can be used to make grid items to span up to 4 columns and/or 4 rows.

You can also control positioning by using the responsive col- and row- classes. In the following example, all we needed todo is specify that the third grid item should go on row-2 and the CSS grid automatically figured out that the fourth item should start in the next available grid cell — in column 3 of row 1:

1
2
columns 1 to 2, row 2
columns 3 to 4, rows 1 to 2
<div class="grid equal-4-cols gap-1">
  <div>1</div>
  <div>2</div>
  <div class="col-span-2 row-2">columns 1 to 2, row 2</div>
  <div class="col-span-2 row-span-2">columns 3 to 4, rows 1 to 2</div>
</div>

The responsive col- and row- positioning classes are used to instruct the grid which grid cell you want your grid item to be placed on (up to 4 columns and/or 4 rows).

Grid items and dense packing

When you control the positioning of grid items, you cam sometimes leave leave spaces of unoccupied cells. This is because CSS grid automatially tries to place the next item in the next available cell — it does not automatically back-fill any empty cells that you have left:

Item 1
Item 2
Item 3
Item 4
Item 5
<div class="grid gap-1 equal-4-cols">
  <div>Item 1</div>
  <div class="col-1 col-span-2">Item 2</div>
  <div class="col-2 col-span-3">Item 3</div>
  <div class="col-4">Item 4</div>
  <div class="col-3">Item 5</div>
</div>

With the grid-dense modifier you can back-fill some or all of these unoccupied cells, by CSS grid reordering (rearranging) your grid items to fill in the spaces. Here’s the example above again, but with grid grid-dense:

Item 1
Item 2
Item 3
Item 4
Item 5

Grid and container queries

The Baselayer grid system has four tiers of container query breakpoint widths, for creating different grid layouts for different sized containers: all widths, sm:, md:, and lg:.

  • Tier 1: grid layout effective at all container widths (including below 640px)
  • Tier 2: sm: grid layout effective at container widths 640px and up
  • Tier 3: md: grid layout effective at container widths 960px and up
  • Tier 4: lg: grid layout effective at container widths 1280px and up

These three breakpoint prefixes can be added to grid wrapper equal- classes (to specify that you want 2, 3, or 4 equalized columns at those container widths). And they can also be added to the per-item positioning and spanning classes. Meanwhile the grid, grid-dense, and gap-* classes cannot be controlled by breatkpoints — because they don’t need to be.

Note: the grid class is effective at all widths, therefore the gap-* class will still work all the way down.

The tier 1 grid system has no container query prefixes (and doesn’t need to be surrounded by a container) — it takes effect at all widths. This makes the tier 1 grid ideal for creating small icon galleries, or for making small media objects that you don’t want to “stack collapse” in narrow columns or on phones (e.g. social messaging or comment cards).

The three responsive (container query powered) grid layout prefixes can be combined on the same HTML elements, so that you can create up to four different layouts on the same grid.

Simple example: With just adding a container around your grid, and then width prefixes on equal- classes, this is all you need for setting up equal width items such as in image galleries, or sets of cards:

1
2
3
4
5
6
<div class="container">
  <div class="grid sm:equal-2-cols md:equal-3-cols gap-2">
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
    <div>6</div>
  </div>
</div>

Another example:

Media object

Lorem ipsum dolor, sit amet consectetur adipisicing elit. Magni rem animi quaerat accusantium illum architecto, nemo, ex harum voluptatum adipisci eum blanditiis dolorum.

A few more examples can be found in examples, where you can see how container query breakpoint width tiers can be used on grid items for positioning and spanning.

Invisibility (hidden) classes

There may be situation where you require some element(s) to be displayed on smaller or larger container widths, but hidden otherwise. Baselayer has:

  • sm:hidden — hides elements in containers with width 640px and up
  • sm:hidden-below — hides elements in containers with width below 640px
  • md:hidden — hides elements in containers with width 960px and up
  • md:hidden-below — hides elements in containers with width below 960px
  • lg:hidden — hides elements in containers with width 1280px and up
  • lg:hidden-below — hides elements in containers with width below 1280px
Invisilility: ✓ = displayed; ✗ = hidden
Class Example ≤639px 640px–959px 960px–1280px ≥1280px
sm:hidden-below
Example
md:hidden-below
Example
lg:hidden-below
Example
sm:hidden
Example
md:hidden
Example
lg:hidden
Example

Content-grid

Note: content-grid is not a container query powered layout.

content-grid is intended for long-read (a.k.a. long-form) prose such as blog losts, news articles, and academic papers.

content-grid uses CSS Grid to set up a 7 column layout. The default behavior of content-grid will place your content in the middle (column 4), where it will have maximum width --w-cg = 40em. This max width will be 640px for the default font size (1rem = 16px) and 800px when used with t-long-read (see the long-lead utility class).

content-grid expects your content typographic blocks (headings, paragraphs, lists, tables, etc.), and the panel blocks below, to be its immediate children.

Popout panels

Use the popout utility class on an immediate child of content-grid to make an element span the middle 3 columns (3 to 5) instead of just column 4. Columns 3 and 5 have width --sp-2.

Example information panel using popout:

☆ Information panel
<div aria-label="Note" class="popout mb-2 bt-3 b-blue b-300 b-dark-invert r-2 p-2 t-black bg-gray bg-100 bg-dark-invert">
  &star; Information panel
</div>

Expanded panels

Sometimes you need to expand a panel more than as is done in the popout above. You can do this using the expand class, that makes an immediate child of container-grid to span the middle 5 columns (2 to 6).

Example “poster” infographic panel using expand (and showing how Baselayer’s aspect ratio utilities work):

This is a lot of example text that may or may not distort the aspect ratio (16×9) of this expand component.

See what it does on a small viewport width (e.g. phone).

A z-index positioning layer (e.g. z-1) is required to make the text overlay the image layer. (Alternatively, you can add another relative context.)

<div class="expand mt-2 mb-3 aspect-ratio-16x9 flex flex-center flex-middle relative">
  <svg>...</svg>
  <div class="z-1 w-sm aspect-ratio-16x9 p-3">
    <p class="h1 t-bold">This is a lot of example text that may or may not distort the aspect ratio (16×9) of this expand component.</p>
    <p class="h1 t-bold">See what it does on a small viewport width (e.g. phone).</p>
  </div>
</div>

Full-bleed panels

Use the full-bleed utility class to make an element span all 7 columns of a content-grid.

Columns 1 and 7 (the first and last column) have a minimum width of --sp-2 — providing the middle columns with inline (x-axis) side whitespace.

If your layout has no sidebars, side spacing (margin or padding), or other object that takes up some of the viewport width, then your content-grid full-bleed will expand to the full width of the viewport. But if it can’t get to the full viewport width, then it will expand to the available width (as seen in the docs example below).

Example colored stripe using full-bleed:

full-bleed — expands to the full width of the avilable space. If there are no sidebars, it will reach the sides of the viewport.
<div class="full-bleed">
 Full bleed panel content...
</div>

Aspect ratios

Common aspect ratio constraints for images, video, and hero blocks.

aspect-ratio-1x1
aspect-ratio-4x3
aspect-ratio-16x9
aspect-ratio-21x9

Overflows

Using auto to add scrollling when the content of a block exceeds its constrained height or width.

  • overflow-x — e.g. for wrapping tables with a lots of columns, that would break a template layout in small viewports
  • overflow-y — e.g. for sidebar menus loaded with content