A comprehensive demonstration of building a production-ready shopping cart using Svelte 5's Context API and reactive primitives.
This project showcases modern state management patterns in Svelte 5, featuring reactive calculations, automatic persistence, and context-based scoped isolation. Built as a companion to the article series on The Hackpile Chronicles.
$state, $derived, and $effect runesThe cart system uses Svelte 5 context to provide scoped, reactive state that's independent of component hierarchy:
// Create isolated cart context
createCartContext({
storageKey: 'vendor-1-cart', // Isolate by vendor
taxRate: 0.0875, // Auto-calculate tax
shippingCost: 799, // Smart shipping logic
freeShippingThreshold: 5000 // Free shipping rules
});
// Access from any child component
const cart = getCartContext();
cart.addItem(product, quantity);
$derived)const subtotal = $derived(items.reduce((sum, item) => sum + item.lineTotal, 0));
const tax = $derived(Math.round(afterDiscount * taxRate));
const total = $derived(afterDiscount + shipping + tax);
All calculations automatically update when items change. No manual tracking needed.
$effect)$effect(() => {
if (!browser || !isInitialized) return;
const data = {
items,
discount: appliedDiscount,
updatedAt: Date.now()
};
localStorage.setItem(storageKey, JSON.stringify(data));
});
Cart state automatically syncs to localStorage whenever it changes.
Each vendor gets its own isolated cart context:
/marketplace/techgear-pro/ → techgear-pro-cart
/marketplace/artisan-crafts/ → artisan-crafts-cart
Users can shop from multiple vendors simultaneously without cart conflicts.
async applyDiscount(code: string): Promise<DiscountResult> {
const response = await fetch('/api/coupons/validate', {
method: 'POST',
body: JSON.stringify({ code, subtotal })
});
// Apply validated discount...
}
src/lib/
cart/
cart-context.svelte.ts # Main cart context implementation
vendor-carts.svelte.ts # Cross-vendor cart aggregation
types.ts # TypeScript definitions
CartProvider.svelte # Context provider component
CartSummary.svelte # Cart display component
AddToCartButton.svelte # Add to cart UI
QuantitySelector.svelte # Quantity controls
CartIcon.svelte # Cart badge with count
wishlist/
wishlist-context.svelte.ts # Parallel wishlist implementation
types.ts # Wishlist types
WishlistProvider.svelte # Wishlist context provider
data/
products.ts # Sample vendor & product data
config/
routes.ts # Route configuration
src/routes/
demo/
+page.svelte # Demo landing page
(vendor)/ # Vendor-specific routes
[email protected] # Vendor layout with context
marketplace/[vendor]/ # Dynamic vendor pages
+page.svelte # Product listing
cart/+page.svelte # Vendor cart page
wishlist/+page.svelte # Vendor wishlist page
marketplace/
all-carts/+page.svelte # Cross-vendor cart summary
api/
coupons/validate/
+server.ts # Discount validation endpoint
# Clone or download this demo
git clone <repository-url>
cd sv-cart
# Install dependencies
pnpm install
# Start development server
pnpm dev
Visit the following routes to see different features:
/demo – Demo home with feature overview/demo/marketplace/techgear-pro – Shop TechGear Pro products/demo/marketplace/techgear-pro/cart – View TechGear cart/demo/marketplace/artisan-crafts – Shop Artisan Crafts products/demo/marketplace/all-carts – See all vendor carts together<!-- +layout.svelte -->
<script lang="ts">
import { createCartContext } from '$lib/cart/cart-context.svelte';
import { CartProvider } from '$lib/cart';
createCartContext({
storageKey: 'my-cart',
taxRate: 0.08,
shippingCost: 599,
freeShippingThreshold: 5000
});
</script>
<!-- Children can now access cart context -->
<slot />
<!-- ProductCard.svelte -->
<script lang="ts">
import { getCartContext } from '$lib/cart/cart-context.svelte';
const cart = getCartContext();
const { product } = $props();
function addToCart() {
const result = cart.addItem(product, 1);
if (!result.success) {
alert(result.message);
}
}
</script>
<button onclick={addToCart}>
Add to Cart ({cart.itemCount})
</button>
<script lang="ts">
const cart = getCartContext();
</script>
<div class="cart-summary">
<div>Subtotal: ${(cart.summary.subtotal / 100).toFixed(2)}</div>
<div>Tax: ${(cart.summary.tax / 100).toFixed(2)}</div>
<div>Shipping: ${(cart.summary.shipping / 100).toFixed(2)}</div>
<hr />
<div>Total: ${(cart.summary.total / 100).toFixed(2)}</div>
</div>
All values automatically update when cart changes!
$state – Reactive primitive state$derived – Computed values that auto-update$derived.by() – Complex derived computations$effect – Side effects (persistence, logging)$effect.pre() – Pre-render effects (loading data)setContext() – Provide cart instance to childrengetContext() – Access cart from any descendanthasContext() – Safely check context availabilityinterface CartOptions {
storageKey?: string; // localStorage key (default: 'cart')
currency?: string; // Currency code (default: 'USD')
taxRate?: number; // Tax rate as decimal (default: 0.08)
shippingCost?: number; // Flat rate in cents (default: 599)
freeShippingThreshold?: number; // Free shipping minimum (default: 5000)
couponEndpoint?: string; // API endpoint (default: '/api/coupons/validate')
calculateShipping?: (subtotal: number, itemCount: number) => number;
}
interface CartContext {
// State (readonly)
items: CartItem[];
summary: CartSummary;
isEmpty: boolean;
isLoading: boolean;
appliedDiscount: AppliedDiscount | null;
itemCount: number;
totalQuantity: number;
// Actions
addItem(product: CartProduct, quantity?: number, options?: CartItemOptions): AddItemResult;
updateQuantity(id: string, quantity: number): UpdateQuantityResult;
removeItem(id: string): void;
clearCart(): void;
getItem(productId: string, options?: CartItemOptions): CartItem | undefined;
hasItem(productId: string, options?: CartItemOptions): boolean;
getQuantity(productId: string, options?: CartItemOptions): number;
applyDiscount(code: string): Promise<DiscountResult>;
removeDiscount(): void;
}
SAVE10 (10% off)FLAT500 ($5 off)/demo/marketplace/all-cartsSAVE10 – 10% off entire orderSAVE20 – 20% off entire orderFLAT500 – $5 flat discountFLAT1000 – $10 flat discountThis project is provided as-is for educational purposes. Feel free to use the code in your own projects.
Built with Svelte 5, SvelteKit, TypeScript, and love