I built an e-commerce header in React. Here’s how it went.

I’m Kayla. I spent a weekend making a shop header in React for my little test store, KayShop. I wanted it fast, clean, and not fussy. You know what? It mostly worked. And a few parts bugged me.

I’ll show real bits I used, the choices I made, and what I’d change.

— Quick stack: Next.js 14, React 18, Chakra UI, Headless UI, Framer Motion, SWR, lodash.debounce, and React Icons.
— Test gear: MacBook Air, iPhone 13 mini, and my old Pixel 4a. Safari gave me the most trouble. Shocker, right?

The header pieces I shipped

I kept the main row simple:

  • Left: menu button on mobile, logo, and a small mega menu on desktop.
  • Middle: a search box with live hints.
  • Right: account, wishlist, and a cart badge.

It’s sticky on scroll. It drops a soft shadow when the page moves. Very light. No jumpy stuff.

Real code I used for the frame

I leaned on Chakra UI for layout. It was fast to build and easy to read later.
Explore Chakra UI’s official documentation to dive into the full component API.

import { Box, Flex, Input, IconButton, Badge, Link } from '@chakra-ui/react';
import { FiSearch, FiShoppingCart, FiMenu, FiUser } from 'react-icons/fi';

export function Header({ cartCount = 0 }) {
  return (
    <Box as="header" position="sticky" top="0" zIndex="1000" bg="white" boxShadow="sm">
      <Flex h="64px" align="center" px="4" gap="3">
        <IconButton
          aria-label="Open menu"
          icon={<FiMenu />}
          display={{ base: 'inline-flex', md: 'none' }}
          variant="ghost"
        />
        <Link href="/" fontWeight="bold" fontSize="xl" whiteSpace="nowrap">KayShop</Link>

        <Input
          aria-label="Search products"
          placeholder="Search shoes..."
          maxW="520px"
          display={{ base: 'none', md: 'block' }}
        />

        <Flex ml="auto" align="center" gap="2">
          <IconButton aria-label="Account" icon={<FiUser />} variant="ghost" />
          <Box position="relative">
            <IconButton aria-label="Cart" icon={<FiShoppingCart />} variant="ghost" />
            {cartCount > 0 && (
              <Badge position="absolute" top="-1" right="-1" borderRadius="full" colorScheme="red">
                {cartCount}
              </Badge>
            )}
          </Box>
        </Flex>
      </Flex>
    </Box>
  );
}

Nothing wild. But it loads fast and looks tidy.

Search that feels quick, not jumpy

I wanted hints while you type. I used lodash.debounce so the API doesn’t get spammed. SWR handled caching. It felt smooth on Wi-Fi and fine on 4G.

import { useState, useMemo } from 'react';
import debounce from 'lodash.debounce';
import useSWR from 'swr';

const fetcher = (url) => fetch(url).then(r => r.json());

export function useSearch() {
  const [query, setQuery] = useState('');
  const { data } = useSWR(
    query.length > 1 ? `/api/search?q=${encodeURIComponent(query)}` : null,
    fetcher,
    { keepPreviousData: true }
  );

  const onChange = useMemo(
    () => debounce((v) => setQuery(v), 200),
    []
  );

  return { query, setQuery: (v) => { onChange(v); }, results: data?.items || [] };
}

And then I render a tiny list under the box. Nothing fancy. It’s good enough for shoes, hats, and hoodies.

{results.length > 0 && (
  <Box role="listbox" aria-label="Search results" bg="white" boxShadow="md" mt="1">
    {results.slice(0, 6).map((item) => (
      <Box key={item.id} role="option" px="3" py="2">
        {item.name}
      </Box>
    ))}
  </Box>
)}

Small thing I liked: if you type “red” it starts to show “Red Runner Sneaker” and “Red Beanie” fast. My kid tested it and said, “Ooh, it knows.” I’ll take the win.

Mega menu that didn’t make me cry

I used Headless UI’s Menu for a tiny mega menu. It gave me keyboard support and ARIA hints without a big fight.
Headless UI’s Menu component documentation spells out every prop and state pattern if you’re curious.

On desktop, hover opens it. On mobile, it turns into a Drawer with big tap targets.

// mobile drawer sketch
import { Drawer, DrawerOverlay, DrawerContent, DrawerBody, Button } from '@chakra-ui/react';

<Drawer isOpen={isOpen} onClose={onClose} placement="left">
  <DrawerOverlay />
  <DrawerContent>
    <DrawerBody>
      <Button variant="ghost" width="100%">Women</Button>
      <Button variant="ghost" width="100%">Men</Button>
      <Button variant="ghost" width="100%">Kids</Button>
    </DrawerBody>
  </DrawerContent>
</Drawer>

I made the tap zones tall. My thumb said thanks. Yours will too.

What worked great

  • Build speed. Chakra’s Flex made spacing dead simple. No CSS rabbit hole.
  • Sticky header. CSS position: sticky was smooth. No jank on Chrome.
  • Search hints. Debounce at 200 ms felt just right. 100 ms was twitchy.
  • Cart badge. Simple Badge looked clean and didn’t clip.

I also added a tiny shadow on scroll with Framer Motion. It’s subtle. People notice, but they don’t know why it feels nice. That’s the sweet spot.

What bugged me (and how I fixed it)

  • Safari on iOS had a little jump when the address bar hid. My fix: stick to 64px header height and avoid 100vh tricks. That calmed it down.
  • The search box zoomed at focus on iOS. Of course it did. I set font-size to 16px. Zoom stopped.
  • Hover mega menu closed too fast when moving the mouse across the gap. I added a small open delay (120 ms) and made the hover area wider. Better.
  • React Headroom looked cool, but it jittered in Safari 16 for me. I dropped it and stayed with plain sticky + a shadow. Boring, but solid.

Real numbers from my tests

I ran Lighthouse in Chrome:

  • Performance: 94 with the header, 96 after I compressed the logo SVG.
  • Interaction to Next Paint: 140 ms on desktop, 220–280 ms on mobile.
  • The search API dropped from 320 ms to 150 ms after I cached the top 50 items in memory. SWR helped.

Nothing crazy. Still, you feel it. Pages just feel lighter when the header behaves.

Little things that made it human

  • I kept the clickable area on icons at least 40×40. My pinky is clumsy before coffee.
  • I honored “prefers-reduced-motion.” If a user says no motion, I cut all fades and slides.
  • I added aria-expanded to menu buttons. Screen reader flow got nicer right away.
  • I preloaded the cart route on hover with Next.js Link. It made the cart pop fast.

While polishing the header, I skimmed other consumer apps that obsess over micro-interactions to keep users engaged. Dating platforms are a gold mine for friction-free navigation tricks, especially those targeted at grown-ups who won’t tolerate clunky UI—this teardown of leading options highlights the best dating apps for adults and breaks down onboarding flows, swipe mechanics, and retention tactics you can borrow to make any React interface feel smoother and more intuitive.

For an even more niche perspective on how upscale dating marketplaces craft high-conversion landing pages, check out the elegant structure and persuasive copywriting on One Night Affair’s profile for Sugar Baby Victoria—Sugar Baby Victoria—the page is a quick study in using scarcity cues, rich imagery, and mobile-first layouts that you can adapt when you’re refining hero sections or call-to-action zones in your own React projects.

Would I build a React header for a shop again?

Yes. I’d use the same stack for a mid-size store. For shop owners who want a turnkey backend they can still customize on the front end, Candypress offers a headless-ready e-commerce platform you can wire up in minutes.
I recently tested the best e-commerce platforms for SEO, and the notes might help you pick the right foundation.
It’s quick to ship and easy to tweak. If you sell 10,000 SKUs, you may want a beefier search box