# Nav Categories

> A store landing header with a draggable carousel of category tiles and a heading that follows the selection.

A shadcn/ui block from the [@esuiss](https://suiss.app/ui?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/esuiss/nav-categories).

- Page, with a live preview: https://shadcn.exchange/r/esuiss/nav-categories
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/esuiss/nav-categories-light-f20e1b6c210d8496.png
- Registry item: https://suiss.app/ui/r/new-york-v4/nav-categories.json
- Categories: blocks, nav
- Listed: 2026-10-20, source last changed 2026-10-20

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/esuiss/nav-categories.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@esuiss/carousel`, `@esuiss/toggle`

## Files

### `components/blocks/nav-categories.tsx`

```tsx
"use client"

import * as React from "react"

import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from "@/components/ui/carousel"
import { Toggle } from "@/components/ui/toggle"

const photo = (id: string) =>
  `https://images.unsplash.com/photo-${id}?w=400&q=80&auto=format&fit=crop`

const categories = [
  { label: "Laptop", image: photo("1785245560368-f6d715956e5d") },
  { label: "Phone", image: photo("1714578187196-29775454aa39") },
  { label: "Pad", image: photo("1607452263110-39a87c399c50") },
  { label: "Watch", image: photo("1660844817855-3ecc7ef21f12") },
  { label: "Pods", image: photo("1599669454699-248893623440") },
  { label: "Cases", image: photo("1703676311066-34caf2cbee33") },
  { label: "Camera", image: photo("1604677209244-6569d050557a") },
  { label: "Colors", image: photo("1633596683562-4a47eb4983c5") },
  { label: "Pro", image: photo("1760978631841-f3754859ca59") },
  { label: "Accessories", image: photo("1645323927877-3de25b4f819c") },
]

export function NavCategories() {
  const [selected, setSelected] = React.useState("Phone")

  return (
    <section className="flex flex-col gap-7 bg-background pt-11 pb-10">
      <div className="flex flex-wrap items-end gap-6 px-6 md:px-12">
        <div className="flex min-w-60 flex-1 flex-col gap-1">
          <h1 className="text-5xl font-semibold tracking-tight">Store.</h1>
          <p className="text-2xl text-balance text-muted-foreground">
            The best way to buy the suiss products you love.
          </p>
        </div>
        <div className="flex flex-col items-start gap-1.5 text-xs">
          <span className="font-semibold text-muted-foreground">
            Need shopping help?
          </span>
          <a href="#specialist" className="text-primary hover:underline">
            Ask a Specialist ›
          </a>
          <a href="#stores" className="text-primary hover:underline">
            Visit a suiss Store near you ›
          </a>
        </div>
      </div>
      <Carousel
        opts={{ align: "start", dragFree: true }}
        aria-label="Categories"
        className="flex flex-col gap-3"
      >
        <CarouselContent className="ms-0 gap-5.5 px-6 py-2 md:px-12">
          {categories.map((category) => {
            const pressed = selected === category.label
            return (
              <CarouselItem key={category.label} className="basis-auto ps-0">
                <Toggle
                  pressed={pressed}
                  onPressedChange={() => setSelected(category.label)}
                  className="group/category h-auto flex-col gap-2.5 rounded-xl p-0 hover:bg-transparent aria-pressed:bg-transparent aria-pressed:hover:bg-transparent"
                >
                  <img
                    src={category.image}
                    alt=""
                    className="h-19.5 w-30 rounded-[0.875rem] bg-secondary object-cover transition-[translate,box-shadow] duration-350 ease-[cubic-bezier(0.3,1.25,0.5,1)] group-aria-pressed/category:-translate-y-0.5 group-aria-pressed/category:ring-2 group-aria-pressed/category:ring-primary"
                  />
                  <span className="text-sm font-semibold text-foreground group-aria-pressed/category:text-primary">
                    {category.label}
                  </span>
                </Toggle>
              </CarouselItem>
            )
          })}
        </CarouselContent>
        <div className="flex justify-end gap-3 px-6 md:px-12">
          <CarouselPrevious
            variant="secondary"
            size="icon"
            className="static inset-auto my-0"
          />
          <CarouselNext
            variant="secondary"
            size="icon"
            className="static inset-auto my-0"
          />
        </div>
      </Carousel>
      <div className="mx-6 border-t border-border pt-5.5 md:mx-12">
        <p className="text-2xl font-semibold tracking-tight text-balance">
          The latest.{" "}
          <span className="text-muted-foreground">
            Take a look at what&apos;s new in {selected}.
          </span>
        </p>
      </div>
    </section>
  )
}
```
