Top App Bar
- Stable
- WCAG 2.2 evidence
- RTL
- Web · iOS · Android
The header of a mobile screen: a leading Back, Menu or Close control, the screen
title as a heading, and actions. A header element, so it is the page's banner landmark when it sits directly in the page
layout (not inside main, article or section). The title is a heading of the level you choose (WCAG 1.3.1, 2.4.6); the
leading control is a button, or a link with leadingHref, at least 44 pt (iOS) or 48 dp (Android) square (2.5.8) with a
visible focus ring (2.4.7); the back chevron and arrow mirror in right-to-left layouts (1.3.2). The bar clears the status
bar through env(safe-area-inset-top) (needs viewport-fit=cover). Scroll-state elevation exists only behind isElevated and fades in only when motion is allowed. Position (sticky top-0)
is yours. Figma advises against it on web pages (use a page header there). Consumer duties: titles that match the screen,
aria-labels on the action buttons, and a heading level that fits the page outline.
import { TopAppBar } from "@nexera-ui/react";- 9
- examples
- 11
- props
- 5
- live controls
- 3
- platforms
- 11
- WCAG criteria
- 6
- blocks use it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { TopAppBar } from "@nexera-ui/react";
<TopAppBar />Examples 8
The same examples as Storybook, rendered live. Open Code to copy one.
Android
Figma Platform=Android: a 64 px bar, the title in Heading/H2 at the start and an arrow for Back.
import { IconButton, TopAppBar } from "@nexera-ui/react";
import { LuEllipsis, LuSearch } from "react-icons/lu";
function Actions({ platform }: { platform: Platform }) {
return (
<>
<IconButton aria-label="Search" icon={<LuSearch />} variant="ghost" platform={platform} />
<IconButton
aria-label="More actions"
icon={<LuEllipsis />}
variant="ghost"
platform={platform}
/>
</>
);
}
export function Android() {
return (
<TopAppBar
title="Leave request"
leadingLabel="Back"
leading="back"
actions={<Actions platform="android" />}
/>
);
}
Large
Figma Variant=Large: the large title (Heading/H1) under the bar is the heading. Elevate to see the small title appear.
import { IconButton, TopAppBar } from "@nexera-ui/react";
import { LuEllipsis, LuSearch } from "react-icons/lu";
function Actions({ platform }: { platform: Platform }) {
return (
<>
<IconButton aria-label="Search" icon={<LuSearch />} variant="ghost" platform={platform} />
<IconButton
aria-label="More actions"
icon={<LuEllipsis />}
variant="ghost"
platform={platform}
/>
</>
);
}
export function Large() {
return (
<TopAppBar
title="Leave request"
leadingLabel="Back"
leading="back"
actions={<Actions platform="ios" />}
/>
);
}
Leading controls
Figma Leading=Menu and Close: icon-only controls named by leadingLabel.
import { TopAppBar } from "@nexera-ui/react";
const leadings: TopAppBarLeading[] = ["back", "menu", "close"];
export function LeadingControls() {
return (
<div className="flex flex-col gap-4">
{(["ios", "android"] as const).flatMap((platform) =>
leadings.map((leading) => (
<article key={`${platform}${leading}`}>
<TopAppBar
platform={platform}
leading={leading}
leadingLabel={leading === "back" ? "Back" : leading === "menu" ? "Menu" : "Close"}
headingLevel={2}
title={`${platform} ${leading}`}
/>
</article>
)),
)}
</div>
);
}
Without leading
Without leadingLabel there is no leading control.
import { IconButton, TopAppBar } from "@nexera-ui/react";
import { LuEllipsis, LuSearch } from "react-icons/lu";
function Actions({ platform }: { platform: Platform }) {
return (
<>
<IconButton aria-label="Search" icon={<LuSearch />} variant="ghost" platform={platform} />
<IconButton
aria-label="More actions"
icon={<LuEllipsis />}
variant="ghost"
platform={platform}
/>
</>
);
}
export function WithoutLeading() {
return <TopAppBar title="Dashboard" platform="ios" actions={<Actions platform="ios" />} />;
}
Elevation
isElevated adds the surface fill and shadow while content scrolls under the bar; the shadow fades only with motion allowed.
import { useState } from "react";
import { IconButton, TopAppBar } from "@nexera-ui/react";
import { LuEllipsis, LuSearch } from "react-icons/lu";
function Actions({ platform }: { platform: Platform }) {
return (
<>
<IconButton aria-label="Search" icon={<LuSearch />} variant="ghost" platform={platform} />
<IconButton
aria-label="More actions"
icon={<LuEllipsis />}
variant="ghost"
platform={platform}
/>
</>
);
}
export function Elevation() {
function Screen() {
const [scrolled, setScrolled] = useState(false);
return (
<div className="max-w-sm overflow-hidden rounded-lg border border-default bg-page">
<TopAppBar
title="Leave request"
leadingLabel="Back"
leading="back"
isElevated={scrolled}
actions={<Actions platform="ios" />}
/>
<div
role="region"
aria-label="Rows"
// A scrollable region needs a keyboard route (axe `scrollable-region-focusable`); it is named by `aria-label`.
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
tabIndex={0}
className="h-56 overflow-y-auto px-4 py-2 focus-visible:focus-ring focus-visible:-outline-offset-2"
onScroll={(event) => {
setScrolled(event.currentTarget.scrollTop > 4);
}}
>
<ul className="m-0 flex list-none flex-col gap-3 p-0">
{Array.from({ length: 12 }, (_, index) => (
<li
key={index}
className="rounded-md border border-default bg-surface p-3 text-body-default text-primary"
>
Row {index + 1}
</li>
))}
</ul>
</div>
</div>
);
}
return <Screen />;
}
Narrow width
A 320 px column with a long title: the title wraps and the bar grows instead of truncating.
import { IconButton, TopAppBar } from "@nexera-ui/react";
import { LuEllipsis, LuSearch } from "react-icons/lu";
function Actions({ platform }: { platform: Platform }) {
return (
<>
<IconButton aria-label="Search" icon={<LuSearch />} variant="ghost" platform={platform} />
<IconButton
aria-label="More actions"
icon={<LuEllipsis />}
variant="ghost"
platform={platform}
/>
</>
);
}
export function NarrowWidth() {
return (
<div className="max-w-xs">
<TopAppBar
platform="android"
leading="menu"
leadingLabel="Menu"
title="Request for leave of absence for the whole team"
actions={<Actions platform="android" />}
/>
</div>
);
}
Long labels
Long translated strings wrap: the title, and the iOS Back label.
import { IconButton, TopAppBar } from "@nexera-ui/react";
import { LuEllipsis, LuSearch } from "react-icons/lu";
function Actions({ platform }: { platform: Platform }) {
return (
<>
<IconButton aria-label="Search" icon={<LuSearch />} variant="ghost" platform={platform} />
<IconButton
aria-label="More actions"
icon={<LuEllipsis />}
variant="ghost"
platform={platform}
/>
</>
);
}
export function LongLabels() {
return (
<div className="max-w-xs">
<TopAppBar
leading="back"
leadingLabel="Genehmigungen"
title="Urlaubsantrag für die gesamte Abteilung"
actions={<Actions platform="ios" />}
/>
</div>
);
}
Right to left
Right to left: the Back chevron points to the right, the actions sit at the left.
import { IconButton, TopAppBar } from "@nexera-ui/react";
import { LuEllipsis, LuSearch } from "react-icons/lu";
function Actions({ platform }: { platform: Platform }) {
return (
<>
<IconButton aria-label="Search" icon={<LuSearch />} variant="ghost" platform={platform} />
<IconButton
aria-label="More actions"
icon={<LuEllipsis />}
variant="ghost"
platform={platform}
/>
</>
);
}
export function RightToLeft() {
return (
<TopAppBar
leading="back"
title="طلب إجازة"
leadingLabel="رجوع"
actions={<Actions platform="ios" />}
/>
);
}
Props 11
Press "Try it" on a card to load that prop into the playground.
11 props shown
title*NexeraReactNodeThe title. It is the heading of the screen: a 1 or 2 word noun that matches the page. It wraps instead of truncating.
headingLevelNexera1 | 2 | 3 | 4 | 5 | 6Level of the title heading (h1 to h6). Use 1 when the bar titles the page, and a lower level when the page has
its own h1.
variantNexera"inline" | "large"Figma Variant: inline shows the title in the bar; large adds a large title (Heading/H1) under it. The large title
is the heading; the small title in the bar is a visual copy that appears with isElevated.
actionsNexeraReactNodeActions at the end of the bar. Pass one to three IconButtons (variant="ghost") with an aria-label each: their
44 pt / 48 dp size comes from the platform. Gaps follow Figma: 18 px on iOS, 8 px on Android.
platformNexera"web" | "ios" | "android"Platform look: bar height (44 px / 64 px), title style and alignment, spacing and the
leading control. Takes the NexeraProvider's platform when omitted. web has no Figma set and renders the iOS look.
isElevatedNexerabooleanThe content is scrolled under the bar. Gives the bar a bg/surface fill and a shadow so content does
not show through, and, for variant="large", shows the small title in the bar. The shadow fades in only when motion is
allowed. You decide when (for example from a scroll observer); the bar does not listen to scrolling.
classNameNexerastringExtra classes, merged last so they win over the defaults.
leadingNexera"back" | "menu" | "close"Not set: the bar has no leading control. Glyph of the leading control. Back is a chevron on iOS and an arrow on Android; both mirror in right-to-left layouts.
leadingLabelNexerastringNot set: the bar has no leading control.
Name of the leading control, for example "Back", "Menu" or "Close". It is
the visible text of the iOS Back control (often the title of the previous screen) and the aria-label of every other
glyph. Setting it shows the control. Translate it.
onLeadingPressNexera() => voidNot set: the bar has no leading control. Called when the leading control is pressed (button mode).
leadingHrefNexerastringNot set: the bar has no leading control. Makes the leading control a link to this address, for example the parent screen.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
8 direct · 3 supporting- 1.1.1Non-text ContentLevel A · tested directly
- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.3.2Meaningful SequenceLevel A · supporting test
- 2.1.1KeyboardLevel A · tested directly
- 2.3.3Animation from InteractionsLevel AAA · supporting test
- 2.4.1Bypass BlocksLevel A · tested directly
- 2.4.4Link Purpose (In Context)Level A · tested directly
- 2.4.6Headings and LabelsLevel AA · tested directly
- 2.4.7Focus VisibleLevel AA · tested directly
- 2.5.8Target Size (Minimum)Level AA · supporting test
- 4.1.2Name, Role, ValueLevel A · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
data-disableddata-elevateddata-hovereddata-pressed
<TopAppBar className="data-disabled:opacity-90 shadow-sm" />Used in blocks
- Mobile app shellApp shells and navigation
- Action sheetMobile patterns
- Bottom sheet pickerMobile patterns
- Colour sheetMobile patterns
- Tab bar appMobile patterns
- Pull to refresh feedMobile patterns
Related components
- BreadcrumbShows where the current page sits in the hierarchy and links back to each level.
- BreadcrumbItemOne level of a `Breadcrumb` trail; use it only inside `Breadcrumb`.
- CursorPagerA previous and next pair for data without stable page numbers, such as an activity log or a cursor API.
- FABThe main action of a mobile screen: a floating button with a `nav/active-bg` fill and a large shadow.
- FeedStatusThe status line at the bottom of an infinite feed or a lazy list: "Loading more", "You have reached the end" or "Could not load more" with a retry action.
- JumpToPageA small "Go to page [48] of 120" control for long paged lists.
- NavigationBarThe bottom bar of a mobile layout: a named `nav` landmark with a list of three to five `NavItem`s.
- NavItemOne destination or action of a `NavigationBar`.