Widget integration guide

Updated 28 June 2026 (rev 2)

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

AttributeTypeRequiredDefaultDescription
boardstringYes-Board key from your dashboard embed settings
public-api-keystringYes-Public API key (safe to commit and ship)
themelight | darkNolightWidget colour scheme
modeembedded | buttonNoembeddedbutton renders a trigger that opens a modal overlay
publicboolean (presence)NoabsentEnable 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.

PropertyDefaultDescription
--uv-bg#FBF6ECPanel background
--uv-border#EFE8DABorders and dividers
--uv-brand#E7B24BAccent colour for coins, buttons, links
--uv-on-brand#1A1A2EText rendered on the brand colour
--uv-text#1A1A2EPrimary text
--uv-muted#8A8577Secondary / muted text
--uv-widget-width100%Width of the host element
--uv-widget-max-widthnoneMax-width of the host element
--uv-panel-width100%Width of the inner panel
--uv-panel-max-width100%Max-width of the inner panel
--uv-widget-min-height430pxMinimum 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.