Eunomia v1.1.0
AllAngularReactNext.jsJavaScriptVue
EunomiaSalmonForestVioletOceanGoldFireCustom…
🇬🇧 English🇫🇷 Français

Breakpoints

A breakpoint is the viewport width where a layout stops working well one way and switches to another, going from a single stacked column on a phone to several columns side by side on a wider screen, for example. Reaching for one shared set of widths instead of a new number each time keeps every layout switch in an app landing on the same handful of points, so the whole page still feels consistent as it resizes.

Eunomia has four breakpoints, mobile, tablet, laptop and desktop. Each one is the width where a level starts: tablet is 768px, so the tablet level applies from 768px up, and everything below it stops just under. Reach for tablet when a layout has enough room to stop behaving like a phone, the most common switch in practice, and for mobile when a change only matters on the smallest phones. laptop and desktop are there for a change that only makes sense once a screen has even more room to spare.

Try it

Drag the box's bottom-right corner, or drag the cursor on the ruler below. The two are connected, so moving one moves the other, and you can also focus the cursor and use the arrow keys. Each breakpoint lights up the moment the width reaches it, the same width >= test as mediaFrom and the eunomia-from-* mixins.

680px drag ↘
mobile600px tablet768px laptop1024px desktop1280px

One source for everything

The breakpoints are defined once, in EUNOMIA_BREAKPOINTS. Everything else is built from that object: the components' own media and container queries, the Sass scale behind the mixins and the grid classes, and the --eun-breakpoint-* custom properties. The library's lint fails if any of them drifts from it, or if a width is written by hand anywhere in the library, so the values below are the ones every layout switch actually uses.

Breakpoint Width Below From
mobile 600px (width < 600px) (width >= 600px)
tablet 768px (width < 768px) (width >= 768px)
laptop 1024px (width < 1024px) (width >= 1024px)
desktop 1280px (width < 1280px) (width >= 1280px)

Range syntax

Every condition uses the range syntax, width < 768px below a breakpoint and width >= 768px from it. At the breakpoint itself, exactly one side applies, and there's no gap between them: the older max-width: 767px / min-width: 768px pair leaves every width between the two numbers unmatched, which a zoomed page or a fractional pixel ratio can land on. The range syntax works in every current browser. Write your own queries the same way, or let the helpers below write them for you.

Using the breakpoints

In CSS

Once a theme stylesheet is loaded, the widths are also custom properties:

Token Value
--eun-breakpoint-mobile 600px
--eun-breakpoint-tablet 768px
--eun-breakpoint-laptop 1024px
--eun-breakpoint-desktop 1280px
.custom-panel {
  max-width: var(--eun-breakpoint-tablet);
}

A @media condition can't read a custom property, the CSS spec doesn't allow it. Use these where a breakpoint is needed inside a regular declaration (a max-width, a calc()) or read from JavaScript with getComputedStyle. To switch a layout in plain CSS, write the condition from the table at the top of this page, or use the Sass mixins.

In Sass

eunomia-below-<breakpoint> applies below a breakpoint and eunomia-from-<breakpoint> from it, with the same conditions as mediaBelow and mediaFrom. eunomia-below($breakpoint) and eunomia-from($breakpoint) take the name as an argument, and fail the build on an unknown one. Load them with @use "@eunomia/elements/styles/breakpoints" as *;, which also brings the $map-device-width scale and emits no CSS of its own.

Mixin Applies
eunomia-below-mobile (width < 600px)
eunomia-below-tablet (width < 768px)
eunomia-below-laptop (width < 1024px)
eunomia-below-desktop (width < 1280px)
eunomia-from-mobile (width >= 600px)
eunomia-from-tablet (width >= 768px)
eunomia-from-laptop (width >= 1024px)
eunomia-from-desktop (width >= 1280px)
@use "@eunomia/elements/styles/breakpoints" as *;

.my-panel {
  @include eunomia-below-tablet {
    flex-direction: column;
  }
}

// Between two breakpoints : nest them
.my-sidebar {
  @include eunomia-from-tablet {
    @include eunomia-below-laptop {
      display: none;
    }
  }
}

In JavaScript

mediaBelow and mediaFrom return the condition for a breakpoint, ready for matchMedia, a @media rule or a @container rule. Import them from the @eunomia/elements/breakpoints.js subpath, which registers no component, so it's safe to load on a server or in a hook without pulling in the whole library. EUNOMIA_BREAKPOINTS comes from the same subpath when you need a raw width.

import { mediaBelow } from "@eunomia/elements/breakpoints.js";

const isMobile = window.matchMedia(mediaBelow("tablet"));

In a Lit css template, wrap the condition with unsafeCSS:

import { css, unsafeCSS } from "lit";
import { mediaBelow } from "@eunomia/elements/breakpoints.js";

css`
  @media ${unsafeCSS(mediaBelow("tablet"))} {
    :host {
      flex-direction: column;
    }
  }
`;

Containers

Container queries

Every condition above reacts to the browser viewport. A @container condition reacts to an ancestor's own rendered size instead, so a component embedded in a narrow dashboard panel compacts the same way it would in a narrow browser window, regardless of how wide the page around it actually is. eun-calendar's hour grid uses exactly this to size its own layout off its rendered width rather than the page's.

An element only becomes a query container once something declares it one with container-type, and mediaBelow and mediaFrom plug into the condition the same way they do in a @media rule:

import { css, unsafeCSS } from "lit";
import { mediaBelow } from "@eunomia/elements/breakpoints.js";

css`
  :host {
    container-type: inline-size;
  }

  @container ${unsafeCSS(mediaBelow("tablet"))} {
    .content {
      flex-direction: column;
    }
  }
`;

Give the container a container-name when a query needs to skip past a nearer container and reach a specific named ancestor instead of the closest one:

:host {
  container-type: inline-size;
  container-name: my-panel;
}

@container my-panel (width < 768px) {
  .content {
    flex-direction: column;
  }
}

Container styles

A container query can also read a custom property's value instead of a size, with style(). Combined with a size query that sets the property, this turns a one-off width check into something the rest of an app can also read, inherit, or override directly:

:host {
  --my-panel-compact: false;
}

@container (width < 768px) {
  :host {
    --my-panel-compact: true;
  }
}

@container style(--my-panel-compact: true) {
  .content {
    flex-direction: column;
  }
}

Because --my-panel-compact is a real custom property, it inherits like any other, so an app can force it on an ancestor (.my-panel { --my-panel-compact: true; }) to opt into the compact layout regardless of the container's actual rendered width. This is exactly the pattern behind eun-stepper's --stepper-mobile and eun-calendar's --scheduler-mobile/--scheduler-tablet custom properties, see the "See also" links below for both in context.

See also