Studio Docs

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

  1. 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.

  2. Add the import at the top of that file:

    import { registerBreakpoints } from "@contentstack/studio-react";
  3. 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> },
      },
    ]);
  4. 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.

  5. 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.

  6. 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.

  1. defaultDisplayName: label shown on the desktop switcher button
  2. defaultWidth / defaultHeight: canvas iframe size when “default” is active
  3. tabletQuery: CSS media query string (e.g. (max-width: 1024px))
  4. tabletWidth / tabletHeight: preview iframe size at tablet
  5. mobileQuery: CSS media query string (e.g. (max-width: 640px))
  6. 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

PitfallWhy it bitesFix
query set on defaultThrows Default breakpoint should not have a queryOmit the key entirely
First entry not id: "default"Throws First breakpoint must be defaultPut the default entry first
Missing previewSize or query on non-default entriesThrows Breakpoint '<field>' is requiredProvide both for every non-default entry
Switcher not visibleregisterBreakpoints ran after canvas mount, or never ranMove it into the same boot path as registerComponent, before rendering
previewSize mismatched to query rangeCanvas frame misrepresents the devicePick a representative width inside the media-query range
Mobile styles bleeding into desktopBase styles were set while on the Mobile breakpointDesign default first, then override downward

See Also