npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

live-quiz

v0.3.1

Published

Live audience quiz plugin for Reveal.js, powered by AnyCable

Downloads

644

Readme

live-quiz

npm version

Add live audience quizzes to your Reveal.js presentations. Powered by AnyCable.

Live Demo — open the presenter view in one tab and the audience page on your phone.

What You Get

You build a Reveal.js deck with quiz slides, deploy it to the web, and present it. When you land on a quiz slide, your audience sees a QR code, scans it on their phones, and votes — results animate on your slides in real time.

  • Multiple-choice questions with up to 4 options and live bar charts
  • Free-text questions with live word cloud results
  • QR code auto-generated on each quiz slide so the audience can join instantly
  • Live results that update as votes come in (sub-second via WebSockets)
  • Participant counter showing how many people are connected
  • Mobile-friendly voting page — no app install, just a browser
  • Automatic question sync — define questions once on your slides, the audience page receives them automatically
  • Theming — inherits your Reveal.js theme's fonts and colors automatically

How It Works

Your presentation needs to be deployed to the web (not just opened locally) because the audience connects to it from their phones. The setup has three parts:

  1. AnyCable — a managed WebSocket service that relays votes between the audience and your slides. The free tier supports up to 2,000 concurrent connections, which is plenty for conference talks and meetups.

  2. Your presentation — a static site (HTML + JS) deployed to Netlify or Vercel. The plugin adds quiz UI to your slides automatically.

  3. Serverless functions — 3 small files that run on Netlify or Vercel. They receive answers from the audience and broadcast results via AnyCable. Secrets stay in environment variables, never in your code.

Presenter's slides              AnyCable              Audience phones
       │                           │                        │
       │   show quiz slide         │                        │
       ├── broadcast state ───────►│── push state ─────────►│
       │  (questions + results)    │  (questions + results)  │
       │                           │                        │
       │                           │◄──── submit vote ──────┤
       │◄── broadcast results ─────┤     (serverless fn)    │
       │   update results           │                        │

Questions are defined once — as data-quiz-* attributes on your slides. The presenter broadcasts them to the audience page via the sync channel, so the participant widget doesn't need its own copy.

Getting Started

There are two ways to set up: the interactive CLI (recommended) or manual setup.

Both follow the same steps:

  1. Create a free AnyCable Plus app (provides the WebSocket infrastructure)
  2. Scaffold a Reveal.js project with quiz slides
  3. Deploy to Netlify or Vercel

Option A: Interactive CLI (recommended)

One command that walks you through everything — creates your AnyCable app, scaffolds the project, and optionally deploys it:

npx create-live-quiz

The CLI will:

  1. Open plus.anycable.io and guide you through creating an AnyCable app
  2. Ask for your WebSocket URL and Broadcast URL (the two values AnyCable gives you)
  3. Scaffold a complete project with quiz slides, audience page, and serverless functions
  4. Install dependencies and initialize git
  5. Deploy via Netlify/Vercel CLI (if installed) or show manual deploy instructions

Option B: Add to an existing Reveal.js presentation

If you already have a Reveal.js deck, you can add live quizzes to it manually.

1. Create an AnyCable Plus app

  1. Sign in at plus.anycable.io with GitHub
  2. Click New Cable, name it anything, pick JavaScript as your backend
  3. On the Application secret screen, clear the secret (empty the input) — this enables public streams mode
  4. After deploy, copy the WebSocket URL and Broadcast URL

2. Install the plugin

npm install live-quiz

3. Wire up the plugin

Add two imports and the liveQuiz config to your existing Reveal.initialize() call:

import RevealLiveQuiz from 'live-quiz';
import 'live-quiz/style.css';

// In your existing Reveal.initialize() call, add:
Reveal.initialize({
  plugins: [RevealLiveQuiz],  // add to your plugins array
  liveQuiz: {
    wsUrl: 'wss://your-cable.anycable.io/cable',   // ← from step 1
    quizGroupId: 'my-talk',
    quizUrl: `${window.location.origin}/quiz.html`,
  },
  // ...your existing config
});

quizUrl resolves dynamically — it will point to the right domain wherever you deploy.

4. Add quiz slides

Add data attributes to your slides — the plugin injects all the UI automatically:

<!-- Multiple-choice question -->
<section data-quiz-id="q1"
         data-quiz-question="Where are you joining from?"
         data-quiz-options='[
           {"label":"A","text":"San Francisco"},
           {"label":"B","text":"New York"},
           {"label":"C","text":"Europe"},
           {"label":"D","text":"Elsewhere"}
         ]'>
</section>

<!-- Results slide (references the quiz above) -->
<section data-quiz-results="q1"
         data-quiz-question="Where are you joining from?"
         data-quiz-options='[
           {"label":"A","text":"San Francisco"},
           {"label":"B","text":"New York"},
           {"label":"C","text":"Europe"},
           {"label":"D","text":"Elsewhere"}
         ]'>
</section>

<!-- Free-text question (word cloud results) -->
<section data-quiz-id="q2" data-quiz-type="text"
         data-quiz-question="What's your favorite framework?">
</section>

<section data-quiz-results="q2" data-quiz-type="text"
         data-quiz-question="What's your favorite framework?">
</section>

data-quiz-type defaults to "choice" when omitted, so existing slides work without changes.

5. Create the audience page

The audience needs a separate page to vote from their phones. Create a quiz.html and a script that mounts the participant widget:

import { createParticipantUI } from 'live-quiz/participant';
import 'live-quiz/participant.css';

createParticipantUI('#quiz-root', {
  wsUrl: 'wss://your-cable.anycable.io/cable',
  quizGroupId: 'my-talk',
});

That's it — questions are synced automatically from your presentation slides. No need to duplicate them here.

6. Add serverless functions and deploy

Your presentation must be deployed — the audience needs to reach it from their phones.

Copy the serverless functions from functions/netlify/ or functions/vercel/ into your project and set one environment variable:

| Variable | Required | Description | |---|---|---| | ANYCABLE_BROADCAST_URL | Yes | Broadcast URL from step 1 | | ANYCABLE_BROADCAST_KEY | No | Broadcast key (if your AnyCable app uses one) |

See functions/README.md for step-by-step deploy instructions for each platform.

AnyCable Plus

This plugin uses AnyCable Plus — a managed WebSocket service. The free tier includes:

  • Up to 2,000 concurrent connections
  • Public streams mode (no backend auth needed)
  • WebSocket + HTTP broadcast endpoints

A note on public streams

By default, the plugin uses public streams — WebSocket messages are not authenticated. This means anyone who knows the channel name could technically observe or interact with the quiz data. For most use cases (conference talks, meetups, workshops) this is perfectly fine — quiz votes aren't sensitive.

If your votes are confidential or you need to restrict who can participate, see Appendix: Authorized Streams.

Configuration

Plugin Options (liveQuiz)

| Option | Type | Required | Description | |---|---|---|---| | wsUrl | string | Yes | AnyCable WebSocket URL | | quizGroupId | string | Yes | Unique ID grouping quizzes in this talk | | quizUrl | string | No | Audience page URL (shown as QR code) | | endpoints | object | No | Custom endpoint paths (default: /.netlify/functions/*) | | titleText | string | No | Title shown on question slides (default: "Pop quiz!") |

Custom Endpoints

For Vercel, override the default Netlify paths:

liveQuiz: {
  endpoints: {
    answer: '/api/quiz-answer',
    sync: '/api/quiz-sync',
  }
}

Theming

The plugin inherits your Reveal.js theme's fonts and colors automatically via --r-* custom properties. Override --lq-* variables to fine-tune:

| Variable | Default | Description | |---|---|---| | --lq-accent | var(--r-link-color, #f59e0b) | Accent color (bar highlights, word cloud top word) | | --lq-text | var(--r-main-color, inherit) | Main text color | | --lq-text-muted | 50% of --lq-text | Secondary text | | --lq-font | var(--r-main-font, inherit) | Body font | | --lq-heading-font | var(--r-heading-font, inherit) | Heading font | | --lq-mono | var(--r-code-font, ...) | Monospace font | | --lq-bar-fill | 35% of --lq-text | Bar fill color | | --lq-bar-correct | var(--lq-accent) | Correct answer bar color | | --lq-bar-track | 10% of --lq-text | Bar track background | | --lq-border-radius | 0.5rem | Border radius |

Participant widget uses --lq-p-* variables — see participant/participant.css for the full list.

Data Attributes Reference

Question Slide

| Attribute | Description | |---|---| | data-quiz-id | Unique quiz identifier | | data-quiz-question | Question text | | data-quiz-type | "choice" (default) or "text" | | data-quiz-options | JSON array of {label, text, correct?} (choice only) |

Results Slide

| Attribute | Description | |---|---| | data-quiz-results | Quiz ID to show results for | | data-quiz-question | Question text (shown as title) | | data-quiz-type | "choice" (default) or "text" | | data-quiz-options | JSON array of {label, text, correct?} (choice only) |

Limitations

  • Two question types — multiple choice (up to 4 options) and free text (word cloud). No ratings or scales yet.
  • Requires deployment — the audience connects over the internet, so the presentation must be hosted, not served locally.
  • AnyCable free tier — supports up to 2,000 concurrent connections. For larger audiences, upgrade to a paid AnyCable Plus plan.
  • No persistent storage — quiz results live in memory during the presentation. Once the presenter closes the tab, results are gone.
  • Netlify and Vercel only — the serverless functions are provided for these two platforms. Other platforms (Cloudflare Workers, AWS Lambda) would need manual porting.

Appendix: Authorized Streams

TODO — Instructions for setting up AnyCable signed streams for private quizzes. Coming soon.

License

MIT