Quick Start
Add the widget to any HTML page in two lines:
<script src="https://astrowidgets.dev/widgets/testimonials.js"></script>
<testimonial-widget layout="grid" theme="light"></testimonial-widget>
That's it. The widget ships with demo data so you can see it working immediately. Replace the data with your own reviews using the data attribute, JS API, or hosted mode.
Hosted Mode
Instead of managing review data yourself, you can use the Astro Widgets dashboard to create widgets, configure their appearance, and manage reviews. Your widget loads its config and reviews automatically at runtime using a widget-id.
How it works
- Sign up for an account and create a widget in the dashboard.
- Configure layout, theme, and other settings visually.
- Add reviews manually (or connect review sources in a future update).
- Copy the embed code — it includes your
widget-id.
Basic embed with meta tags
The simplest approach is to add Supabase config as <meta> tags on your page. All widgets on the page will use these automatically:
<!-- Add meta tags for Supabase config (once per page) -->
<meta name="supabase-url" content="https://your-project.supabase.co" />
<meta name="supabase-key" content="your-publishable-key" />
<script src="https://astrowidgets.dev/widgets/testimonials.js"></script>
<!-- The widget fetches its config and reviews automatically -->
<testimonial-widget widget-id="your-widget-uuid"></testimonial-widget>
Per-widget config override
You can also pass the Supabase URL and key directly on the widget element, which takes priority over meta tags:
<!-- Override Supabase config per widget (no meta tags needed) -->
<testimonial-widget
widget-id="your-widget-uuid"
supabase-url="https://your-project.supabase.co"
supabase-key="your-publishable-key"
></testimonial-widget>
When a widget-id is present, the widget fetches its saved configuration (layout, theme, visibility toggles, custom CSS) and reviews from the backend. You can still override any attribute on the element — local attributes take priority over the stored config.
Note: The publishable key is safe to include in client-side code. It only allows read access to published widgets. Never expose your secret/service key.
HTML Attributes
Configure the widget entirely through HTML attributes:
| Attribute | Type | Default | Description |
layout | string | "grid" | Layout style: grid, slider, list, badge, masonry |
theme | string | "light" | Color theme: light or dark |
columns | number | 3 | Number of columns for grid and masonry layouts |
max-reviews | number | 0 (all) | Maximum number of reviews to display. 0 shows all. |
show-rating | boolean | true | Show star ratings |
show-date | boolean | true | Show review dates |
show-source | boolean | true | Show source badges (Google, Yelp, etc.) |
show-avatar | boolean | true | Show reviewer avatars or initials |
autoplay | boolean | false | Auto-rotate slides (slider layout only) |
autoplay-speed | number | 5000 | Autoplay interval in milliseconds |
data | string | — | JSON string of review data (see Data Format) |
custom-css | string | — | Raw CSS to inject into the shadow DOM |
widget-id | string | — | UUID of a hosted widget. When set, the widget fetches its config and reviews from the backend. See Hosted Mode. |
supabase-url | string | — | Supabase project URL. Overrides the <meta name="supabase-url"> tag. Only needed with widget-id. |
supabase-key | string | — | Supabase publishable key. Overrides the <meta name="supabase-key"> tag. Only needed with widget-id. |
Example with all options
<testimonial-widget
layout="grid"
theme="dark"
columns="2"
max-reviews="4"
show-rating="true"
show-date="false"
show-source="true"
show-avatar="true"
autoplay="false"
></testimonial-widget>
CSS Custom Properties
Override the widget's appearance without touching its internals. Set these properties on the <testimonial-widget> element:
| Property | Default (Light) | Description |
--tw-bg | transparent | Widget background |
--tw-card-bg | #ffffff | Card background color |
--tw-card-bg-hover | #fafafa | Card background on hover |
--tw-text | #1a1a2e | Primary text color |
--tw-text-secondary | #64648c | Secondary text (dates, source names) |
--tw-accent | #6c5ce7 | Accent color (dots, highlights) |
--tw-star-color | #f59e0b | Filled star color |
--tw-star-empty | #d1d5db | Empty star color |
--tw-border-color | #e5e7eb | Border color |
--tw-border-radius | 12px | Card border radius |
--tw-card-shadow | subtle | Card box shadow |
--tw-card-shadow-hover | elevated | Card shadow on hover |
--tw-card-padding | 24px | Card internal padding |
--tw-card-border | 1px solid ... | Card border shorthand |
--tw-font-family | system fonts | Font family |
--tw-font-size | 15px | Review text size |
--tw-font-size-name | 15px | Reviewer name size |
--tw-font-size-small | 13px | Small text size (dates, sources) |
--tw-avatar-size | 44px | Avatar diameter |
--tw-gap | 20px | Gap between cards |
--tw-max-width | 1200px | Maximum widget width |
--tw-transition | 0.25s ease | Transition timing |
--tw-quote-font-style | normal | Quote text font style (try italic) |
Example: Custom brand colors
<testimonial-widget
layout="grid"
style="
--tw-accent: #e74c3c;
--tw-star-color: #e74c3c;
--tw-card-bg: #fef9f8;
--tw-border-radius: 20px;
--tw-font-family: Georgia, serif;
"
></testimonial-widget>
Custom CSS Injection
For complete control, inject arbitrary CSS into the widget's shadow DOM using the custom-css attribute:
<testimonial-widget
layout="grid"
custom-css=".tw-card { border: 2px solid #e74c3c; } .tw-name { color: #e74c3c; }"
></testimonial-widget>
Available internal class names
| Class | Element |
.tw-root | Root container |
.tw-card | Review card |
.tw-card-header | Card header (avatar + meta) |
.tw-avatar | Avatar image/initials |
.tw-name | Reviewer name |
.tw-stars | Star rating container |
.tw-date | Review date |
.tw-text | Review text (blockquote) |
.tw-source | Source badge |
.tw-card-footer | Card footer |
JavaScript API
Programmatically control the widget after it's rendered:
// Get the widget element
const widget = document.querySelector('testimonial-widget');
// Set reviews programmatically
widget.setReviews([
{
name: 'Jane Doe',
avatar: 'https://example.com/avatar.jpg',
rating: 5,
text: 'Amazing product!',
date: '2026-03-01',
source: 'google'
}
]);
// Or use the reviews property
widget.reviews = myReviewsArray;
// Change attributes dynamically
widget.setAttribute('layout', 'slider');
widget.setAttribute('theme', 'dark');
Data Format
Each review object accepts these fields:
{
"name": "Sarah Chen", // Required. Reviewer name.
"avatar": "", // Optional. URL to avatar image.
// If empty, initials are shown.
"rating": 5, // Required. 1-5 star rating.
"text": "Great service!", // Required. Review text.
"date": "2026-02-18", // Optional. ISO date string.
"source": "google" // Optional. One of: google, yelp,
// facebook, trustpilot
}
Via HTML attribute
Pass reviews as a JSON string in the data attribute:
<testimonial-widget
layout="grid"
data='[{"name":"Jane","rating":5,"text":"Wonderful!","source":"google"}]'
></testimonial-widget>
Astro Integration
In an Astro project, add the script and use the component directly in your .astro files:
---
// src/pages/index.astro
import Layout from '../layouts/Layout.astro';
const reviews = await fetch('/api/reviews').then(r => r.json());
---
<Layout>
<script src="/widgets/testimonials.js" is:inline></script>
<testimonial-widget
layout="masonry"
columns="3"
theme="light"
data={JSON.stringify(reviews)}
></testimonial-widget>
</Layout>
You can also host the widget JS in your own public/ directory instead of loading it from our CDN.
Dynamic island pattern
For dynamic review loading, use a client-side script:
<testimonial-widget id="reviews" layout="grid"></testimonial-widget>
<script is:inline>
fetch('/api/reviews')
.then(r => r.json())
.then(data => {
document.getElementById('reviews').setReviews(data);
});
</script>