# Wishlist 1 - Wishlist grid with price-drop badges

> A three-column wishlist of saved products with price-drop and stock badges, add-to-cart or notify actions, add-all, and an empty state.

A shadcn/ui block from the [@shadcnblocks](https://shadcnblocks.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/shadcnblocks/wishlist1).

- Page, with a live preview: https://shadcn.exchange/r/shadcnblocks/wishlist1
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/shadcnblocks/wishlist1-light-83473c9b1831230b.png
- Registry item: https://shadcnblocks.com/r/wishlist1.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://shadcnblocks.com/r/wishlist1.json
```

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

## Dependencies

- npm packages: `cn`, `lucide-react`
- Registry items: `aspect-ratio`, `badge`, `button`, `card`

## Files

### `block/wishlist1/wishlist1.tsx`

```tsx
"use client";

import { Heart, ShoppingCart, Trash2, TrendingDown } from "lucide-react";
import { useState } from "react";
import { cn } from "cn";

import { AspectRatio } from "@/components/ui/aspect-ratio";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
interface WishlistItem {
  id: string;
  name: string;
  image: string;
  price: number;
  originalPrice?: number;
  inStock: boolean;
  priceDrop?: boolean;
}

const DEFAULT_ITEMS: WishlistItem[] = [
  {
    id: "1",
    name: "Wireless Noise-Canceling Headphones",
    image:
      "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/ecommerce/electronics/Modern-White-Headphones-1.png",
    price: 249.99,
    originalPrice: 299.99,
    inStock: true,
    priceDrop: true,
  },
  {
    id: "2",
    name: "Premium Leather Crossbody Bag",
    image:
      "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/ecommerce/clothes/maroon-leather-handbag.png",
    price: 189.0,
    inStock: true,
  },
  {
    id: "3",
    name: "Classic Wool Blend Coat",
    image:
      "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/ecommerce/clothes/Young-Man-in-White-Hoodie-1.png",
    price: 329.0,
    inStock: false,
  },
  {
    id: "4",
    name: "Gold Hoop Earrings",
    image:
      "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/ecommerce/accessories/Elegant-Gold-Earrings-1.png",
    price: 79.0,
    originalPrice: 99.0,
    inStock: true,
    priceDrop: true,
  },
  {
    id: "5",
    name: "Oversized Cotton Hoodie",
    image:
      "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/ecommerce/clothes/black-hoodie-against-light-background.png",
    price: 65.0,
    inStock: true,
  },
  {
    id: "6",
    name: "Minimalist Watch",
    image:
      "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/ecommerce/accessories/Elegant-Watch-on-Womans-Wrist-1.png",
    price: 195.0,
    inStock: true,
  },
];

interface Wishlist1Props {
  items?: WishlistItem[];
  className?: string;
}

const Wishlist1 = ({ items = DEFAULT_ITEMS, className }: Wishlist1Props) => {
  const [wishlistItems, setWishlistItems] = useState(items);

  const removeItem = (id: string) => {
    setWishlistItems((prev) => prev.filter((item) => item.id !== id));
  };

  const formatPrice = (price: number) => {
    return new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: "USD",
    }).format(price);
  };

  return (
    <section className={cn("py-16 md:py-24", className)}>
      <div className="container max-w-6xl">
        {/* Header */}
        <div className="mb-8 flex items-center justify-between">
          <div>
            <h1 className="text-2xl font-semibold tracking-tight md:text-3xl">
              My Wishlist
            </h1>
            <p className="mt-1 text-muted-foreground">
              {wishlistItems.length} items saved
            </p>
          </div>
          {wishlistItems.length > 0 && (
            <Button variant="outline">
              <ShoppingCart className="mr-2 size-4" />
              Add All to Cart
            </Button>
          )}
        </div>

        {/* Grid */}
        {wishlistItems.length > 0 ? (
          <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
            {wishlistItems.map((item) => (
              <Card key={item.id} className="group gap-0 overflow-hidden p-0">
                <div className="relative">
                  <AspectRatio ratio={1} className="bg-muted">
                    <img
                      src={item.image}
                      alt={item.name}
                      className={cn(
                        "size-full object-cover",
                        !item.inStock && "opacity-50",
                      )}
                    />
                  </AspectRatio>

                  {/* Badges */}
                  <div className="absolute top-3 left-3 flex flex-col gap-2">
                    {item.priceDrop && (
                      <Badge className="bg-emerald-600 hover:bg-emerald-600">
                        <TrendingDown className="mr-1 size-3" />
                        Price Drop
                      </Badge>
                    )}
                    {!item.inStock && (
                      <Badge variant="secondary">Out of Stock</Badge>
                    )}
                  </div>

                  {/* Remove Button */}
                  <Button
                    variant="secondary"
                    size="icon"
                    className="absolute top-3 right-3 opacity-0 transition-opacity group-hover:opacity-100"
                    onClick={() => removeItem(item.id)}
                  >
                    <Trash2 className="size-4" />
                  </Button>
                </div>

                <CardContent className="p-4">
                  <h3 className="leading-tight font-medium">{item.name}</h3>
                  <div className="mt-2 flex items-center gap-2">
                    <span className="text-lg font-semibold">
                      {formatPrice(item.price)}
                    </span>
                    {item.originalPrice && (
                      <span className="text-sm text-muted-foreground line-through">
                        {formatPrice(item.originalPrice)}
                      </span>
                    )}
                  </div>
                  <Button
                    className="mt-4 w-full"
                    disabled={!item.inStock}
                    variant={item.inStock ? "default" : "secondary"}
                  >
                    {item.inStock ? "Add to Cart" : "Notify When Available"}
                  </Button>
                </CardContent>
              </Card>
            ))}
          </div>
        ) : (
          <Card className="p-0">
            <CardContent className="flex flex-col items-center justify-center py-16 text-center">
              <div className="mb-4 flex size-16 items-center justify-center rounded-full bg-muted">
                <Heart className="size-8 text-muted-foreground" />
              </div>
              <h2 className="text-xl font-semibold">Your wishlist is empty</h2>
              <p className="mt-2 max-w-sm text-muted-foreground">
                Save items you love by clicking the heart icon on any product
              </p>
              <Button className="mt-6">Continue Shopping</Button>
            </CardContent>
          </Card>
        )}
      </div>
    </section>
  );
};

export { Wishlist1 };
```
