Navbar
Government website header with a utility bar, brand, navigation and actions.
Introduction
UX4G specifies a two-tier header for government sites: a primary utility bar carrying the Government of India mark and site-wide controls, over a white bar carrying brand, navigation and actions.
The parts are slots rather than a single configured component, because a header is the piece every site changes most.
Installation
npx shadcn@latest add https://indiacn.in/r/navbar.jsonimport {
Navbar,
NavbarUtilityBar,
NavbarGovBadge,
NavbarSkipLink,
NavbarSeparator,
NavbarMain,
NavbarBrand,
NavbarNav,
NavbarLink,
NavbarActions,
NavbarToggle,
} from '@/components/ui/navbar';Example
The preview is far narrower than a real header, so this example carries three links rather than the six a site would. Note also that the collapse breakpoint is a viewport query, not a container one — inside this box the desktop layout still applies even though the box is narrow.
'use client';
import { Search as SearchIcon } from 'lucide-react';
import { useCallback, useState } from 'react';
import { Button } from '@/components/ui/button';
import {
Navbar,
NavbarActions,
NavbarBrand,
NavbarLink,
NavbarMain,
NavbarNav,
NavbarLanguage,
NavbarSeparator,
NavbarSkipLink,
NavbarTextSize,
NavbarThemeToggle,
NavbarToggle,
NavbarUtilityBar,
} from '@/components/ui/navbar';
import { Label2, Title2 } from '@/components/ui/typography';
const LANGUAGES = [
{ code: 'en', label: 'English' },
{ code: 'hi', label: 'हिन्दी' },
{ code: 'bn', label: 'বাংলা' },
{ code: 'ta', label: 'தமிழ்' },
];
const LINKS = [
{ label: 'About us', href: '#about' },
{ label: 'Citizen Corner', href: '#citizen', hasMenu: true },
{ label: 'Resources', href: '#resources', hasMenu: true },
];
export default function Component() {
const [language, setLanguage] = useState('en');
const handleLanguage = useCallback((code: string) => setLanguage(code), []);
return (
<div className='relative w-full overflow-hidden rounded-md border border-neutral-100'>
<Navbar>
<NavbarUtilityBar>
<NavbarSkipLink href='#main-demo' />
<NavbarSeparator />
<NavbarTextSize />
<NavbarSeparator />
<NavbarThemeToggle />
<NavbarSeparator />
<NavbarLanguage languages={LANGUAGES} value={language} onSelect={handleLanguage} />
</NavbarUtilityBar>
<NavbarMain className='relative'>
<NavbarBrand href='#home'>
<Title2 className='text-primary font-semibold'>Sewa Setu</Title2>
</NavbarBrand>
<NavbarNav>
{LINKS.map(link => (
<NavbarLink
key={link.label}
href={link.href}
hasMenu={link.hasMenu}
active={link.label === 'About us'}
>
{link.label}
</NavbarLink>
))}
</NavbarNav>
<NavbarActions>
<Button variant='text' iconButton aria-label='Search'>
<SearchIcon />
</Button>
<Button variant='outlined' size='sm' className='hidden sm:inline-flex'>
Login
</Button>
<Button size='sm' className='hidden sm:inline-flex'>
Signup
</Button>
<NavbarToggle />
</NavbarActions>
</NavbarMain>
</Navbar>
<div id='main-demo' className='p-6'>
<Label2 className='text-neutral-600'>
Page content starts here. Tab from the top to reach the skip link.
</Label2>
</div>
</div>
);
}The utility bar controls
UX4G specifies three controls in the utility bar, and they are the substance of it — a government header without them is just a coloured strip.
| Control | Behaviour |
|---|---|
NavbarTextSize | A−, A, A+ across three steps. Shares its store with the Accessibility Widget, so the header and the panel cannot disagree about how large the page is |
NavbarThemeToggle | Flips the dark class. Pass isDark and onToggle to defer to an existing theme provider — two things writing that class will fight |
NavbarLanguage | Globe, current language, chevron. Built on Dropdown |
<NavbarUtilityBar>
<NavbarSkipLink href="#main" />
<NavbarSeparator />
<NavbarTextSize />
<NavbarSeparator />
<NavbarThemeToggle />
<NavbarSeparator />
<NavbarLanguage languages={LANGUAGES} value={language} onSelect={setLanguage} />
</NavbarUtilityBar>The text-size control reads the same module store as the accessibility panel, through useAccessibilitySettings(). Duplicating that state was the obvious shortcut and the wrong one: two controls for the same setting that disagree is worse than one.
The skip link
NavbarSkipLink is hidden until it receives focus, then appears in the utility bar. That is the only arrangement that works: it has to be the first thing a keyboard user reaches, and invisible to everyone else.
Point it at your main landmark and give that landmark the matching id:
<NavbarSkipLink href="#main" />
...
<main id="main" tabIndex={-1}>…</main>Without tabIndex={-1} on the target, some browsers move the viewport but not focus, so the next Tab returns to the header.
The tricolour
NavbarGovBadge draws the flag from a theme token rather than shipping an image, so installing the component needs no asset file.
The token is defined once and deliberately not redefined in .dark — saffron, white and green are the flag, not a palette we retint for dark mode. If you need it elsewhere, the utility is bg-tricolour.
Measurements
| value | |
|---|---|
| utility bar | #613af5 (primary), 41px |
| main bar | #ffffff (neutral-0), 72px |
| content width | 1440px max |
Responsive behaviour
Below lg the navigation collapses behind NavbarToggle and opens as a stacked panel beneath the header rather than an overlay, so it never traps focus and never covers the content it links to. NavbarMain needs className="relative" for the panel to anchor to it.
API Reference
NavbarUtilityBar
| Prop | Type | Default | Description |
|---|---|---|---|
start | ReactNode | <NavbarGovBadge /> | Leading content |
children | ReactNode | — | Trailing controls |
NavbarGovBadge
| Prop | Type | Default |
|---|---|---|
href | string | https://www.india.gov.in |
label | string | Government of India |
NavbarThemeToggle
| Prop | Type | Default | Description |
|---|---|---|---|
isDark | boolean | — | Controlled mode. Omit to let the toggle manage the dark class |
onToggle | () => void | — | Called instead of toggling the class |
NavbarLanguage
| Prop | Type | Description |
|---|---|---|
languages | { code, label }[] | Options |
value | string | Selected code |
onSelect | (code: string) => void | Selection handler |
NavbarTextSize
Takes no props — it reads and writes the shared accessibility store.
NavbarLink
| Prop | Type | Description |
|---|---|---|
active | boolean | Marks the current page, and sets aria-current |
hasMenu | boolean | Shows a chevron for a link that opens a menu |
Other parts
NavbarBrand, NavbarNav, NavbarActions, NavbarSeparator and NavbarSecondaryRow are layout slots. NavbarToggle reads its expanded state from the navbar and controls NavbarNav by id.