"It works on desktop" isn't enough anymore. The same Fiori app gets opened on a phone in a warehouse, a tablet in a meeting, and a widescreen monitor at a desk. SAPUI5 has real machinery for adapting to all three — but it doesn't happen by accident. The controls you choose determine how gracefully your app reshapes itself.
This post covers the three workhorses of responsive UI5 design: FlexibleColumnLayout, DynamicPage, and the Grid — plus the habits that make them work.
FlexibleColumnLayout: the master-detail standard
If your app has a master-detail flow (and most Fiori apps do), sap.f.FlexibleColumnLayout is the container to build it in. It manages up to three columns — Begin, Mid, End — and automatically collapses them based on screen width.
<f:FlexibleColumnLayout id="fcl"
layout="TwoColumnsMidExpanded"
stateChange="onStateChange">
<f:beginColumnPages>
<mvc:XMLView viewName="my.app.view.Master"/>
</f:beginColumnPages>
<f:midColumnPages>
<mvc:XMLView viewName="my.app.view.Detail"/>
</f:midColumnPages>
</f:FlexibleColumnLayout>
The magic is in the layout property and the framework's layout breakpoints. On a desktop, TwoColumnsMidExpanded shows master and detail side by side. On a phone, the same layout value renders as a single full-screen column with automatic back navigation — the framework handles the column-to-fullscreen translation, including the arrow button to go back.
You rarely set layout values by hand. The standard pattern uses the FlexibleColumnLayoutSemanticHelper, which computes the right next layout from the current one:
_onOrderSelect: function (oEvent) {
var oFCL = this.byId("fcl");
var oHelper = this._getFclHelper(oFCL);
var oNextUIState = oHelper.getNextUIState(1); // 1 = show detail
oFCL.setLayout(oNextUIState.layout);
this.getOwnerComponent().getRouter().navTo("detail", {
orderId: sOrderId,
layout: oNextUIState.layout
});
}
Storing the layout in the route (:layout: pattern parameter) keeps the back button and bookmarks working — deep-linking into a detail view restores the right column arrangement.
FCL's real value isn't the columns — it's that phone behavior comes free. Master-detail navigation that would need custom code in a SplitApp just works, including the back button, because the control owns the responsive logic.
DynamicPage: headers that collapse gracefully
The sap.f.DynamicPage solves a different problem: the object page with a big header (title, attributes, KPIs, tabs) that needs to stay usable while scrolling through long content. Its header has two states — expanded and snapped — and it pins the title when collapsed.
<f:DynamicPage id="detailPage"
toggleHeaderOnTitleClick="true">
<f:title>
<f:DynamicPageTitle>
<f:heading>
<Title text="{OrderID}"/>
</f:heading>
<f:actions>
<Button text="Edit" press="onEdit"/>
</f:actions>
</f:DynamicPageTitle>
</f:title>
<f:header>
<f:DynamicPageHeader pinnable="true">
<!-- Object attributes, KPI tiles, micro charts -->
<f:content>
<FlexBox wrap="Wrap">
<ObjectAttribute title="Customer" text="{CustomerName}"/>
<ObjectAttribute title="Status" text="{Status}"/>
<ObjectNumber number="{NetAmount}" unit="{Currency}" title="Net Value"/>
</FlexBox>
</f:content>
</f:DynamicPageHeader>
</f:header>
<f:content>
<IconTabBar>
<!-- line items, attachments, notes -->
</IconTabBar>
</f:content>
</f:DynamicPage>
On scroll, the header collapses to just the title row — actions stay reachable, content gets the space. On phones this matters enormously: a 400-pixel header on a 700-pixel viewport leaves no room for actual content. The pinned title means users never lose context about which object they're looking at.
The pinnable header lets users pin it open if they want the KPIs visible while scrolling — a small touch, but the kind that makes power users happy.
Grid: responsive form layouts without media queries
For everything that isn't master-detail or object-page — dashboards, forms, overview pages — the sap.ui.layout.Grid gives you a 12-column responsive grid with zero CSS:
<l:Grid defaultSpan="XL3 L3 M6 S12" class="sapUiSmallMarginTop">
<l:content>
<GenericTile header="Open Orders" subheader="This month" frameType="OneByOne">
<TileContent>
<NumericContent value="47" icon="sap-icon://sales-order"/>
</TileContent>
</GenericTile>
<GenericTile header="Overdue" subheader="Needs attention" frameType="OneByOne">
<TileContent>
<NumericContent value="6" valueColor="Error" icon="sap-icon://alert"/>
</TileContent>
</GenericTile>
<!-- more tiles... -->
</l:content>
</l:Grid>
defaultSpan="XL3 L3 M6 S12" reads as: on extra-large and large screens each tile takes 3 of 12 columns (4 across), on medium 6 (2 across), on small 12 (full width, stacked). One attribute, four layouts. That's the whole responsive story for card-based pages.
For forms, pair the Grid with sap.ui.layout.form.SimpleForm, which has its own responsive layout="ResponsiveGridLayout" — labels above fields on phones, beside fields on desktop, handled automatically.
Reach for the Grid whenever you're tempted to write CSS media queries in a UI5 app. The framework's breakpoints (S/M/L/XL) are tested across the control library; hand-rolled breakpoints fight the controls instead of working with them.
Habits that make it all work
Test at 3 widths, not 30. Phone (~375px), tablet (~768px), desktop (~1440px). If it works at those three, the in-between sizes almost always behave. Browser dev tools device emulation is fine for layout checks.
Hide, don't squish. A table with 8 columns on a phone is unreadable whether you shrink or scroll. Use minScreenWidth on columns to drop the less important ones on small screens, or demandPopin to reformat them as stacked labels. Prioritize: which 3 columns would a phone user actually need?
Touch targets matter. Fiori's cozy/compact density helps — sap.m.List items and buttons are comfortably tappable in cozy mode. If your app targets phones, don't force compact density globally; let the device decide via the standard density helper in Component.js.
Navigation patterns differ. On desktop, master-detail shows both columns; the user selects and inspects. On phones, it's a drill-down: list → tap → detail → back. FCL handles this, but your content should respect it too — don't put critical actions only in the master column's toolbar where phone users might miss them.
Images and charts need explicit care. Layout controls adapt; content doesn't always. A vizFrame chart needs its own responsive handling, and large images should use densityAware or CSS max-width. Test charts on phones — legends and axis labels are the usual casualties.
Bottom line
Responsive UI5 isn't a separate mode you bolt on — it's a consequence of choosing the right containers. FCL for master-detail, DynamicPage for object pages, Grid for everything else. Pick those three well and the phone/tablet/desktop adaptations mostly take care of themselves; fight them with custom layouts and you'll be debugging breakpoints forever.