Add testimonial components to your React app. NPM package with TypeScript support. Or just drop in a script tag.
The recommended way to add Shoutjar to React. Full TypeScript support and tree-shakeable.
npm install @shoutjar/react
# or
yarn add @shoutjar/react
# or
pnpm add @shoutjar/reactimport { ShoutjarWidget } from '@shoutjar/react'
function App() {
return (
<div>
<h1>My App</h1>
<ShoutjarWidget widgetId="YOUR_WIDGET_ID" />
</div>
)
}
export default Appimport { ShoutjarWidget } from '@shoutjar/react'
function Testimonials() {
return (
<ShoutjarWidget
widgetId="YOUR_WIDGET_ID"
style="carousel"
theme="dark"
maxItems={6}
showRating={true}
showSource={true}
className="my-4"
/>
)
}// Grid - Multiple testimonials in columns
<ShoutjarWidget widgetId="..." style="grid" />
// Carousel - One at a time, auto-rotate
<ShoutjarWidget widgetId="..." style="carousel" />
// Marquee - Continuous scrolling ticker
<ShoutjarWidget widgetId="..." style="marquee" />
// Badge - Compact rating display
<ShoutjarWidget widgetId="..." style="badge" />
// Single - Feature one testimonial
<ShoutjarWidget widgetId="..." style="single" />For simple setups or quick testing. No build step required.
<!-- In your index.html -->
<script src="https://cdn.shoutjar.com/widget.js" defer></script>function Testimonials() {
return (
<div
data-shoutjar-widget="YOUR_WIDGET_ID"
data-shoutjar-style="carousel"
/>
)
}import { useEffect, useRef } from 'react'
function ShoutjarEmbed({ widgetId, style = 'carousel' }) {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
if (window.Shoutjar && ref.current) {
window.Shoutjar.init(ref.current)
}
}, [widgetId])
return (
<div
ref={ref}
data-shoutjar-widget={widgetId}
data-shoutjar-style={style}
/>
)
}The NPM package is recommended for production apps. The script tag works but lacks TypeScript support and tree-shaking.
Full TypeScript definitions included. No @types packages needed.
import { ShoutjarWidget } from '@shoutjar/react'
import type { WidgetProps, WidgetStyle, WidgetTheme } from '@shoutjar/react'
type WidgetStyle = 'grid' | 'carousel' | 'marquee' | 'badge' | 'single'
type WidgetTheme = 'light' | 'dark' | 'auto'
interface WidgetProps {
widgetId: string
style?: WidgetStyle
theme?: WidgetTheme
maxItems?: number
showRating?: boolean
showSource?: boolean
className?: string
onLoad?: () => void
onError?: (error: Error) => void
}import { ShoutjarWidget, WidgetStyle } from '@shoutjar/react'
interface Props {
testimonialStyle: WidgetStyle
}
function Testimonials({ testimonialStyle }: Props) {
return (
<ShoutjarWidget
widgetId={import.meta.env.VITE_SHOUTJAR_WIDGET_ID}
style={testimonialStyle}
/>
)
}Same NPM package, same component. Just different environment variable prefixes.
import { ShoutjarWidget } from '@shoutjar/react'
function App() {
return (
<div className="App">
<ShoutjarWidget
widgetId={process.env.REACT_APP_SHOUTJAR_WIDGET_ID!}
/>
</div>
)
}REACT_APP_SHOUTJAR_WIDGET_ID=your_widget_idimport { ShoutjarWidget } from '@shoutjar/react'
function App() {
return (
<ShoutjarWidget
widgetId={import.meta.env.VITE_SHOUTJAR_WIDGET_ID}
/>
)
}VITE_SHOUTJAR_WIDGET_ID=your_widget_idimport { ShoutjarWidget } from '@shoutjar/react'
export default function Page() {
return (
<ShoutjarWidget
widgetId={process.env.GATSBY_SHOUTJAR_WIDGET_ID}
/>
)
}Same as Vite -- environment variables work with PARCEL_ prefix.
For Next.js, see our dedicated Next.js integration guide -- it covers App Router, Pages Router, SSR, and dynamic imports.
function Hero() {
return (
<section className="hero">
<h1>Your Product</h1>
<p>Value proposition here</p>
<button>Get Started</button>
<ShoutjarWidget
widgetId="YOUR_WIDGET_ID"
style="badge"
className="mt-4"
/>
</section>
)
}function TestimonialsSection() {
return (
<section className="py-16 bg-gray-50">
<div className="max-w-6xl mx-auto">
<h2 className="text-3xl font-bold text-center mb-8">
What Our Customers Say
</h2>
<ShoutjarWidget
widgetId="YOUR_WIDGET_ID"
style="grid"
maxItems={6}
/>
</div>
</section>
)
}function Footer() {
return (
<footer>
<ShoutjarWidget
widgetId="YOUR_WIDGET_ID"
style="marquee"
className="border-t border-b py-4"
/>
<div className="py-8">{/* Footer links */}</div>
</footer>
)
}function Testimonials({ isVisible = true }) {
if (!isVisible) return null
return (
<ShoutjarWidget
widgetId="YOUR_WIDGET_ID"
onLoad={() => console.log('Widget loaded')}
onError={(err) => console.error('Widget error:', err)}
/>
)
}function App() {
return (
<>
<header>
<ShoutjarWidget widgetId="badge_widget_id" style="badge" />
</header>
<main>
<ShoutjarWidget widgetId="grid_widget_id" style="grid" />
</main>
<footer>
<ShoutjarWidget widgetId="marquee_widget_id" style="marquee" />
</footer>
</>
)
}<ShoutjarWidget
widgetId="YOUR_WIDGET_ID"
className="rounded-lg shadow-lg border"
/>:root {
--shoutjar-primary: #3B82F6;
--shoutjar-background: #ffffff;
--shoutjar-text: #111827;
--shoutjar-text-secondary: #6B7280;
--shoutjar-border: #E5E7EB;
--shoutjar-radius: 8px;
}
.dark {
--shoutjar-background: #1F2937;
--shoutjar-text: #F9FAFB;
--shoutjar-text-secondary: #9CA3AF;
--shoutjar-border: #374151;
}<ShoutjarWidget
widgetId="YOUR_WIDGET_ID"
className="max-w-4xl mx-auto p-4 bg-white dark:bg-gray-800 rounded-xl shadow-md"
/>import styled from 'styled-components'
import { ShoutjarWidget } from '@shoutjar/react'
const StyledWrapper = styled.div`
background: linear-gradient(to bottom, #f9fafb, #ffffff);
padding: 2rem;
border-radius: 1rem;
`
function Testimonials() {
return (
<StyledWrapper>
<ShoutjarWidget widgetId="YOUR_WIDGET_ID" />
</StyledWrapper>
)
}| Prop | Type | Default | Description |
|---|---|---|---|
widgetId | string | required | Your Shoutjar widget ID |
style | string | 'carousel' | Widget style: grid, carousel, marquee, badge, single |
theme | string | 'auto' | Color theme: light, dark, auto |
maxItems | number | undefined | Maximum testimonials to show |
showRating | boolean | true | Show star ratings |
showSource | boolean | true | Show source icons (Twitter, G2, etc.) |
className | string | undefined | Custom CSS class |
onLoad | () => void | undefined | Callback when widget loads |
onError | (error: Error) => void | undefined | Callback on error |
gzipped bundle
dependencies
tree-shakeable
import { lazy, Suspense } from 'react'
const ShoutjarWidget = lazy(() =>
import('@shoutjar/react').then(mod => ({ default: mod.ShoutjarWidget }))
)
function Testimonials() {
return (
<Suspense fallback={<div>Loading testimonials...</div>}>
<ShoutjarWidget widgetId="YOUR_WIDGET_ID" />
</Suspense>
)
}One widget ID per style -- don't reuse the same widget with different styles
Lazy load widgets below the fold
Use environment variables -- don't hardcode widget IDs
Let widgets handle responsiveness -- don't constrain width unnecessarily
Shoutjar pulls in reviews from multiple platforms into one unified widget
Shoutjar monitors mentions of your product across the web -- even untagged tweets and Reddit threads.
All sources display in one unified widget -- no multiple integrations needed.
Yes. Install the NPM package and use it like any React component.
Yes. Same as CRA -- install and import the component.
Yes. Full type definitions included in the package.
~3KB gzipped. Zero dependencies (React is a peer dependency).
Yes. For Next.js, see our dedicated Next.js guide. For other SSR frameworks, the component renders client-side.
Yes. Use className prop, CSS variables, or configure in Shoutjar dashboard.
Add or edit testimonials in Shoutjar. Changes appear in your app automatically -- no code changes needed.
Yes. Each can have different styles and configurations.
NPM package with TypeScript support. Works with CRA, Vite, and any React setup. Start in 5 minutes.