UIDex

Site Header and Navbar

ترويسة الموقع وشريط التنقّل

<header> + <nav>

also callednavbar، top nav، site header، masthead، primary navigation، app bar، الهيدر، شريط التنقّل العلوي، ترويسة الصفحة، نافبار، الشريط العلوي، قائمة التنقّل الرئيسية

The header is the strip across the top of the page, and the <header> element that carries it becomes a banner landmark on its own, as long as it does not sit inside <article>, <aside>, <main>, <nav>, or <section>. The navbar is the row of section links inside that strip, written as <nav> and exposed as a navigation landmark. Developers swap the two words every day, and the difference between them is a counting rule: a page gets exactly one banner, while it may hold several <nav> elements so long as each carries an aria-label that tells them apart. A header is not a tab strip, because tabs swap the contents of one panel with no new URL, while header links move the user between pages. Nor is it a toolbar: role="toolbar" groups buttons that act on the content in view and moves between them with arrow keys inside a single Tab stop. The role most often misused here is menubar, which forces desktop application menu behaviour onto what is really a list of links. A sidebar shows the whole section tree; a header shows the handful of top destinations that repeat on every page.

If you called it…

"the bar at the top with the logo and the links""the menu that shows on every page""the links next to the site name""the three lines that open the menu on a phone""the top strip that stays put while you scroll"

Live specimen

Interact with the demo. Every part is real and numbered.

تخطَّ إلى المحتوىيسبق الترويسة، ولا يظهر إلا عند التركيز

أنت الآن في

الصفحة الرئيسية

RTLالشعار عند البداية والإجراءات عند النهاية، بلا row-reverse

Anatomy: every part, named

Hover a row to locate it in the demo above.

Build prompt

Build a site header. Make the strip a single root level <header> with no second <header> nested inside it, and put a skip link immediately before it that targets the main content id and appears on focus. Lay the contents out as a flex row: a logo link to the home page whose accessible name is the site name, then <nav aria-label="Main"> holding a <ul role="list"> of section links, then the actions cluster (search inside <form role="search">, account, language switcher) pushed over with margin-inline-start: auto, then the narrow screen menu button at the logical end. Give exactly one link aria-current="page" and leave the rest of the strip without it, and carry the visual difference with weight rather than colour alone. Make the menu button a <button type="button"> with aria-expanded and aria-controls, keep its accessible name the same when open and closed, close its panel on Escape and return focus to the button, and give every icon-only button an aria-label. If the header is sticky, give it a z-index above the content and add scroll-margin-block-start equal to its height on every anchor target. Use logical properties throughout: padding-inline, margin-inline, inset-inline-start, inset-inline-end. Never write left, right, or padding-left, and never add flex-direction: row-reverse under [dir="rtl"]. Zero the list padding with padding-inline-start: 0, align dropdowns with inset-inline-start: 0, and move the mobile panel with translateX(calc(100% * var(--dir))) where --dir is -1 in RTL.

How it behaves right-to-left

This section is ours alone.

Order across the strip

mirrors

DOM order stays the same in both directions: logo, then the nav list, then the actions. A flex row lays them on the inline axis and that axis already flips with dir, so a flex-direction: row-reverse added under [dir="rtl"] flips a second time and sends the logo back to the left with Tab order fighting what the eye sees. Push the actions cluster to the far end with margin-inline-start: auto; margin-left: auto absorbs the free space on the wrong side in RTL and pulls the cluster back toward the nav instead of out to the edge.

The list keeps its own padding

mirrors

A <ul> arrives with 40px of leading padding from the browser stylesheet, and Chrome, Firefox, and Safari all express it today as padding-inline-start. That is why the usual reset padding-left: 0 works in English and leaves forty pixels stuck to the right of the list in Arabic, so the links sit away from the logo with nothing on screen to explain the gap. Write padding-inline-start: 0 or padding-inline: 0, and read the computed value in devtools rather than trusting the stylesheet.

A dropdown under a nav item

mirrors

A panel dropping from a header link aligns to that link's logical start: inset-inline-start: 0 on the absolutely positioned panel, not left: 0. If you place it from JavaScript, remember that getBoundingClientRect().left is always physical and knows nothing about page direction, so correct RTL alignment is built from the right value of the same rect. Floating UI resolves bottom-start and bottom-end against writing direction while the physical placements stay fixed, so use the start and end alignments and let flip and shift keep the panel on screen.

The language switcher

never mirrors

The switcher keeps its slot inside the actions cluster in both directions, so do not write [dir="rtl"] .lang { order: -1 } to move it elsewhere; whoever reaches for it usually cannot read the language the page is in, and a control that changes corners is one they hunt for every time. Set the new direction through the dir attribute on <html> rather than direction: rtl in a stylesheet, because :dir(), the [dir="rtl"] selectors you already wrote, and any script reading document.dir all read the attribute and never see the CSS value. Write each language in its own script, "العربية" and English, and put lang="en" on the Latin label so a screen reader does not pronounce it with an Arabic voice.

The toggle and its panel

mirrors

The toggle comes last in the DOM, so it lands at the logical inline end of the strip, the left in Arabic, with no direction rule of its own and no second auto margin splitting the free space with the actions cluster. The panel it opens is anchored with inset-inline-end: 0 and enters from that same edge, but transform has no logical form: write translateX(calc(100% * var(--dir))) and set --dir to -1 under [dir="rtl"], or the panel flies in from the opposite side and crosses the whole screen in front of the user. The three bars in the icon are horizontal and equal, so flipping them changes nothing, while a submenu chevron points along the inline axis and does flip with the page.

In code

Each row is one framework's word for the same thing. Take the row your project speaks.

HTML<header>A banner at root level, and generic inside article, aside, main, nav, or section.
HTML<nav aria-label="…">The name is what separates header navigation from footer navigation for a screen reader.
ARIAaria-current="page"page for the page itself, true for the parent section, and only one link carries page.
CSSscroll-margin-block-startStops a sticky header from swallowing the heading you jumped to.
CSSinset-inline-start: 0Aligns a dropdown to the logical start of its trigger instead of the physical left.
Radix UI<NavigationMenu.Root>Gives you the keyboard behaviour and the dropdowns with one shared content viewport.
Material UI<AppBar position="sticky">Material calls the header an App Bar, and its inner Toolbar is a layout, not role="toolbar".

See also

Updated · 2026-08-19