Skip to main content

Mobile & responsive

Several nodes adapt to narrow screens. All of it is opt-in on the model and degrades gracefully without JavaScript.

Tables → stacked cards​

Set stackOnMobile and a wide table becomes, on a narrow screen, one card per row with Column: value lines (the header row is hidden; each cell prints its column name):

UiTable.of("products", "Products")
.column(UiColumn.of("name", "Name"))
.column(UiColumn.of("price", "Price"))
.row(Map.of("id", "p1", "name", "Widget", "price", "€ 19.00"))
.stackOnMobile(true);

Live, in a frame deliberately kept below 640px — so the media query is in effect and you see the real thing, not a sketch of it:

The header row is gone; every cell prints its own column name.

Each <td> carries a data-label (the column label) that the CSS shows via a ::before, so it works in both the SSR and SPA output and needs no script.

Tabs → wrap or a "⋯ More" menu​

Tab bars wrap onto more rows by default when they don't fit. For a single-row bar that collapses the overflow into a dropdown instead, set tabOverflow: MENU:

UiSection.of("main", null)
.section("overview", "Overview", overviewBody)
.section("orders", "Orders", ordersBody)
// …many tabs…
.tabOverflow(UiSection.TabOverflow.MENU);

It works out of the box: mount your page through a SuiEventBus and the tabs that don't fit collapse into a trailing ⋯ dropdown on their own — it re-computes on resize. Without JS the bar simply wraps, so it stays usable either way.

const bus = new SuiEventBus(renderer, root);
renderer.mount(page); // tab overflow is handled automatically

Button bars → wrap or a "⋯ More" menu​

The button bar of a list's or table's header and the footer of a form or a detail wrap the same way by default. A button's label is always one line, so a bar that runs out of room wraps whole buttons — it never breaks "Back to the list" over three lines inside one. For a bar that stays one row and folds the buttons that do not fit into the same ⋯ dropdown the tabs use, set actionsOverflow(Overflow.MENU):

UiList.of("mail", "Inbox")
.action(UiAction.secondary("back", "Back to the list"))
.action(UiAction.secondary("assistant", "Assistant").icon("sparkles"))
.action(UiAction.primary("write", "Write").icon("plus"))
.actionsOverflow(Overflow.MENU);

The buttons fold from the end, so put the one that must stay visible first. In a header the bar shares the row with the title while it fits; on a phone it takes a row of its own under the title, measured against the full width, and a search box in the header (headerExtra) keeps its own row below. A footer's links stay where they are; only its buttons and menus fold. Without JS the bar wraps, as before.

The sidebar UiMenu has its own responsive story — a RESPONSIVE mode that is a push rail on desktop and an overlay drawer on mobile. See menu.


Do I still need the wire… helpers?

As a normal consumer — no. The SuiEventBus watches its root and runs these enhancers itself after every render, so tab overflow, menu-button popovers and the sidebar's persisted collapse state all just work once you've mounted through a bus.

The functions stay exported only as an escape hatch for the rare case of rendering without a bus — e.g. progressively enhancing a static, server-rendered page with a sprinkle of JS but no full SPA runtime. Then you'd call the one you need once after the markup is in the DOM. If you use the bus (the usual case), you can forget they exist.