AniUI
Blocks/Commerce

Product Detail

A product detail screen with image, pricing, size selector, quantity picker, and add-to-cart CTA.

Classic T-Shirt

$29.99$39.99-25%
4.5(128 reviews)

A timeless staple made from 100% organic cotton. Soft, breathable, and perfectly fitted — wear it dressed up or down. Available in multiple sizes and colors.

Size

Quantity

1

Installation

npx @aniui/cli add text button badge separator

Also requires react-native-safe-area-context for SafeAreaView.

Usage

Copy this screen into your app and wire up cart and wishlist handlers. Works with React Navigation or Expo Router.

import { ProductDetailScreen } from "@/screens/product-detail";

// In your navigator:
<Stack.Screen name="ProductDetail" component={ProductDetailScreen} />

Source

screens/product-detail.tsx
import React, { useState } from "react";
import { View, ScrollView, Pressable } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Text } from "@/components/ui/text";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";

const SIZES = ["S", "M", "L", "XL"] as const;
type Size = (typeof SIZES)[number];

export function ProductDetailScreen() {
  const [selectedSize, setSelectedSize] = useState<Size>("M");
  const [quantity, setQuantity] = useState(1);

  return (
    <SafeAreaView className="flex-1 bg-background">
      <ScrollView className="flex-1" showsVerticalScrollIndicator={false}>
        {/* Product Image */}
        <View className="h-48 bg-gradient-to-br from-primary/10 to-accent/20 items-center justify-center relative">
          <Pressable
            accessible={true}
            accessibilityRole="button"
            accessibilityLabel="Go back"
            className="absolute top-4 left-4 h-9 w-9 rounded-full bg-background/80 items-center justify-center"
          >
            {/* Back arrow icon */}
            <View className="w-5 h-5 items-center justify-center">
              <View className="w-2.5 h-0.5 bg-foreground rotate-45 translate-y-[1px]" />
              <View className="w-2.5 h-0.5 bg-foreground -rotate-45 -translate-y-[1px]" />
            </View>
          </Pressable>
          <Pressable
            accessible={true}
            accessibilityRole="button"
            accessibilityLabel="Add to wishlist"
            className="absolute top-4 right-4 h-9 w-9 rounded-full bg-background/80 items-center justify-center"
          >
            {/* Heart icon placeholder */}
            <View className="w-5 h-5 items-center justify-center">
              <View className="w-4 h-3 rounded-t-full border border-foreground" />
            </View>
          </Pressable>
        </View>

        <View className="px-5 pt-5 pb-8 gap-4">
          {/* Name */}
          <Text className="text-xl font-bold text-foreground">Classic T-Shirt</Text>

          {/* Price Row */}
          <View className="flex-row items-center gap-2">
            <Text className="text-xl font-bold text-foreground">$29.99</Text>
            <Text className="text-sm text-muted-foreground line-through">$39.99</Text>
            <Badge variant="destructive" className="bg-destructive/10 text-destructive rounded-full">
              -25%
            </Badge>
          </View>

          {/* Rating */}
          <View className="flex-row items-center gap-1.5">
            <Text className="text-sm text-foreground">4.5</Text>
            <Text className="text-sm text-muted-foreground">(128 reviews)</Text>
          </View>

          {/* Description */}
          <Text className="text-sm text-muted-foreground leading-relaxed">
            A timeless staple made from 100% organic cotton. Soft, breathable, and perfectly
            fitted — wear it dressed up or down. Available in multiple sizes and colors.
          </Text>

          <Separator />

          {/* Size Selector */}
          <View className="gap-2">
            <Text className="text-sm font-semibold text-foreground">Size</Text>
            <View className="flex-row gap-2">
              {SIZES.map((size) => (
                <Pressable
                  key={size}
                  accessible={true}
                  accessibilityRole="button"
                  accessibilityLabel={`Size ${size}`}
                  accessibilityState={{ selected: selectedSize === size }}
                  onPress={() => setSelectedSize(size)}
                  className={`min-w-12 h-10 items-center justify-center rounded-xl border px-3 ${
                    selectedSize === size
                      ? "border-primary bg-primary"
                      : "border-border bg-background"
                  }`}
                >
                  <Text
                    className={`text-sm font-semibold ${
                      selectedSize === size ? "text-primary-foreground" : "text-foreground"
                    }`}
                  >
                    {size}
                  </Text>
                </Pressable>
              ))}
            </View>
          </View>

          <Separator />

          {/* Quantity */}
          <View className="flex-row items-center justify-between">
            <Text className="text-sm font-semibold text-foreground">Quantity</Text>
            <View className="flex-row items-center gap-3">
              <Pressable
                accessible={true}
                accessibilityRole="button"
                accessibilityLabel="Decrease quantity"
                onPress={() => setQuantity((q) => Math.max(1, q - 1))}
                className="min-h-12 min-w-12 items-center justify-center rounded-xl border border-border bg-background"
              >
                <Text className="text-base font-semibold text-foreground">−</Text>
              </Pressable>
              <Text className="text-sm font-semibold text-foreground w-6 text-center">
                {quantity}
              </Text>
              <Pressable
                accessible={true}
                accessibilityRole="button"
                accessibilityLabel="Increase quantity"
                onPress={() => setQuantity((q) => q + 1)}
                className="min-h-12 min-w-12 items-center justify-center rounded-xl border border-border bg-background"
              >
                <Text className="text-base font-semibold text-foreground">+</Text>
              </Pressable>
            </View>
          </View>

          <Separator />

          {/* CTAs */}
          <View className="gap-3 mt-1">
            <Button onPress={() => {}} size="lg" className="w-full h-12 rounded-xl">
              Add to Cart
            </Button>
            <Button variant="outline" onPress={() => {}} size="lg" className="w-full h-12 rounded-xl">
              Add to Wishlist
            </Button>
          </View>
        </View>
      </ScrollView>
    </SafeAreaView>
  );
}