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

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