Eunomia v1.0.0-beta.2
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_BREAKPOINTS is that shared set for this library: four widths named mobile, tablet, laptop, and desktop. Reach for mobile when a change only matters on the smallest phones. Reach for tablet once a layout generally has enough room to stop behaving like a phone, the most common switch in practice. laptop and desktop are there for a change that only makes sense once a screen has even more room to spare, at the same steps as the Sass page grid scale (see "See also" below).

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 checkpoint lights up the moment the width reaches it, using the exact same width >= EUNOMIA_BREAKPOINTS.<token> test a component's own @media rule makes internally.

680px drag ↘
mobile600px tablet768px laptop1024px desktop1280px

Usage

EUNOMIA_BREAKPOINTS is a plain TypeScript object. Import it, both for a component's own css template and for your own responsive code:

import { css } from "lit";
import { EUNOMIA_BREAKPOINTS } from "@eunomia/elements";

css`
  @media (max-width: ${EUNOMIA_BREAKPOINTS.tablet}px) {
    :host {
      flex-direction: column;
    }
  }
`;

The same object works for a plain matchMedia listener too, for a layout switch driven from your own JavaScript instead of CSS:

import { EUNOMIA_BREAKPOINTS } from "@eunomia/elements";

const isMobile = window.matchMedia(
  `(max-width: ${EUNOMIA_BREAKPOINTS.tablet}px)`,
);

In Sass

The same widths are also available as Sass mixins, built on the same numbers as EUNOMIA_BREAKPOINTS. Every eunomia-lte-<token> mixin applies below that width, the same - 1 convention the components use internally, and its eunomia-gte-<token> counterpart applies from that width upward, so the two together tile the full range with no gap and no overlap at the breakpoint itself:

Mixin Applies
eunomia-lte-mobile width below 600px
eunomia-lte-tablet width below 768px
eunomia-lte-laptop width below 1024px
eunomia-lte-desktop width below 1280px
eunomia-gte-mobile width from 600px up
eunomia-gte-tablet width from 768px up
eunomia-gte-laptop width from 1024px up
eunomia-gte-desktop width from 1280px up
eunomia-mobile shorthand for eunomia-lte-tablet
eunomia-desktop shorthand for eunomia-gte-tablet

eunomia-mobile and eunomia-desktop exist because splitting at tablet is the single most common switch in practice (see "Try it" above); reach for the full eunomia-lte-<token>/eunomia-gte-<token> set for any other split.

@use "@eunomia/elements/styles/tools/indexes/eunomia-media-queries.index" as *;

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

In CSS

The same four widths are also published as CSS custom properties once a theme stylesheet is loaded, so a plain stylesheet can reach for them without importing anything from TypeScript or Sass:

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 still can't read a custom property, the CSS spec doesn't allow it, so this doesn't replace the Sass mixins above for switching a layout at a given width. Reach for it instead where a breakpoint value is needed inside a regular declaration (a max-width, a calc()) or read from JavaScript with getComputedStyle, so that value stays in sync with the same numbers everything else here uses.

Container queries

Every switch point 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 EUNOMIA_BREAKPOINTS plugs into the condition the same way it does in a @media rule, again as a literal pixel value rather than a var():

import { css } from "lit";
import { EUNOMIA_BREAKPOINTS } from "@eunomia/elements";

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

  @container (max-width: ${EUNOMIA_BREAKPOINTS.tablet}px) {
    .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 (max-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 (max-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