Skip to content

Make a plugin work on desktop, tablet, and mobile ​

What you're doing ​

The platform is mobile-first: a page you author with no responsive settings renders as a single stacked column and already works on a phone. This guide is about the deliberate adjustments — hiding a column on small screens, a desktop-only side panel, a device-restricted menu item.

The default you get for free ​

  • Rows and columns collapse to a single column below the sm breakpoint.
  • A core.grid becomes horizontally scrollable inside its own container.
  • KPI rows wrap.

So the tutorial pages — header, KPI row, filter row, grid, dialogs — are usable on mobile with zero extra work. Verify that's true before adding overrides.

The breakpoint model ​

  • The base breakpoint is xs (mobile). It has no key inside breakpointOverrides — its own hidden state is the sibling property hiddenAtXs on the row / column / item.
  • Wider breakpoints are sm, md, lg, xl, set under breakpoints:
json
{
  "id": "item-3",
  "kind": "block",
  "hiddenAtXs": true,
  "breakpoints": {
    "md": { "hidden": false },
    "lg": { "span": 4 }
  },
  "block": { "...": "..." }
}

Read that as: hidden on mobile, shown again from md up, and 4/12 columns wide from lg up. This is how you express "desktop-only content" — author it hidden at xs, re-show it at a wider breakpoint.

  • spanAtXs (on a column) is an independent 1–12 width for mobile, not a mirror of sm.

The complete example — a detail panel that's a dialog on mobile, a column on desktop ​

The tutorial keeps it simple: detail is always a core.dialog, which works everywhere. If you want a side-by-side layout on desktop, put the detail content in a second column that is hiddenAtXs: true with breakpoints.lg.hidden: false, and keep the dialog for xs–md.

json
"rows": [
  { "id": "row-main", "columns": [
    { "id": "col-list", "items": [ /* grid */ ], "spanAtXs": 12, "breakpoints": { "lg": { "span": 7 } } },
    { "id": "col-detail", "hiddenAtXs": true, "breakpoints": { "lg": { "hidden": false, "span": 5 } },
      "items": [ /* detail card bound to page.detailRecord */ ] }
  ] }
]

Device-restricted menu items ​

A menu node's visibility.devices:

json
"visibility": { "visible": true, "enabled": true, "devices": ["desktop", "tablet"] }

[] = every device. Use this for a bulk-admin screen you don't want on phones.

Page variants (advanced) ​

A page can carry variants — alternate layouts selected by role, device class, or a route param:

json
"variants": [
  { "id": "mobile", "rule": { "deviceClass": "mobile" }, "rows": [ /* a trimmed layout */ ] }
]

Use this only when the mobile experience is genuinely a different screen, not just a reflow. For most list pages the default stacking plus a couple of hiddenAtXs flags is enough.

How to verify it worked ​

erp plugin test validates the breakpoint shape. For the real check, publish and open the page in the ERP, then use your browser's device-emulation to switch between phone, tablet, and desktop widths. Confirm:

  • nothing overflows the viewport horizontally (the page body must never scroll sideways — only inner containers);
  • every action reachable on desktop is reachable on mobile (even if in a different place);
  • text stays legible (no fixed pixel widths forcing tiny text).

Common mistakes ​

SymptomCauseFix
page scrolls sideways on mobilea fixed-width block or minWidth on a containeruse relative units; let the grid scroll inside its own container
xs override ignoredput an xs key inside breakpointOverridesthere is no xs key there — use hiddenAtXs / spanAtXs
desktop-only panel shows on mobileset breakpoints.lg.hidden:false without hiddenAtXs:trueyou must hide at xs first, then re-show
a dialog is unusable on a phonefixed minWidth: 520px on the dialog contentuse max-width: 100% / flexible widths