UIPackage
Menu

Framework

Change language

Boilerplate repo

Ratings & Review Badges

blockmarketing

Third-party proof strip: four review-platform cards with star rating, score out of five, review count, and a category award line, over a rating-distribution bar and a verified-reviews footnote.

Also available for Vue ->

Installation

$npx shadcn@latest add https://uipkge.dev/r/react/ratings-review-badges.json
Named registry:npx shadcn@latest add @uipkge-react/ratings-review-badgesInstalls to:components/blocks/

Variants

Loading interactive previews…

Files installed (1)

  • components/blocks/RatingsReviewBadges.tsx4.5 kB
    'use client'
    
    import { Star } from 'lucide-react'
    import { Badge } from '@/components/ui/badge'
    import { Button } from '@/components/ui/button'
    import { Card, CardContent } from '@/components/ui/card'
    import { Progress } from '@/components/ui/progress'
    import { Separator } from '@/components/ui/separator'
    
    const platforms = [
      { name: 'G2', score: 4.8, reviews: 412, award: 'Leader — Analytics Platforms, Winter 2026' },
      { name: 'Capterra', score: 4.7, reviews: 268, award: 'Best Value — Business Intelligence' },
      { name: 'Product Hunt', score: 4.9, reviews: 1340, award: '#1 Product of the Day' },
      { name: 'TrustRadius', score: 4.6, reviews: 97, award: 'Top Rated — Data Governance' },
    ]
    
    const distribution = [
      { stars: 5, share: 78 },
      { stars: 4, share: 16 },
      { stars: 3, share: 4 },
      { stars: 2, share: 1 },
      { stars: 1, share: 1 },
    ]
    
    const STAR_SLOTS = [1, 2, 3, 4, 5]
    
    /** Fill percentage for one star slot, so a 4.6 renders a real partial star. */
    function fillFor(score: number, slot: number) {
      return `${Math.min(Math.max(score - (slot - 1), 0), 1) * 100}%`
    }
    
    export function RatingsReviewBadges() {
      return (
        <section data-slot="ratings-review-badges" className="bg-background">
          <div className="mx-auto max-w-6xl px-6 py-20 lg:py-28">
            <div className="max-w-2xl">
              <Badge variant="secondary">Reviews</Badge>
              <h2 className="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">
                Rated by the people who run it daily
              </h2>
              <p className="text-muted-foreground mt-3 text-lg">
                Every score below is pulled from a public review platform. None of them are ours to edit.
              </p>
            </div>
    
            <div className="mt-10 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
              {platforms.map((platform) => (
                <Card key={platform.name}>
                  <CardContent className="p-5">
                    <div className="flex items-baseline justify-between gap-3">
                      <p className="text-sm font-semibold">{platform.name}</p>
                      <p className="text-muted-foreground font-mono text-xs">{platform.reviews} reviews</p>
                    </div>
    
                    <div className="mt-3 flex items-center gap-2">
                      <div className="flex items-center gap-0.5" role="img" aria-label={`${platform.score} out of 5`}>
                        {STAR_SLOTS.map((slot) => (
                          <span key={slot} className="relative inline-flex">
                            <Star className="text-muted-foreground/30 size-4" aria-hidden="true" />
                            {/* Dynamic clip width is the only way to draw a partial star. */}
                            <span
                              className="absolute inset-y-0 left-0 overflow-hidden"
                              style={{ width: fillFor(platform.score, slot) }}
                              aria-hidden="true"
                            >
                              <Star className="fill-primary text-primary size-4" />
                            </span>
                          </span>
                        ))}
                      </div>
                      <span className="text-sm font-medium">{platform.score.toFixed(1)}</span>
                    </div>
    
                    <p className="text-muted-foreground mt-4 text-xs leading-relaxed">{platform.award}</p>
                  </CardContent>
                </Card>
              ))}
            </div>
    
            <Separator className="my-10" />
    
            <div className="grid gap-10 lg:grid-cols-[1fr_1.4fr] lg:items-center">
              <div>
                <p className="font-display text-5xl font-bold tracking-tight">4.8</p>
                <p className="text-muted-foreground mt-2 text-sm">
                  Weighted average across 2,117 verified reviews on four platforms.
                </p>
                <Button variant="outline" className="mt-5">
                  Read the reviews
                </Button>
              </div>
    
              <dl className="space-y-3">
                {distribution.map((row) => (
                  <div key={row.stars} className="flex items-center gap-4">
                    <dt className="text-muted-foreground w-12 shrink-0 font-mono text-xs">{row.stars} star</dt>
                    <dd className="flex min-w-0 grow items-center gap-4">
                      <Progress value={row.share} className="h-1.5" aria-label={`${row.stars} star reviews`} />
                      <span className="text-muted-foreground w-10 shrink-0 text-right font-mono text-xs">{row.share}%</span>
                    </dd>
                  </div>
                ))}
              </dl>
            </div>
          </div>
        </section>
      )
    }
    

Raw manifest:https://uipkge.dev/r/react/ratings-review-badges.json