When to Use
Register default/tablet/mobile breakpoints at SDK boot and verify the canvas breakpoint switcher renders.
Use when wiring responsive viewports into a Studio host app, e.g. “add tablet/mobile breakpoints”, “the breakpoint switcher is missing from my canvas toolbar”, or validating a breakpoint config against BreakpointInput. Run AFTER install-studio and setup-section-preview. The switcher only appears once the canvas mounts and the registry has at least two breakpoints.
Auth preflight: settle the credential before the first API call. Resolve it OAuth-first per authenticate-cma: CS_OAUTH_ACCESS_TOKEN, else the Contentstack MCP’s stored session. Never ask the user for a session authtoken. If nothing resolves, or a refresh fails with 400 invalid_refresh_token, hand them ! CONTENTSTACK_REGION=<code> npx @contentstack/mcp --auth (it needs a TTY and a browser, so it cannot be run for them) and wait. 403 error_code 316 is a valid credential aimed at another org: fix the org or the api_key, do not re-authenticate.
Register Breakpoints (default + Tablet + Mobile)
Context
Studio’s canvas can preview a composition at multiple viewport sizes. The host app declares those viewports once at bootstrap by calling registerBreakpoints from @contentstack/studio-react. The first entry is always the default breakpoint (no query key). Subsequent entries carry a raw CSS media query string plus a previewSize used to resize the canvas iframe.
The shape comes from @contentstack/studio-registry (breakpoint-registry.type.ts):
interface Breakpoint {
id: string;
displayName: string;
query: string; // raw CSS media query
previewSize: { width: number; height: number };
}
// Input tuple: first entry MUST be the default and MUST NOT carry `query`.
type BreakpointInput = [DefaultBreakpoint, ...PreProcessedBreakpoint[]];
The registry tracks unique ids and unique displayNames. Duplicates throw. The call must run before any <StudioCanvas /> mounts, so it belongs in the same bootstrap module as registerComponent / registerDesignTokens (commonly src/studio/bootstrap.ts or wherever your app shell already imports @/lib/contentstack).
Reference doc: docs/bring-your-own-components/configure-custom-breakpoints.md.
Task
Locate the bootstrap module: by content, not by path. grep -rln "registerComponent" src app lib 2>/dev/null (path lists produce false negatives on src/app, route groups, and locale segments). Typical homes are ( src/studio/bootstrap.ts, src/studio/registerComponents.ts, or whatever the project pulls in from @/lib/contentstack). If none exists, create src/studio/bootstrap.ts and import it from the app shell (app/layout.tsx, src/main.tsx, app/root.tsx, etc.) for its side effects.
Add the import at the top of that file:
import { registerBreakpoints } from "@contentstack/studio-react";Call registerBreakpoints before any canvas surface renders. Use the user-provided values. The structure must be:
registerBreakpoints([ { id: "default", // required literal displayName: "<defaultDisplayName>", // NO `query` key on default — registry throws if present previewSize: { width: <defaultWidth>, height: <defaultHeight> }, }, { id: "tablet", displayName: "Tablet", query: "<tabletQuery>", previewSize: { width: <tabletWidth>, height: <tabletHeight> }, }, { id: "mobile", displayName: "Mobile", query: "<mobileQuery>", previewSize: { width: <mobileWidth>, height: <mobileHeight> }, }, ]);Confirm uniqueness. Every id and every displayName in the array must be unique. The registry maintains a uniqueNames set and throws Duplicate breakpoint name '<id>' on conflict.
Restart the dev server. Studio reads the registry once at boot. HMR updates may not re-register. A clean restart is the only reliable way to pick up a new array.
Verify in the canvas. Open a composition in Studio canvas and walk through the verification checks below.
Inputs Needed From the User
In this order. Defaults above are sensible if the user is unsure. Keep going with them unless they explicitly want different values.
- defaultDisplayName: label shown on the desktop switcher button
- defaultWidth / defaultHeight: canvas iframe size when “default” is active
- tabletQuery: CSS media query string (e.g. (max-width: 1024px))
- tabletWidth / tabletHeight: preview iframe size at tablet
- mobileQuery: CSS media query string (e.g. (max-width: 640px))
- mobileWidth / mobileHeight: preview iframe size at mobile
Pick representative device widths inside each media-query range. A (max-width: 640px) rule with a 1024-wide previewSize will mislead the author.
Acceptance
This skill succeeds only when ALL of the following hold. If any fails, surface the failure and stop.
- registerBreakpoints is imported from @contentstack/studio-react in the bootstrap module
- The call runs at module load (no lazy wrapping in a component effect)
- First entry has id: "default" and NO query key (the doc example showing query: "" is wrong. Omit the key entirely: the registry rewrites it to "*" internally)
- Every id and displayName is unique
- Each non-default entry has both query and previewSize
- Canvas toolbar renders three buttons with the configured displayNames
- Clicking each switcher resizes the canvas iframe to the matching previewSize
- Opening the Design panel on any component shows breakpoint-scoped overrides. Switching the breakpoint changes the active slot
- A canvas iframe screenshot at each breakpoint (not just an a11y snapshot: iframe contents are opaque to it) shows the composition reflowing as expected
Common Pitfalls
| Pitfall | Why it bites | Fix |
|---|---|---|
| query set on default | Throws Default breakpoint should not have a query | Omit the key entirely |
| First entry not id: "default" | Throws First breakpoint must be default | Put the default entry first |
| Missing previewSize or query on non-default entries | Throws Breakpoint '<field>' is required | Provide both for every non-default entry |
| Switcher not visible | registerBreakpoints ran after canvas mount, or never ran | Move it into the same boot path as registerComponent, before rendering |
| previewSize mismatched to query range | Canvas frame misrepresents the device | Pick a representative width inside the media-query range |
| Mobile styles bleeding into desktop | Base styles were set while on the Mobile breakpoint | Design default first, then override downward |
See Also
- docs/bring-your-own-components/configure-custom-breakpoints.md for the authoring model and override semantics.
- install-studio and setup-section-preview for the prerequisite setup.
- troubleshoot-canvas if the switcher never appears even after a clean restart.