Skip to content

Floating Cart

A sleek, animated shopping cart widget with expandable drawer, item management, and smooth micro-interactions for e-commerce applications.

Installation

pnpm dlx shadcn@latest add https://ui.sanjid.shop/r/floating-cart.json

Backend Integration

The cart widget works with your existing e-commerce backend. Here's how to integrate it with common patterns:

  1. Cart State Management: /hooks/useCart.ts
1import { useState, useEffect } from "react";2
3interface CartItem {4  id: number;5  name: string;6  price: number;7  quantity: number;8  image: string;9}10
11export function useCart() {12  const [items, setItems] = useState<CartItem[]>([]);13
14  // Load cart from localStorage on mount15  useEffect(() => {16    const savedCart = localStorage.getItem("cart");17    if (savedCart) {18      setItems(JSON.parse(savedCart));19    }20  }, []);21
22  // Save cart to localStorage when items change23  useEffect(() => {24    localStorage.setItem("cart", JSON.stringify(items));25  }, [items]);26
27  const addItem = (product: Omit<CartItem, "quantity">) => {28    setItems((prev) => {29      const existing = prev.find((item) => item.id === product.id);30      if (existing) {31        return prev.map((item) =>32          item.id === product.id33            ? { ...item, quantity: item.quantity + 1 }34            : item,35        );36      }37      return [...prev, { ...product, quantity: 1 }];38    });39  };40
41  const removeItem = (id: number) => {42    setItems((prev) => prev.filter((item) => item.id !== id));43  };44
45  const updateQuantity = (id: number, quantity: number) => {46    if (quantity <= 0) {47      removeItem(id);48      return;49    }50    setItems((prev) =>51      prev.map((item) => (item.id === id ? { ...item, quantity } : item)),52    );53  };54
55  const clearCart = () => setItems([]);56
57  return {58    items,59    addItem,60    removeItem,61    updateQuantity,62    clearCart,63    itemCount: items.reduce((sum, item) => sum + item.quantity, 0),64    subtotal: items.reduce((sum, item) => sum + item.price * item.quantity, 0),65  };66}
  1. Checkout Integration: /app/api/checkout/route.ts
1import { NextResponse } from "next/server";2import Stripe from "stripe";3
4const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {5  apiVersion: "2023-10-16",6});7
8export async function POST(request: Request) {9  try {10    const { items, customerEmail } = await request.json();11
12    // Create Stripe checkout session13    const session = await stripe.checkout.sessions.create({14      payment_method_types: ["card"],15      line_items: items.map((item: any) => ({16        price_data: {17          currency: "usd",18          product_data: {19            name: item.name,20            images: [item.image],21          },22          unit_amount: Math.round(item.price * 100),23        },24        quantity: item.quantity,25      })),26      mode: "payment",27      success_url: `${process.env.NEXT_PUBLIC_URL}/success`,28      cancel_url: `${process.env.NEXT_PUBLIC_URL}/cart`,29      customer_email: customerEmail,30    });31
32    return NextResponse.json({ sessionId: session.id });33  } catch (error) {34    console.error("Checkout error:", error);35    return NextResponse.json(36      { error: "Failed to create checkout session" },37      { status: 500 },38    );39  }40}
  1. Environment Variables

Add these to .env.local:

1STRIPE_PUBLISHABLE_KEY=pk_test_your_stripe_public_key2STRIPE_SECRET_KEY=sk_test_your_stripe_secret_key3NEXT_PUBLIC_URL=http://localhost:3000

Props

PropTypeRequiredDefaultDescription
itemsCartItem[]No[]Array of cart items to display
onViewCart() => voidNoundefinedCallback when 'View Cart' button is clicked
onCheckout() => voidNoundefinedCallback when 'Checkout' button is clicked

1type CartItem = {2  id: number;3  name: string;4  price: number;5  quantity: number;6  image: string;7};

Features

  • Floating Design: Fixed position widget that doesn't interfere with page content
  • Expandable Drawer: Smooth slide-out drawer with backdrop overlay
  • Item Management: Add, remove, and update quantities with animated feedback
  • Real-time Updates: Live subtotal calculation and item count badge
  • Smooth Animations: Framer Motion powered micro-interactions throughout
  • Click Outside: Automatically closes when clicking outside the drawer
  • Keyboard Support: ESC key to close, full accessibility support
  • Responsive Design: Adapts perfectly to mobile and desktop screens
  • Dark Mode: Full dark mode support with proper contrast ratios
  • Touch Friendly: Large touch targets optimized for mobile interaction
  • Loading States: Built-in loading indicators for async operations
  • TypeScript: Complete TypeScript support with proper type definitions

Animation Details

The component uses Framer Motion for fluid animations:

  • Drawer Entrance: Spring-based scale and fade animation with backdrop
  • Item Stagger: Sequential item animations with custom delay timing
  • Quantity Controls: Smooth scale animations on button interactions
  • Hover Effects: Subtle translate and background color transitions
  • Remove Actions: Rotate and scale animations for delete interactions
  • Badge Animations: Scale-in animation for cart count indicator
  • Button States: Loading spinners and state transitions

Customization

1<FloatingCartWidget2  items={cartItems}3  onViewCart={() => router.push('/cart')}4  onCheckout={handleCheckout}5/>6
7// Custom CSS for positioning8.floating-cart-custom {9  @apply fixed right-4 bottom-4 md:right-8 md:bottom-8;10}

1// Extend the component for custom item display2const CustomFloatingCart = ({ items, ...props }) => {3  const formatPrice = (price) => {4    return new Intl.NumberFormat("en-US", {5      style: "currency",6      currency: "USD",7    }).format(price);8  };9
10  return (11    <FloatingCartWidget12      items={items.map((item) => ({13        ...item,14        displayPrice: formatPrice(item.price),15      }))}16      {...props}17    />18  );19};

1import { useSelector, useDispatch } from "react-redux";2import { removeFromCart, updateQuantity } from "./cartSlice";3
4export function ConnectedFloatingCart() {5  const dispatch = useDispatch();6  const cartItems = useSelector((state) => state.cart.items);7
8  return (9    <FloatingCartWidget10      items={cartItems}11      onViewCart={() => router.push("/cart")}12      onCheckout={() => router.push("/checkout")}13    />14  );15}

Best Practices

  • Use
    1React.memo
    for cart items to prevent unnecessary re-renders
  • Implement debounced quantity updates for better UX
  • Consider virtualization for large cart item lists

  • Ensure proper ARIA labels for all interactive elements
  • Support keyboard navigation throughout the component
  • Provide screen reader announcements for cart updates

  • Show loading states during async operations
  • Provide clear feedback for all user actions
  • Consider auto-save functionality for cart persistence
  • Implement undo functionality for item removals

Build your next big idea with us

From lightning-fast landing pages to fully functional SaaS products, we turn your vision into reality. Book a call today and let's make something extraordinary.

They transformed our idea into a fully functional product in record time. Couldn't be happier!

Alex Chen

Founder, StartupX

The team's attention to detail and design expertise set our product apart in the market.

Sarah Kim

CTO, Innovate Inc