Responsive Breakpoints
Design uses the Framer model: every screen is ONE document (one DOM
tree). Breakpoint frames are the SAME document rendered at different
viewport widths — never separate copies. Edits cascade:
- The base is the primary (widest) frame. Base edits are plain
unprefixed classes / inline styles and cascade down to every narrower
breakpoint unless overridden there.
- Edits made while a narrower breakpoint is active persist as
width-scoped overrides that apply from just below the next-wider frame
down to zero. Narrower breakpoints layer their own overrides on top.
The bound for an override is next-wider frame width - 1 (e.g. breakpoints
390/810 with a 1280 primary: editing 810 scopes to ≤ 1279px, editing 390
scopes to ≤ 809px). breakpointUpperBoundPx in
shared/responsive-classes.ts is the canonical implementation.
Managing breakpoints
A newly generated design's frame set comes from generate-design's devices
param (("mobile"|"tablet"|"desktop")[], default ["desktop","mobile"]). The
cascade is desktop-base: the widest requested device is the primary/base
frame and each narrower device is a breakpoint frame. The default injected set
is therefore a Desktop base plus a single Mobile (390) breakpoint frame — the
narrower frame(s) only, never a frame at the primary desktop width and never an
auto-added tablet.
add-breakpoint — adds a device-width frame to designs.data.breakpointSet
(Framer defaults: Desktop 1200 / Tablet 810 / Phone 390, or custom 320-3840).
Duplicate widths are ignored.
remove-breakpoint — removes one by id (overrides already written stay in
the document until removed).
set-active-breakpoint — persists the active edit scope to application
state (design-active-breakpoint:<designId>); the UI mirrors it in the
design-selection state (activeBreakpointId). Check the active
breakpoint via view-screen before making responsive-only edits. Its
editScope is cascade-smaller by default (edits at this breakpoint also
apply to every narrower one); pass only when the user explicitly wants a
bounded, breakpoint-only override.
The UI's breakpoint bar (chips above the focused screen) and the overview's
side-by-side linked frames drive the same state — a chip click changes the
iframe viewport width AND calls set-active-breakpoint.
Editing responsively (agent parity)
Use apply-visual-edit with activeFrameWidthPx set to the ACTIVE
breakpoint frame width. This scopes BOTH class and style intents
uniformly through one class-vs-media decision:
- Values that are Tailwind utilities (
text-lg, p-4) become width-scoped
classes: max-[809px]:text-lg (arbitrary max-width variants the Tailwind
CDN JIT compiles to @media (max-width: 809px)).
- Raw CSS values (
137px, rgb(...), calc(...)) persist into the managed
<style data-agent-native-breakpoints> block as
@media (max-width: <bound>px) rules targeting the element's
data-agent-native-node-id. The block is deterministic (wider buckets
first so narrower ranges win by source order) and readable/editable in the
Code panel.
Other targeting options:
maxWidthPx — explicit desktop-down bound, overrides derivation.
intent.kind: "breakpoint-style" — direct managed-media write/remove:
{ target, maxWidthPx, property, value, operation: "set" | "remove" }.
activeBreakpoint: "md" etc. — LEGACY min-width prefix scoping for class
edits only (mobile-first md:text-lg). Still supported for documents
authored mobile-first; prefer activeFrameWidthPx for breakpoint-bar
parity.
When the design has no breakpoint set, activeFrameWidthPx falls back to
the legacy min-width prefix behaviour for class edits. When the active frame
IS the widest context, edits are base writes (correct — the widest frame is
the base).
Resolving and inspecting overrides
effectiveUtilityAtWidth(className, stem, widthPx) — which utility
renders at a width (narrowest applicable max-width scope wins, then
min-width prefixes, then base).
getBreakpointOverrideState({ className, html, nodeId, property, breakpointWidths, baseWidthPx, activeWidthPx }) from
shared/breakpoint-media.ts — aggregates class + media overrides for the
inspector's overridden-at-this-breakpoint indicators.
- To RESET an override: remove the scoped class
(
responsive-class remove with maxWidthPx + stem) or the media rule
(breakpoint-style with operation: "remove"); the base value cascades
back down.
Cautions
- Managed media rules anchor on
data-agent-native-node-id. Inline (SQL)
designs keep these ids; localhost write-back strips them — for local-file
sources prefer scoped classes.
- Never rewrite the managed block wholesale by hand; use the edit intents so
same-property writes replace instead of accumulate. The parser tolerates
Code-panel edits but unknown selectors are ignored.
- Adding a breakpoint between two existing widths changes the bound for NEW
overrides only; previously written overrides keep their original bounds.
1---2name: responsive-breakpoints3description: Framer-style responsive breakpoint editing in Design: one DOM tree per screen with cascading width-scoped overrides. Use when adding/removing breakpoints, editing a design at a specific device width, resolving which value renders at a width, or persisting responsive style changes.4---56# Responsive Breakpoints78Design uses the **Framer model**: every screen is ONE document (one DOM9tree). Breakpoint frames are the SAME document rendered at different10viewport widths — never separate copies. Edits cascade:1112- The **base** is the primary (widest) frame. Base edits are plain13 unprefixed classes / inline styles and cascade down to every narrower14 breakpoint unless overridden there.15- Edits made while a **narrower breakpoint is active** persist as16 width-scoped overrides that apply from just below the next-wider frame17 down to zero. Narrower breakpoints layer their own overrides on top.1819The bound for an override is `next-wider frame width - 1` (e.g. breakpoints20390/810 with a 1280 primary: editing 810 scopes to `≤ 1279px`, editing 39021scopes to `≤ 809px`). `breakpointUpperBoundPx` in22`shared/responsive-classes.ts` is the canonical implementation.2324## Managing breakpoints2526A newly generated design's frame set comes from `generate-design`'s `devices`27param (`("mobile"|"tablet"|"desktop")[]`, default `["desktop","mobile"]`). The28cascade is **desktop-base**: the widest requested device is the primary/base29frame and each narrower device is a breakpoint frame. The default injected set30is therefore a Desktop base plus a single Mobile (390) breakpoint frame — the31narrower frame(s) only, never a frame at the primary desktop width and never an32auto-added tablet.3334- `add-breakpoint` — adds a device-width frame to `designs.data.breakpointSet`35 (Framer defaults: Desktop 1200 / Tablet 810 / Phone 390, or custom 320-3840).36 Duplicate widths are ignored.37- `remove-breakpoint` — removes one by id (overrides already written stay in38 the document until removed).39- `set-active-breakpoint` — persists the active edit scope to application40 state (`design-active-breakpoint:<designId>`); the UI mirrors it in the41 `design-selection` state (`activeBreakpointId`). Check the active42 breakpoint via `view-screen` before making responsive-only edits. Its43 `editScope` is `cascade-smaller` by default (edits at this breakpoint also44 apply to every narrower one); pass `only` when the user explicitly wants a45 bounded, breakpoint-only override.4647The UI's breakpoint bar (chips above the focused screen) and the overview's48side-by-side linked frames drive the same state — a chip click changes the49iframe viewport width AND calls `set-active-breakpoint`.5051## Editing responsively (agent parity)5253Use `apply-visual-edit` with `activeFrameWidthPx` set to the ACTIVE54breakpoint frame width. This scopes BOTH `class` and `style` intents55uniformly through one class-vs-media decision:5657- Values that are Tailwind utilities (`text-lg`, `p-4`) become width-scoped58 classes: `max-[809px]:text-lg` (arbitrary max-width variants the Tailwind59 CDN JIT compiles to `@media (max-width: 809px)`).60- Raw CSS values (`137px`, `rgb(...)`, `calc(...)`) persist into the managed61 `<style data-agent-native-breakpoints>` block as62 `@media (max-width: <bound>px)` rules targeting the element's63 `data-agent-native-node-id`. The block is deterministic (wider buckets64 first so narrower ranges win by source order) and readable/editable in the65 Code panel.6667Other targeting options:6869- `maxWidthPx` — explicit desktop-down bound, overrides derivation.70- `intent.kind: "breakpoint-style"` — direct managed-media write/remove:71 `{ target, maxWidthPx, property, value, operation: "set" | "remove" }`.72- `activeBreakpoint: "md"` etc. — LEGACY min-width prefix scoping for class73 edits only (mobile-first `md:text-lg`). Still supported for documents74 authored mobile-first; prefer `activeFrameWidthPx` for breakpoint-bar75 parity.7677When the design has no breakpoint set, `activeFrameWidthPx` falls back to78the legacy min-width prefix behaviour for class edits. When the active frame79IS the widest context, edits are base writes (correct — the widest frame is80the base).8182## Resolving and inspecting overrides8384- `effectiveUtilityAtWidth(className, stem, widthPx)` — which utility85 renders at a width (narrowest applicable max-width scope wins, then86 min-width prefixes, then base).87- `getBreakpointOverrideState({ className, html, nodeId, property,88 breakpointWidths, baseWidthPx, activeWidthPx })` from89 `shared/breakpoint-media.ts` — aggregates class + media overrides for the90 inspector's overridden-at-this-breakpoint indicators.91- To RESET an override: remove the scoped class92 (`responsive-class` remove with `maxWidthPx` + stem) or the media rule93 (`breakpoint-style` with `operation: "remove"`); the base value cascades94 back down.9596## Cautions9798- Managed media rules anchor on `data-agent-native-node-id`. Inline (SQL)99 designs keep these ids; localhost write-back strips them — for local-file100 sources prefer scoped classes.101- Never rewrite the managed block wholesale by hand; use the edit intents so102 same-property writes replace instead of accumulate. The parser tolerates103 Code-panel edits but unknown selectors are ignored.104- Adding a breakpoint between two existing widths changes the bound for NEW105 overrides only; previously written overrides keep their original bounds.