Home/Blocks/Bottom tabs

Bottom tabs

The primary phone-resolution nav for every portal — 3 to 5 thumb-reachable destinations at the bottom of the viewport. Replaces the nine bespoke .gd-tabs / .ow-tabs / .pa-tabs etc. shipped by individual portals.

Stable .b-bottom-tabs Phone only

Default

Four tabs, one active. Each tab is a stacked icon + label, ≥ 52 px tall so the touch target clears the iOS / Android guidance.

A page renders here. Body content gets padding-bottom equal to tab-bar height + safe-area inset so the last row clears the bar.
4 tabs with badge.b-bottom-tabs

Parts

PropertyValueNotes
height64 px + safe-areaIncludes env(safe-area-inset-bottom) on iOS
min tap target52 × 52 pxiOS HIG floor; Android Material recommends 48
tab count3–54 is the canonical sweet-spot; 5+ goes into a "More" sheet
active colour--brand-strong (label), --brand (icon)Inherits per-portal theme
inactive colour--text-subtleHover lifts to --text-strong
badge--tone-danger pill, top-rightFor unread counts; suppress at 0
elevation0 -6 18 px -8 ink @ 10%Subtle lift off the canvas
visibility≤ 900 px viewportHidden ≥ 901 px where sidebar takes over

Positioning

The contract for where this block lives in the DOM.

This block is position: fixed; left: 0; right: 0; bottom: 0. Place it as a direct child of <body> (or the demo phone-stage shell), never nested inside a scrolling container — the fixed positioning needs the viewport as its containing block, and a nested ancestor with transform or filter applied will silently turn the bar into position: absolute relative to that ancestor.

For doc-page previews that need to render the bar inside a 280-px stage frame, override locally with position: absolute on the stage child (as this page does). A roadmap modifier (.b-bottom-tabs--inline) is tracked under audit A-3 to ship this as a first-class variant.

Pad page content with padding-bottom: calc(64px + env(safe-area-inset-bottom)) so the bar never covers the last row.

"More" overflow

When the portal has 5+ destinations, the fifth tab becomes "More" and opens an x-bottom-sheet with the remaining items as a list. Never spill past 5 tabs in the bar itself.

Do & don't

Do

Keep labels to 1 word. "Notes" not "Notifications". The bar must stay scannable in a thumb-flick.

Don't

Pack more than 5 tabs. Touch targets compress, labels truncate. Use a "More" sheet instead.

Do

Pad page content with padding-bottom: calc(64px + env(safe-area-inset-bottom)) so the bar never covers the last row.

Don't

Show the bar on tablet / desktop. ≥ 901 px the app shell takes over — the bar would compete with the sidebar.

Do

Carry unread badges. The bar is the operator's only signal at phone resolution that something needs attention elsewhere in the app.

Don't

Animate the active-state transition. The tap is the feedback; movement is noise.

Accessibility

  • Wrap with <nav aria-label="Primary"> so screen readers announce the landmark.
  • Each tab is a <button> (or <a> if it routes via URL). The active tab carries aria-current="page".
  • The badge gets a visually-hidden context label: <span class="sr-only">2 unread</span>. Pure numbers read as ambiguous out of context.
  • Tabs must be reachable by keyboard tab order even though the bar is phone-first — desktop users still hit it on narrow windows.