Status: Canonical (v1.0) Last updated: 2025-12-23 Taxonomy placement: D1x. Layout Framing (Space & Rhythm extension) Audience: Design Systems, Product Design, Front-End Engineering
CEM first rule: If an adapter’s breakpoint names or defaults conflict with CEM semantics, CEM semantics win. Adapters translate to their own keyspaces; the consumer-facing vocabulary remains compact/medium/expanded/large/xlarge.
CEM uses breakpoints as a layout framing primitive that selects canonical layout archetypes (single-pane, two-pane, multi-pane) based on available space. Breakpoints express semantic meaning about available UI space, not device categories.
Companion specs:
cem-colors) — emotional palette and action statescem-dimension) — spacing scale, density modes, layout gapscem-responsive) — intrinsic vs container vs breakpoint adaptationcem-coupling) — zone/guard/halo invariants preserved across rangescem-layering) — depth/surface hierarchycem-stroke) — separability may need reinforcement in dense layoutscem-voice-fonts-typography) — reading constraints validated in large widthscem-timing) — layout transition timingD1x (Breakpoints) defines:
compact, medium, expanded, large, xlarge — stable names for layout bands.D1x does not define:
CEM breakpoint semantics are defined by available layout space and consumer intent (one-pane vs two-pane vs multi-pane), not by framework defaults (for example, md meaning “900px” in MUI). Treat framework breakpoints as an adapter layer.
A breakpoint expresses available UI space (window/container), not “tablet vs desktop”.
Do not implement isTablet-style logic based on breakpoint names.
Breakpoints are a small, stable vocabulary. Prefer 3–5 ranges, not dozens of product-specific micro-breakpoints.
Breakpoints choose canonical layout archetypes (one-pane → two-pane → multi-pane). Spacing, coupling, and stroke rules stay governed by their own dimensions (D1/D2/D5).
CEM breakpoints use a two-layer model:
Basis bounds (numeric boundaries)
--cem-bp-{axis}-{range}-{min|max}
│
▼
Semantic ranges (consumer vocabulary)
compact | medium | expanded | large | xlarge
CEM adopts the Material Design 3 window size class vocabulary as the primary reference because it is intentionally device-agnostic and defined on available width/height.
| CEM range | Meaning (consumer) | Width — reference |
|---|---|---|
compact |
single-pane default; tight navigation | < 600dp |
medium |
two-pane possible; avoid over-wide lines | 600dp ≤ w < 840dp |
expanded |
two-pane comfortable; navigation rail viable | 840dp ≤ w < 1200dp |
large |
multi-pane viable; guard against sparse “stretch” | 1200dp ≤ w < 1600dp |
xlarge |
desktop-class; constrain reading width, increase density | ≥ 1600dp |
Height is usually secondary due to vertical scrolling, but matters for landscape phones and split-screen.
| CEM range | Height — reference | Device representation (informative) |
|---|---|---|
compact |
< 480dp |
99.78% of phones in landscape |
medium |
480dp ≤ h < 900dp |
96.56% of tablets in landscape, 97.59% of phones in portrait |
expanded |
≥ 900dp |
94.25% of tablets in portrait |
Note: Most apps adapt based on width alone. Consider height when width is
mediumwhile height iscompact(e.g., phones in landscape), where two-pane layouts are often impractical.
These tokens are numeric bounds for ranges, consumed by build-time systems (PostCSS/@custom-media), JS breakpoints, and runtime CSS systems. Epsilon is used when expressing half-open intervals [min, nextMin) as exclusive max-width/max-height values.
| Token | Value | Description | tier |
|---|---|---|---|
--cem-bp-epsilon |
0.01px |
Default epsilon for exclusive upper bounds | required |
--cem-bp-epsilon-css |
0.01px |
Explicit CSS adapter epsilon (same as default) | recommended |
--cem-bp-epsilon-mui |
0.05px |
MUI adapter epsilon (theme.breakpoints.step = 5 parity) |
recommended |
--cem-bp-width-compact-min |
0px |
Compact width lower bound | required |
--cem-bp-width-compact-max |
calc(var(--cem-bp-width-medium-min) - var(--cem-bp-epsilon)) |
Compact width upper bound (exclusive) | required |
--cem-bp-width-medium-min |
600px |
Medium width lower bound (M3 reference) | required |
--cem-bp-width-medium-max |
calc(var(--cem-bp-width-expanded-min) - var(--cem-bp-epsilon)) |
Medium width upper bound (exclusive) | required |
--cem-bp-width-expanded-min |
840px |
Expanded width lower bound (M3 reference) | required |
--cem-bp-width-expanded-max |
calc(var(--cem-bp-width-large-min) - var(--cem-bp-epsilon)) |
Expanded width upper bound (exclusive) | recommended |
--cem-bp-width-large-min |
1200px |
Large width lower bound (M3 reference) | recommended |
--cem-bp-width-large-max |
calc(var(--cem-bp-width-xlarge-min) - var(--cem-bp-epsilon)) |
Large width upper bound (exclusive) | recommended |
--cem-bp-width-xlarge-min |
1600px |
Xlarge width lower bound (no max; unbounded) | recommended |
| Token | Value | Description | tier |
|---|---|---|---|
--cem-bp-height-compact-min |
0px |
Compact height lower bound | recommended |
--cem-bp-height-compact-max |
calc(var(--cem-bp-height-medium-min) - var(--cem-bp-epsilon)) |
Compact height upper bound (exclusive) | recommended |
--cem-bp-height-medium-min |
480px |
Medium height lower bound | recommended |
--cem-bp-height-medium-max |
calc(var(--cem-bp-height-expanded-min) - var(--cem-bp-epsilon)) |
Medium height upper bound (exclusive) | recommended |
--cem-bp-height-expanded-min |
900px |
Expanded height lower bound (no max; unbounded) | recommended |
The @media helpers in Block B set this token so that JS can read the current breakpoint class
via getComputedStyle(document.documentElement).getPropertyValue('--cem-bp-active-width').trim().
There is no :root default; the value is always resolved from @media context.
| Token | Description | tier |
|---|---|---|
--cem-bp-active-width |
Current active width class (compact/medium/expanded/large/xlarge); read via JS | recommended |
--cem-bp-active-height |
Current active height class (short/medium/tall); read via JS | recommended |
These tables drive the @media and height helper blocks in the generator. They are not token
sources — rows do not begin with --cem-* and are excluded from the manifest automatically.
| range | min-width | max-width | description |
|---|---|---|---|
| compact | 0px | 599.99px | max-only rule; M3 compact width |
| medium | 600px | 839.99px | M3 medium width |
| expanded | 840px | 1199.99px | M3 expanded width |
| large | 1200px | 1599.99px | M3 large width |
| xlarge | 1600px | — | min-only rule; M3 xlarge width |
| range | min-height | max-height | description |
|---|---|---|---|
| compact | 0px | 479.99px | max-only rule; compact height |
| medium | 480px | 899.99px | medium height |
| expanded | 900px | — | min-only rule; expanded height |
compact, medium, expanded, optionally large, xlarge).[min, nextMin) (lower bound inclusive, upper bound exclusive).max-width/max-height, subtract a small epsilon.
theme.breakpoints.step (default 5, i.e., 0.05px) which is used to implement exclusive down()/between() upper bounds.@custom-media --cem-compact (max-width: calc(600px - 0.01px));
@custom-media --cem-medium (min-width: 600px) and (max-width: calc(840px - 0.01px));
@custom-media --cem-expanded (min-width: 840px) and (max-width: calc(1200px - 0.01px));
@custom-media --cem-large (min-width: 1200px) and (max-width: calc(1600px - 0.01px));
@custom-media --cem-xlarge (min-width: 1600px);
/* Height classes */
@custom-media --cem-height-compact (max-height: calc(480px - 0.01px));
@custom-media --cem-height-medium (min-height: 480px) and (max-height: calc(900px - 0.01px));
@custom-media --cem-height-expanded (min-height: 900px);
/*
* NOTE:
* The `0.01px` epsilon is illustrative. Align epsilon to your toolchain:
* - If you use MUI default step=5, prefer 0.05px for exclusive upper bounds.
*/
M3 window size classes provide the reference lattice for this spec. CEM directly adopts M3’s width/height boundaries.
| M3 class | Width boundary |
|---|---|
| Compact | < 600dp |
| Medium | 600–839dp |
| Expanded | 840–1199dp |
| Large | 1200–1599dp |
| Extra-large | ≥ 1600dp |
| MUI key | Default value |
|---|---|
xs |
0px |
sm |
600px |
md |
900px |
lg |
1200px |
xl |
1536px |
Gap analysis: MUI lacks the 840px boundary (M3 expanded) and uses 1536px instead of 1600px for xl.
CEM semantics are stable; your MUI integration chooses an adapter strategy.
Strategy A (recommended for most MUI codebases): keep MUI keys, align values to the CEM lattice.
This avoids custom keys and minimizes TypeScript/module-augmentation friction while matching CEM/M3 boundaries.
const theme = createTheme({
breakpoints: {
values: {
xs: 0,
sm: 600, // medium lower bound
md: 840, // expanded lower bound (repurposes MUI `md`)
lg: 1200, // large lower bound
xl: 1600, // xlarge lower bound (repurposes MUI `xl`)
},
},
});
Strategy B (semantic purity): replace MUI keys with CEM keys (compact/medium/expanded/large/xlarge).
This yields the cleanest vocabulary, but requires TypeScript module augmentation and a broader migration.
Strategy C (bridge / mixed keyspace): add expanded/xlarge alongside the defaults.
Use only for incremental migration. This introduces two “large-ish” keys (xl vs xlarge)—treat the mixed period as transitional.
Strategy A mapping (aligned values, default keys):
| CEM range | MUI binding |
|---|---|
compact |
down('sm') |
medium |
between('sm', 'md') |
expanded |
between('md', 'lg') |
large |
between('lg', 'xl') |
xlarge |
up('xl') |
Strategy B mapping (CEM keys as MUI keys):
| CEM range | MUI binding |
|---|---|
compact |
down('medium') (or only('compact') if you define it explicitly) |
medium |
between('medium', 'expanded') |
expanded |
between('expanded', 'large') |
large |
between('large', 'xlarge') |
xlarge |
up('xlarge') |
Strategy C mapping (mixed keyspace, transitional):
| CEM range | MUI binding |
|---|---|
compact |
down('sm') |
medium |
between('sm', 'expanded') |
expanded |
between('expanded', 'lg') |
large |
between('lg', 'xlarge') |
xlarge |
up('xlarge') |
Breakpoints trigger layout archetype changes:
compact layout, progressively enhance.xlarge to preserve D6 readability (45–75 characters optimal).Breakpoints (D1x) classify available space. Responsiveness strategy (D1y) classifies how layout adapts. Treat these as orthogonal axes:
intrinsic — continuous adaptation via Flex wrap / intrinsic Grid / fluid constraints.container — adaptation based on container size (@container), for portable components.breakpoint — adaptation based on viewport/window size classes, for top-level IA shifts.hybrid — intrinsic-first, with breakpoint/container steps only where meaningfully needed.In CEM, the default posture is intrinsic-first; introduce breakpoint-driven behavior primarily when the meaning of layout changes (e.g., one-pane → two-pane), not to compensate for rigid mechanics. See cem-responsive.
Use this table to choose where D1x breakpoints apply and which D1y strategy to prefer. The “Responsiveness strategy” column is normative for component recipes; other columns are guidance.
| Component family | Breakpoint role (D1x) | Preferred query context | Responsiveness strategy (D1y) | Notes |
|---|---|---|---|---|
| App shell navigation (bottom bar / rail / drawer) | Yes (archetype switch) | Viewport/window | breakpoint / hybrid |
Breakpoints change navigation meaning; keep D2 invariants. |
| Master–detail / list–detail panes | Yes (introduce second pane) | Viewport/window, sometimes container | hybrid |
Use intrinsic sizing within panes; breakpoint decides when the second pane exists. |
| Card listings / galleries | Usually No | Container first | intrinsic / container |
Prefer wrap or auto-fit/minmax(); avoid fixed column counts. |
| Forms (field groups) | Rare | Container first | intrinsic |
Prefer flowing groups and min()/max() widths; breakpoint only for major reflow. |
| Data tables / dense comparison grids | Sometimes | Container first | container / hybrid |
Container size often determines column visibility / stacking. |
| Side panels, filters, inspector panes | Sometimes | Container first | container / hybrid |
The panel width (not viewport) is the driver in split UI. |
| Overlays (dialogs, popovers, tooltips) | No (typically) | Container/anchor | intrinsic |
Size to content and coupling; do not key to viewport breakpoints. |
CEM breakpoints define semantic ranges of available space. They do not mandate a fixed grid, fixed column count, or device-class assumptions.
flex-wrap, and/or Grid patterns like repeat(auto-fit, minmax(...)) so layout adapts smoothly to the actual space available.A well-designed intrinsic layout can span small mobile → large desktop → ultra-wide without introducing additional breakpoint classes. Use breakpoints primarily when the meaning of layout changes (e.g., introducing a second pane), not to compensate for rigid layout mechanics.
clamp()) to avoid hard jumps in spacing/typography.responsive?Generally no: “responsive” is an implementation property (intrinsic vs breakpoint-driven), not a breakpoint range.
If you need to mark that a layout is intended to be intrinsic-responsive (wrapping/container-first), prefer a documentation tag in component mapping (e.g., strategy: intrinsic) or an adapter/build-time flag. Avoid encoding “responsive” as an extra breakpoint value because it conflates method with available space semantics.
Some UIs (split panes, sidebars, embedded widgets) need size classes based on container width, not viewport.
These reference values reuse the same semantic lattice as the viewport breakpoints. Consumers place
them in their own @container rules; CEM does not emit @container selectors directly.
Uses --cem-bp-epsilon directly — no separate --cem-cq-epsilon needed.
R-D1x-WRAP resolved: CEM v1 documents the containment requirement but does not ship a wrapper component.
Component libraries and products must opt each queryable host into containment with container-type (and optionally
container-name). This keeps the token package framework-neutral and avoids adding layout side effects to consumers’
DOM. A wrapper may still be built by an adapter library, but it is not part of the canonical D1x token output.
| Token | Value | Description | tier |
|---|---|---|---|
--cem-cq-width-compact-max |
calc(var(--cem-cq-width-medium-min) - var(--cem-bp-epsilon)) |
Compact container upper bound (exclusive) | recommended |
--cem-cq-width-medium-min |
600px |
Medium container lower bound | recommended |
--cem-cq-width-medium-max |
calc(var(--cem-cq-width-expanded-min) - var(--cem-bp-epsilon)) |
Medium container upper bound (exclusive) | recommended |
--cem-cq-width-expanded-min |
840px |
Expanded container lower bound | recommended |
--cem-cq-width-expanded-max |
calc(var(--cem-cq-width-large-min) - var(--cem-bp-epsilon)) |
Expanded container upper bound (exclusive) | recommended |
--cem-cq-width-large-min |
1200px |
Large container lower bound | recommended |
.container {
container-type: inline-size;
container-name: card;
}
@container card (max-width: 599.98px) {
/* compact container layout */
}
@container card (min-width: 600px) and (max-width: 839.98px) {
/* medium container layout */
}
Implementation requirements:
container-type: inline-size.container-name) when more than one query context can be present or when component recipes need a
stable target.--cem-cq-width-* values to activate behavior by themselves; CSS custom properties cannot be used
inside @container conditions.Material UI (optional): container query adapter and shorthand
Material UI exposes theme.containerQueries and an sx shorthand using @<size> / @<size>/<name>.
// Theme API (breakpoint keys or unitless widths)
theme.containerQueries.up('sm'); // => '@container (min-width: 600px)'
theme.containerQueries('card').up(600); // => '@container card (min-width: 600px)'
// sx shorthand (unitless sizes render as px; `@500px` is invalid syntax)
<Box
sx={{
'@': { p: 1 }, // 0px
'@600': { p: 2 }, // 600px
'@840': { p: 3 }, // 840px
}}
/>
compact/medium/expanded/...).Treat as breaking if you:
compact, medium, expanded, large, xlarge).medium stops being the “two-pane possible” band).Treat as non-breaking if you:
Token tier is encoded in the tier column of each source table. The manifest validator derives
expected token names from these tables using the same XPath pattern as cem-breakpoints.html.
| Source table h6 id | Tokens covered | Validator derivation |
|---|---|---|
cem-bp-basis |
--cem-bp-epsilon* (3) + --cem-bp-width-* (9) = 12 tokens |
one token per row |
cem-bp-height |
--cem-bp-height-* (5 tokens) |
one token per row |
cem-bp-active |
--cem-bp-active-width + --cem-bp-active-height (2 tokens; set by @media, no :root default) |
one token per row |
cem-bp-cq |
--cem-cq-width-* (6 tokens) |
one token per row |
Generator helper tables cem-bp-media-ranges and cem-bp-height-ranges are NOT token sources —
their rows start with range names (not --cem-*), so they are automatically excluded from the manifest.
Note: @custom-media is NOT emitted in production output per Principle P5. The @media helper
block (Block B) sets --cem-bp-active-width for JS consumption. The @container helper block
(Block C) is intentionally not emitted after R-D1x-WRAP resolution; --cem-cq-* reference values are
available in :root for consumers or adapter-owned wrappers to use in their own @container rules.
cem-dimension) — spacing scale and layout gapscem-coupling) — density invariants across rangescem-stroke) — separability reinforcement under density pressurecem-voice-fonts-typography) — reading constraints and scanning patternscem-responsive) — intrinsic/container/breakpoint strategy vocabularyclamp())This spec is the canonical D1x contract for CEM responsive breakpoints and adapter mapping.