DocumentationComponentsTheme Creator
Theme CreatorGitHubIntroduction
InstallationUsageThemingMotion
Components
BreadcrumbTabsPaginationStepperMenu ItemToolbarMenubarNavigation MenuAccordionCollapsibleCommandBottom NavTop Header MobileTop Header DesktopSidebar
AI agentsContributing
  1. Components
  2. Top Header Mobile

Top Header Mobile

Mobile top app bar with leading/trailing slots, centred or start-aligned title, subtitle, six variants, five sizes, tokened bottom rounding, three layout modes, scroll elevate/collapse behavior, search and sub-row decks, Surface tone, and full RTL support.

$ pnpm add @tessinaui/ui

When to use

  • The top of a phone screen. A bar that names where you are, offers a way back or to a menu, and holds the few actions that belong to this screen.
  • A short title. One or two words that describe the screen, not the app's name. Set titleAs="span" when the page H1 lives in the content.
  • A handful of actions, one of them prominent at most. Keep the rest ghost and move overflow into a DropdownMenu.
  • A bar that follows the content. Use sticky with scrollBehavior so the bar lifts, or a large title folds to inline, as content scrolls under it.
  • A second deck under the title. Compose a Search field into search, or Tabs or a Segmented Control into subRow.

When not to use

  • On a desktop-width page. Use Top Header Desktop.
  • For an app's top-level destinations. Use a Bottom Nav on a phone or a Sidebar on a wide screen; the bar hosts a menu trigger only.
  • For a sheet or modal header. Top rounding and a grabber belong to a Drawer or an Action Sheet.
  • For a full-screen takeover menu or a command palette. Use a Drawer or Command; the bar holds the trigger.
  • For a screen with no title, navigation or actions. Leave the bar out rather than render an empty one.
  • To stack many similar actions. More than a few icons crowd the title; use a menu.

Installation

Terminal
pnpm add @tessinaui/ui

Usage

example.tsx
import { TopHeaderMobile } from "@tessinaui/ui";
import { IconButton } from "@tessinaui/ui";
example.tsx
<TopHeaderMobile
  bordered
  title="Inbox"
  subtitle="3 unread"
  leadingAction={<IconButton variant="ghost" size="sm" icon={<ArrowLeft />} aria-label="Back" />}
  trailingActions={<IconButton variant="ghost" size="sm" icon={<Search />} aria-label="Search" />}
/>

Examples

Default

Back, dead-centred title, one trailing action — the dominant single-row shape, composed from library IconButtons. The second bar names the back destination (a ghost Button in the same slot).

Variants

Six paints — filled, elevated, transparent, blur, primary, on-color. The coloured variants publish the Surface tone, so nested library controls re-ink themselves. blur paints opaque inside a coloured <Surface>, where its glass would be mostly the plate and a focus ring would lose its ground.

Sizes

Bar min-heights 48 / 56 / 64 / 80 / 96px. xs, sm and md are the three heights that actually recur in shipping mobile bars: 48px for a dense shell, 56px for a standard global nav, 64px once the row carries a filled action.

Layout modes

small keeps the title inline; medium and large drop it below the action row. Content-hugging — a subtitle simply grows the bar, and the large row wraps up to two lines instead of truncating.

Scroll behavior

scrollBehavior="elevate" paints a hairline + shadow once content scrolls under the sticky bar; "collapse" additionally folds a medium/large bar to the inline title — the large-title fold. An IntersectionObserver sentinel drives both — no scroll listeners.

Subtitle

Presence, status, count or context under the title — in centred, start-aligned and large layouts. The chat header composes an Avatar into the title cluster.

Title alignment

center holds the title dead-centre regardless of the side clusters; start left-aligns after the leading slot. Both slots are optional.

Title menu

The title as a dropdown trigger — the feed/workspace switcher pattern. Pure composition with DropdownMenu; the hand-rolled trigger carries its own 44px touch band.

Search

The search slot renders its own deck under the action row. The deck is a plain padded box — the field you compose owns the search landmark, so Search brings its own and nothing is nested. Passing a bare <input>? Wrap it yourself in <search> or <form role="search">. For a field that replaces the title, pass it as the title.

Sub row

The structured second deck — tabs, segmented control, or a scrolling chip rail — padded to the bar's own gutter.

Transparent over media

The bar paints nothing; the actions carry their own fill (chip-style IconButtons), tone="on-color" re-inks any text, and the H1 stays in the content.

Select mode

Cancel + live count + one prominent Done — pure composition, no mode prop. The three-control row needs about 320px of bar width; in a narrower column drop the count or move Cancel into a leading icon.

Commerce

Brand mark in the title slot, a filled CTA, wishlist and a cart count on IconButton's own badge.

Skip link

WCAG 2.4.1: TopHeaderMobileSkipLink hides until keyboard focus, then pins over the bar's leading corner. Click into the demo and press Tab.

On color

A transparent bar inherits the tone around it, so over a coloured Surface its title and icon buttons take the plate's ink. primary and on-color publish on-color themselves, and blur turns opaque on a coloured plate.

Loading

The skeleton mirrors the bar's box — same cva, same size maps, icon circles on IconButton's size tokens, text bars sized in characters.

RTL

Set dir="rtl" and the row mirrors: the back control sits at the right and the trailing actions at the left. The back icon is yours, so pass one that points right. Leave dir unset and the row follows the page.

API Reference

TopHeaderMobile

PropTypeDefaultDescription
variant"filled" | "elevated" | "transparent" | "blur" | "primary" | "on-color""filled"Visual style. primary/on-color publish the on-color Surface tone
size"xs" | "sm" | "md" | "lg" | "xl""md"Bar min-height 48/56/64/80/96px
rounded"none" | "sm" | "md" | "lg" | "full""none"Bottom-corner rounding; full = the tokened panel scale
titleReactNode—Title text or custom element
titleAs"h1" | "h2" | "h3" | "span""h1"Heading element for a string title. Use "span" when the page H1 lives in the content
titleAlign"start" | "center""center"Inline-title alignment
subtitleReactNode—Second line under the title (status, presence, context)
leadingActionReactNode—Leading slot (back/menu control)
trailingActionsReactNode—Trailing slot (icon buttons, text actions)
layout"small" | "medium" | "large""small"Inline title, or title below the row at two scales
collapsedboolean—Fold medium/large to inline. Leave unset with scrollBehavior="collapse" for self-folding; set it and the prop wins
stickybooleanfalsesticky top-0 + safe-area top inset
borderedbooleanfalseHairline bottom border
scrollBehavior"none" | "elevate" | "collapse""none"Scroll response — elevate chrome, or elevate + collapse. Pair with sticky
searchReactNode—Search deck row under the bar; a plain padded box, the composed field owns the search landmark
subRowReactNode—Structured second deck (tabs, segmented, chips)
tone"default" | "on-color"autoSurface tone for nested controls; auto-detected from variant, override for transparent-over-media
skeletonbooleanfalseSwap the bar for its skeleton
renderuseRender.RenderProp—Replace the rendered <header> element
dir"ltr" | "rtl"—Text and layout direction

asChild was removed — it was declared but never implemented and leaked onto the DOM. Use render instead.

TopHeaderMobileSkeleton

PropTypeDefaultDescription
variant / size / rounded / layout / borderedsame as the bar—The skeleton is the same box
showLeadingbooleantrueLeading circle
trailingCountnumber (0–3)1Trailing circles
titleCharsnumber12Title bar width, in characters of the real title
showSubtitlebooleanfalseSubtitle line

TopHeaderMobileSkipLink

PropTypeDefaultDescription
hrefstring"#content"Jump target. Render it first inside leadingAction

Keyboard navigation

KeyAction
Tab / Shift+TabMove focus through the slot controls in DOM order (skip link first when present)
Enter / SpaceActivate the focused control

The bar itself is not interactive and never traps focus; arrow-key behavior belongs to composed widgets (Tabs, SegmentedControl, DropdownMenu).

Accessibility

VoiceOver + Safari: passed 2026-09-14. The bar reads "Back, button", "heading level 1, Inbox", "Search, button"; the second example reads "Orders, button" and "heading level 1, Order #8104". NVDA and JAWS are untested.

  • Renders a plain <header> with no hardcoded role="banner" — the banner landmark applies only at body level (APG); nested bars stay generic.
  • A string title renders as titleAs (default h1). Set "span" for actions-only bars so the content owns the document outline.
  • The search slot is a plain padded box — no landmark of its own, so a composed Search (which renders role="search") is not nested inside a second one. Wrap a bare <input> yourself if you want the landmark.
  • TopHeaderMobileSkipLink provides the WCAG 2.4.1 bypass block.
  • Slot controls must carry aria-labels; library IconButton enforces the 44px touch floor on phones.
  • The skeleton is one role="status" region announced by sr-only text.
  • The large-title fold animates opacity only, gated by motion-safe.
  • RTL: dir="rtl" flips the row; the title cluster right-aligns.

Boundaries

  • Trailing actions: two icons is the working cap (one if it is filled); the observed mobile mode is two, max five. Keep to ≤3 and push overflow into a DropdownMenu — never an in-bar auto-overflow.
  • One prominent action. At most one filled/text-emphasized action, on the trailing side. Everything else stays ghost — two prominent actions in one bar leaves neither reading as the primary one.
  • Back vs Close are different verbs. Back returns within a flow; Close dismisses a modal context. A sheet header (top rounding + grabber) belongs to Drawer/ActionSheet, not this bar.
  • Full-screen takeover menus belong to Drawer/Command — the bar hosts the trigger only.
  • Bottom rounding only. A top bar never curves its top edge: curvature there implies expandability, and the edge is flush with the status bar anyway. Our rounded axis applies to the bottom edge only, tokened through §2.
  • Non-sticky bars at the very top of a standalone PWA still sit under the status bar — add pt-[env(safe-area-inset-top)] via className there, or use sticky.
Bottom NavTop Header Desktop

On this page

When to useWhen not to useInstallationUsageExamplesDefaultVariantsSizesLayout modesScroll behaviorSubtitleTitle alignmentTitle menuSearchSub rowTransparent over mediaSelect modeCommerceSkip linkOn colorLoadingRTLAPI ReferenceTopHeaderMobileTopHeaderMobileSkeletonTopHeaderMobileSkipLinkKeyboard navigationAccessibilityBoundaries