# Review

> Review built with shadcn/ui.

A shadcn/ui block from the [@shadcnuikit](https://shadcnuikit.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/shadcnuikit/review-card7).

- Page, with a live preview: https://shadcn.exchange/r/shadcnuikit/review-card7
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/shadcnuikit/review-card7-light-97befaeba7433139.png
- Registry item: https://shadcnuikit.com/r/review-card7.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://shadcnuikit.com/r/review-card7.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: `lucide-react`
- Registry items: `button`, `card`

## Files

### `contents/examples/cards/review/7/index.tsx`

```tsx
"use client";

import * as React from "react";
import {
  StarIcon,
  ThumbsUpIcon,
  ThumbsDownIcon,
  MoreHorizontalIcon,
  CheckCircleIcon,
} from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";

const reviewImages = [
  "https://images.unsplash.com/photo-1545454675-3531b543be5d?w=200&h=200&fit=crop",
  "https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=200&h=200&fit=crop",
  "https://images.unsplash.com/photo-1608043152269-423dbba4e7e1?w=200&h=200&fit=crop",
];

export default function ReviewCard() {
  const [helpful, setHelpful] = React.useState(1);
  const [unhelpful, setUnhelpful] = React.useState(3);
  const [vote, setVote] = React.useState<"helpful" | "unhelpful" | null>(null);

  function handleVote(type: "helpful" | "unhelpful") {
    if (vote === type) {
      setVote(null);
      type === "helpful" ? setHelpful((n) => n - 1) : setUnhelpful((n) => n - 1);
    } else {
      if (vote === "helpful") setHelpful((n) => n - 1);
      if (vote === "unhelpful") setUnhelpful((n) => n - 1);
      setVote(type);
      type === "helpful" ? setHelpful((n) => n + 1) : setUnhelpful((n) => n + 1);
    }
  }

  return (
    <Card className="w-full shadow-none md:w-[360px]">
      <CardContent className="space-y-3">
        {/* Header */}
        <div className="flex items-center justify-between">
          <div className="flex items-center gap-2.5">
            <img
              src="https://images.unsplash.com/photo-1531746020798-e6953c6e8e04?w=80&h=80&fit=crop&crop=face"
              alt="Somalie"
              className="size-9 rounded-full object-cover"
            />
            <span className="text-sm font-medium">By Somalie</span>
          </div>
          <Button variant="ghost" size="icon" className="size-7 text-muted-foreground">
            <MoreHorizontalIcon className="size-4" />
          </Button>
        </div>

        {/* Stars */}
        <div className="flex items-center gap-0.5">
          {Array.from({ length: 5 }).map((_, i) => (
            <StarIcon
              key={i}
              className={cn(
                "size-4",
                i < 1 ? "fill-amber-400 text-amber-400" : "fill-muted text-muted"
              )}
            />
          ))}
        </div>

        {/* Title + body */}
        <div className="space-y-1.5">
          <p className="font-semibold">Best Audio Upgrade</p>
          <p className="text-sm leading-relaxed text-muted-foreground">
            The soundbar with subwoofer has significantly upgraded my home
            theater system. The sound quality is incredible, and the setup was
            very straightforward. I love the wireless connectivity and the rich
            bass. Totally worth it.
          </p>
        </div>

        {/* Meta */}
        <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
          <span>1 year ago</span>
          <span>•</span>
          <CheckCircleIcon className="size-3.5 text-emerald-500" />
          <span>Verified Purchase</span>
        </div>

        {/* Images */}
        <div className="flex gap-2">
          {reviewImages.map((src, i) => (
            <img
              key={i}
              src={src}
              alt={`Review photo ${i + 1}`}
              className="size-16 rounded-lg object-cover"
            />
          ))}
        </div>

        {/* Actions */}
        <div className="flex items-center justify-between pt-1">
          <div className="flex items-center gap-3">
            <button
              onClick={() => handleVote("helpful")}
              className={cn(
                "flex items-center gap-1.5 text-sm transition-colors",
                vote === "helpful"
                  ? "text-foreground"
                  : "text-muted-foreground hover:text-foreground"
              )}>
              <ThumbsUpIcon
                className={cn("size-4", vote === "helpful" && "fill-foreground")}
              />
              Helpful ({helpful})
            </button>
            <button
              onClick={() => handleVote("unhelpful")}
              className={cn(
                "flex items-center gap-1.5 text-sm transition-colors",
                vote === "unhelpful"
                  ? "text-foreground"
                  : "text-muted-foreground hover:text-foreground"
              )}>
              <ThumbsDownIcon
                className={cn("size-4", vote === "unhelpful" && "fill-foreground")}
              />
              Unhelpful ({unhelpful})
            </button>
          </div>
          <button className="text-sm font-medium text-primary hover:underline">
            Report
          </button>
        </div>
      </CardContent>
    </Card>
  );
}
```
