IndiaCN UI

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.json
import {
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.

Page content starts here. Tab from the top to reach the skip link.
NavbarDefault
'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.

ControlBehaviour
NavbarTextSizeA−, 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
NavbarThemeToggleFlips the dark class. Pass isDark and onToggle to defer to an existing theme provider — two things writing that class will fight
NavbarLanguageGlobe, 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.

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 width1440px 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

PropTypeDefaultDescription
startReactNode<NavbarGovBadge />Leading content
childrenReactNodeTrailing controls
PropTypeDefault
hrefstringhttps://www.india.gov.in
labelstringGovernment of India
PropTypeDefaultDescription
isDarkbooleanControlled mode. Omit to let the toggle manage the dark class
onToggle() => voidCalled instead of toggling the class
PropTypeDescription
languages{ code, label }[]Options
valuestringSelected code
onSelect(code: string) => voidSelection handler

Takes no props — it reads and writes the shared accessibility store.

PropTypeDescription
activebooleanMarks the current page, and sets aria-current
hasMenubooleanShows 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.

On this page