Skip to content
Browse documentation
Decided

Tablets and adaptive layout

How the app lays itself out on iPads and Android tablets, following the Saymail tablet specification.

Updated: 29 October 2026

The rules are Saymail’s SPECIFICATION-TABLET.md, applied to Incico’s four tabs. They are restated here because they are part of the contract for both apps.

Window classes

One width comparison per platform, reading the window width, never the device type:

ClassWindow widthLayout
Compact< 600 dpPhone layout, unchanged
Medium600 to 1000 dpIcon rail + list pane + detail pane
Expanded> 1000 dpLabelled rail + list pane + detail pane

Rules R3.1 to R3.5 from Saymail apply verbatim: read the window, not the display; a phone-shaped window (an iPad in one-third Split View) renders compact; no userInterfaceIdiom or isTablet checks; the comparison lives in exactly one function per platform (IncicoWindowClass.kt / IncicoWindowClass.swift); crossing a breakpoint at runtime re-lays out without losing state.

Look

The circle-first look (Visual design, A101) carries over unchanged; mocks in mobile/design/redesign/redesign-round-4.html. A selected row in a list pane is tinted with tint and a 3 dp primary edge on its leading side. The rail sits on surface with a hairline on its right; the active destination is a tint pill with the icon and label in primaryText.

Rail

The rail replaces the bottom tab bar in medium and expanded. It holds the four destinations (Contacts, Circles, Services, Profile), the active profile at the top (avatar, first name and “Switch profile” in the labelled rail, the avatar alone in the icon rail; tap to switch profile) and Settings at the bottom (the gear, with its word in the labelled rail). In expanded it shows labels (200 dp); in medium icons only (76 dp), with an expand affordance at the top (“Expand navigation”) that overlays the labelled rail over the panes on a solid scrim (A95). The overlay carries the same header, destinations and Settings; the collapse button (“Collapse navigation”), Back, a tap on the scrim or choosing a destination closes it, and it stays open across a rotation. Rail on surface with a hairline on its right; panes on bg, divided by hairlines.

The list pane opens with the tab’s name (22 sp) and the tab’s own actions beside it, the same as the phone header: Contacts the bell and Find, Circles Add, Services Connect; Profile has none, since Settings is on the rail. The Contacts search field shows the / key that focuses it.

The contact card in the detail pane keeps the phone’s one-page layout with the avatar beside the name and the overflow at the end of that row, no back link (the list beside it is the way back), the sections in two columns at most 560 dp wide, and the keyboard shortcuts under it (“Keys: J / K next and previous · Enter open · / search · F favourite · ? all shortcuts”). Circle details in the detail pane put Preview as Family and the overflow beside the circle’s name, add “sees 9 of your 10 details” to the line under it, and show what the circle sees beside who is in it, with “Drag a person onto a circle on the left to move them.” The circle list in two-pane has no Preview buttons and no section header; the circle shown is tinted. Either pane falls back to one column when it is narrower than two columns need (a medium window).

Panes per tab

TabList pane (fixed 360 dp expanded, 320 dp medium)Detail pane (remaining, ≥ 400 dp)
ContactsTitle with the bell and Find, search field (with a ”/” key hint), circle chips, Needs you, Favourites, Everyone, Requests; “Changed this week” folds into the rows’ second line (“New mobile · today”) to keep the pane shortContact card of the selected connection (the phone’s one-page card, its sections in two columns), or the invitation / sent-request detail, or the empty pane
CirclesTitle with Add, then the list of circles with dot, name and member count; the selected one tinted (A112: list + details stays; the phone’s who-sees-what grid is not used here)Circle details: dot and name, status line, Preview as Family, overflow; then two columns, “What Family sees” (switch list) and “People · 8” with Move people, and the hint “Drag a person onto a circle on the left to move them.”
ServicesConnected services with last-sync and bucket badgesService details: circles, allow-delete, sync button, collapsible buckets, cross-reference entry
ProfileProfile list (main + managed) and the detail categories as a vertical listThe selected category’s editor (names, phones, e-mails, addresses, dates, photos)

Selecting a row in the list pane sets a selection observed by the detail pane; it does not push a route (R7.1). The same holds inside the detail pane: a member opened from a circle’s “People in this circle” keeps the circle selected and shows that contact’s card over the Circles detail pane, under a full-width ”‹ Back to Family” row (A96, the iOS behaviour). That row, the card’s own back arrow and system Back all return to the circle; choosing another circle in the list pane closes the card; the open card survives rotation and process recreation with its circle. Accepting an invitation, by contrast, replaces the invitation with the new card on the Contacts tab, where the invitation was. Modal destinations stay routes in every layout (R7.4): add circle, add service, add profile, send invitation, sync progress, onboarding steps, settings.

Settings is such a route, and inside it the same rule applies: in medium and expanded it takes both panes, with the section list in the list pane and the selected section in the detail pane (Account is selected when it opens, so the pane is never empty). Choosing a section is a selection, not a push; on a phone the section is pushed over the list.

Empty detail pane

One muted line, belonging to the tab that is showing. Contacts: “Pick a contact on the left to see their details.” (Dutch: “Kies links een contact om de gegevens te zien.”). Circles: “Pick a circle on the left to see its details.” (Dutch: “Kies links een kring om de details te zien.”). Services and Profile show their own empty-state line. The contacts sentence never stands in for another tab, because it is about the wrong screen. No illustrations, no promotional text (Saymail removed its “inbox pulse” for exactly this reason).

Pointer, keyboard, drag

  • Hover reveals trailing quick actions (favourite, move circle, remove) as the pointer equivalent of the phone’s swipe actions; gated on pointer presence, not size class. They also show on the row under the keyboard cursor; a resting list shows name, one line and the circle only (round 4).
  • Keyboard (Contacts list, both two-pane layouts; A95): J/K move a keyboard cursor through the rows, drawn as a 2 dp primary focus ring that is distinct from the selection’s tint; Enter opens the row under it in the detail pane; / focuses search; F toggles the favourite of the row under the cursor; ? (Shift+/) shows the shortcut overlay, each line read as “J: Next contact”. The list takes keyboard focus when the tab opens. Shortcuts are inert while a text field has focus and whenever Ctrl, Alt or Meta is held, so system chords keep working; Cmd/Ctrl+Enter to submit a form is left to the form.
  • Drag (expanded only, A95): on the Circles tab, where the circle list and the selected circle’s details are side by side, a long press on a member row in “People in this circle” picks the contact up; a card with their name and “Drop on a circle to move” follows the pointer, the circle under it is outlined and announced as “Move to Work”, and the circle the contact is already in is no target. Releasing over a circle asks the Move sheet’s own question (“Move Anna to Work?” with what Work will now see or stop seeing) every time, because a drop is easy to make by accident and changes what the contact can see; nothing is written until the person confirms. The Move button on each member row stays the accessible way to do the same.
  • Multi-select in the Contacts list (two-pane only, A95): a long press, or a Ctrl-click, ticks a connection row; Shift-click ticks the range from the row ticked last. While anything is ticked every connection row shows a checkbox (“Select Anna”), a plain click ticks rather than opens, and a bar above the list says “2 selected” (a polite live region) with Select all, Clear, Move to circle and Remove. Move to circle opens the Move sheet in bulk mode (a mixed selection is offered every circle) and is sent at once; Remove asks first (“Remove 2 contacts? …”). Each batch shows one Undo (“2 contacts moved to Work”, “2 contacts removed”). Undoing a move puts every contact back in the circle it came from, and says “Not everything could be undone. Check your contacts.” if any of those writes is refused. A removal cannot be undone on the server, because it removes both sides of the connection, so the rows leave the list at once and the removals are sent only when the six-second undo window closes, when the Undo is put away, or when the next batch starts; Undo inside the window sends nothing. Invitation rows take no tick; the keyboard cursor still visits them.

State preservation

Selected item id, scroll positions, collapsed sections, search text and multi-selection survive rotation, breakpoint changes and process recreation (R16). A breakpoint change recomposes the tab in a different place in the tree, so per-tab saveable state hangs off one holder owned by the shell rather than off the layout, and the Contacts tab keeps its search text and collapsed groups in its view model, which both layouts read. Rotating while reading a contact card never pops or re-fetches.

Phone landscape

Out of scope: iPhone stays portrait-locked; Android phones in landscape keep the compact layout.

Deliberate platform divergence

As in Saymail: Android disables row swipe in two-pane layouts so horizontal drag can be used for reassigning; iOS keeps swipe because its list has no drop target on the same screen. iOS multi-select does not use native EditMode, to keep both platforms identical.