Widget integration guide
Embed conviction voting in any web app in under five minutes. The unovote-widget web component handles rendering, coin allocation, and participant verification out of the box.
Add a conviction-voting board to any web page with two lines of HTML. Participants allocate a fixed pool of coins across your competing ideas. Spending on one means skipping another, revealing intensity of preference.
Quick start
Add the script tag and the custom element anywhere in your HTML. No build step required.
<script src="https://cdn.unovote.com/widget.js" defer></script> <unovote-widget board="your-board-key" public-api-key="pk_your_key"> </unovote-widget>
Find your board key and public API key in your board embed settings.
Widget attributes
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
board | string | Yes | - | Board key from your dashboard embed settings |
public-api-key | string | Yes | - | Public API key (safe to commit and ship) |
theme | light | dark | No | light | Widget colour scheme |
mode | embedded | button | No | embedded | button renders a trigger that opens a modal overlay |
public | boolean (presence) | No | absent | Enable public mode where visitors verify by email OTP instead of being identified |
Identified participants
When your users are signed in, call identify() after the widget mounts. The widget passes the backer ID to the API so votes are linked to your user record.
<script> const widget = document.querySelector('unovote-widget'); widget.identify({ backerId: 'user_123', // required: your stable user ID backerEmail: 'user@example.com', // optional: pre-fills the email field backerName: 'Jane Smith', // optional: display name }); </script>
Call identify() as soon as your auth state resolves. The widget queues the call if board data has not loaded yet.
Public mode
Public mode lets anonymous visitors vote. They allocate coins first, then enter their email address to confirm their submission via a one-time code.
<unovote-widget board="your-board-key" public-api-key="pk_your_key" public> </unovote-widget>
Do not call identify() in public mode. The OTP flow handles participant identity.
Theming
Override widget colours and dimensions with CSS custom properties on the host element.
| Property | Default | Description |
|---|---|---|
--uv-bg | #FBF6EC | Panel background |
--uv-border | #EFE8DA | Borders and dividers |
--uv-brand | #E7B24B | Accent colour for coins, buttons, links |
--uv-on-brand | #1A1A2E | Text rendered on the brand colour |
--uv-text | #1A1A2E | Primary text |
--uv-muted | #8A8577 | Secondary / muted text |
--uv-widget-width | 100% | Width of the host element |
--uv-widget-max-width | none | Max-width of the host element |
--uv-panel-width | 100% | Width of the inner panel |
--uv-panel-max-width | 100% | Max-width of the inner panel |
--uv-widget-min-height | 430px | Minimum panel height |
unovote-widget { --uv-bg: #ffffff; --uv-brand: #6366f1; --uv-on-brand: #ffffff; --uv-widget-max-width: 480px; }
Content Security Policy
If your app sets a Content-Security-Policy header, add these two directives:
script-src 'self' https://cdn.unovote.com;
connect-src 'self' https://app.unovote.com;
The widget loads from cdn.unovote.com and calls the API at app.unovote.com. No other origins are required.
React and Next.js
The web component works in React without any extra package. Load the script once (e.g. in your root layout) and use the element directly in JSX.
// app/layout.tsx (Next.js App Router) export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html> <head> <script src="https://cdn.unovote.com/widget.js" defer /> </head> <body>{children}</body> </html> ); }
// FeatureVoting.tsx 'use client'; import { useEffect, useRef } from 'react'; declare global { namespace JSX { interface IntrinsicElements { 'unovote-widget': React.HTMLAttributes<HTMLElement> & { board?: string; 'public-api-key'?: string; theme?: 'light' | 'dark'; mode?: 'embedded' | 'button'; public?: boolean; }; } } } export function FeatureVoting({ userId }: { userId: string }) { const ref = useRef<HTMLElement>(null); useEffect(() => { const el = ref.current as HTMLElement & { identify?: (b: object) => void }; el?.identify?.({ backerId: userId }); }, [userId]); return ( <unovote-widget ref={ref} board="your-board-key" public-api-key="pk_your_key" /> ); }
TypeScript requires the declare global block to recognise custom elements in JSX. Add it once in a global.d.ts file to share it across your project.
Get your API key
Open Board settings → Embed in your Unovote dashboard. Copy the board key and public API key shown in the embed snippet. The public API key is safe to include in client-side code.