Reference

Plugins

Optional add-ons · activated by an attribute on deck-root or an explicit import. None of them are in the core bundle · they fetch on first use only.

The five plugins

Rikiki ships five optional modules. The core (dist/index.js, ~14 KB gzip) doesn't load any of them. Each one fetches separately the first time it's needed, so a deck that doesn't use a plugin pays nothing for it.

PluginSize (gz)Activated byUse-case
deck-transition ~1.7 KB transition="..." attr on deck-root Slide-in / fade / zoom / flip animations between slides
deck-presenter ~2.9 KB Press P Speaker window with notes + next-slide preview + timer
shiki ~0.6 KB + vendored Shiki Explicit import + installShiki() Production-grade syntax highlighting for deck-code
click-stages ~0.8 KB Explicit import + installClickStages() Reveal elements one click at a time (Slidev-style v-click)
deck-notes ~0.4 KB (auto-loaded with core) Use the deck-notes element Speaker notes (read by deck-presenter)

deck-transition · animations between slides

Setting a transition attribute on deck-root triggers a lazy import of the plugin on first navigation. The plugin then cross-fades both the outgoing and incoming slides on every navigation (no background flash regardless of slide colors).

basic usage
<!-- Set on deck-root · the entire deck animates -->
<deck-root transition="slide">
  <deck-cover>...</deck-cover>
  <deck-section>...</deck-section>
  <deck-feature>...</deck-feature>
</deck-root>

Available transitions

ValueEffectDefault duration
slide Auto-directional horizontal. Forward enters from the right, backward from the left.560 ms
slide-right Same as slide but starts in the left-to-right direction (still auto-flips on backward nav).560 ms
slide-up Auto-directional vertical. Forward enters from below, backward from above.520 ms
slide-down Mirror of slide-up.520 ms
fade Soft scale-up · the default if no value matches.480 ms
zoom Aggressive scale-up with spring ease (good for hooks/punchlines).520 ms
flip 3D rotateY · subtle card-flip feel.560 ms

Per-slide override

Set data-transition on any slide host to override the deck-wide default for that slide only.

per-slide
<!-- Per-slide override · data-transition wins over the parent attribute -->
<deck-root transition="fade">
  <deck-cover>...</deck-cover>
  <deck-section data-transition="zoom">...</deck-section>
  <deck-takeaway data-transition="flip">...</deck-takeaway>
</deck-root>

All transitions are transform-only · no opacity, no background animation, no flashing. Honours prefers-reduced-motion: reduce (animations disable cleanly).

deck-presenter · speaker window

Press P during the talk · a popup window opens (you'll need to allow popups for the deck's origin). The window shows:

  • Current slide · live mirror of what the audience sees
  • Next slide · preview of what's coming
  • Speaker notes · the text content of deck-notes inside the current slide
  • Timer · auto-starts, pause / resume / reset buttons
  • Slide counter · current / total

The popup syncs with the main deck via BroadcastChannel · no localStorage races, no postMessage ceremony. Forward keyboard input from the popup also drives the main deck, so you can use one laptop with the second screen mirroring the audience view.

On a Chromium browser with a second screen, pressing P sends the deck fullscreen to the external screen (the projector) and opens the speaker window on your current screen · the audience sees the slides, you keep the presenter view on your laptop (the deck fullscreen is released when you close the presenter). The first P press prompts for the Window Management permission · the speaker window opens regardless, and the slides move to the projector once the layout is known (the very first time that can take a second P press, just after you grant the permission). Without the API (Firefox/Safari), the permission, or a second screen, the deck stays put and the popup uses the default placement. The Current / Next previews are constrained to a 16:9 box so the thumbnail matches the projected slide's geometry whatever the window shape. While the speaker window is open, the projected deck auto-hides its key-hint chips and nav arrows (same as no-hint / no-arrows), restoring them on close.

Speaker notes · the deck-notes element

Add deck-notes inside any slide host. The element is hidden in the audience view (:host { display: none }) and read by the presenter window via textContent. Free-form text · use markdown-style bullets if you want, but it's rendered as white-space: pre-wrap.

notes example
<deck-root>
  <deck-cover>
    <h1>Hello</h1>
  </deck-cover>
  <deck-feature>
    <h1 slot="title">My slide</h1>
    <p slot="lead">Audience sees this.</p>

    <deck-notes>
      Speaker-only text. Only visible in the presenter window.
      - Bullet point one
      - Pause for laughter
      - Mention the rollout date
    </deck-notes>
  </deck-feature>
</deck-root>

shiki · production code highlighting

The default deck-code highlighter is a hand-rolled regex pass (~10 keywords per language, no template literal awareness, no JSX). Good enough for blog snippets, brittle for serious prod code.

Opt in to Shiki when you need real syntax highlighting · TextMate grammars, full theme set, JSX/TSX, template literal interpolation. The vendored Shiki bundle is large (every grammar + theme, JS engine, no wasm) · the plugin loads it from dist/vendor/shiki.js on first use (offline, no CDN), so the cost is paid only if you opt in.

install shiki
<!doctype html>
<html>
<head>
  <link rel="stylesheet" href="rikiki/themes/rikiki.css">
  <script type="module" src="rikiki/dist/index.js"></script>

  <!-- Opt-in Shiki highlighter · loads the vendored Shiki bundle on first highlight (offline) -->
  <script type="module">
    import { installShiki } from './rikiki/dist/shiki.js';
    await installShiki({
      theme: 'one-dark-pro',
      langs: ['ts', 'tsx', 'html', 'css', 'json'],
    });
  </script>
</head>
<body>
  <deck-root>
    <deck-feature>
      <h1 slot="title">Real syntax</h1>
      <deck-code lang="ts" hero>
        const fn = <T,>(x: T): T => x;
      </deck-code>
    </deck-feature>
  </deck-root>
</body>
</html>

After install, every existing and future deck-code instance re-renders through Shiki. Languages that aren't loaded fall back to the regex highlighter so the deck never breaks. Under the hood it registers a highlighter through deck-code's public setDeckCodeHighlighter hook (see Writing a plugin) rather than patching the component.

click-stages · reveal elements on click

Slidev-style click-through. Tag any element with data-click and it stays hidden until you click, then fades in. The step dots at the bottom count the reveals, and stepping back hides them again. It builds on the same step engine deck-code already uses, so one slide can mix click reveals and code step-groups.

install click-stages
<!doctype html>
<html>
<head>
  <link rel="stylesheet" href="rikiki/themes/rikiki.css">
  <script type="module" src="rikiki/dist/index.js"></script>

  <!-- Opt-in click-through reveals -->
  <script type="module">
    import { installClickStages } from './rikiki/dist/click-stages.js';
    installClickStages();
  </script>
</head>
<body>
  <deck-root>
    <deck-feature>
      <h1 slot="title">Build it up</h1>
      <p data-click>Shows on the first click.</p>
      <p data-click data-anim="slide-up">Then this one, sliding up.</p>
      <p data-click="3" data-anim="scale">Jumps straight to click 3.</p>
      <p data-click-hide>Here now, gone on the next click.</p>
    </deck-feature>
  </deck-root>
</body>
</html>

Attributes

AttributeEffect
data-clickHidden until the next click, then revealed.
data-click="N"Revealed on click N specifically.
data-click-hideVisible at first, hidden on the next click.
data-anim="..."How it enters · fade (default), slide-up, slide-left, scale.

Nothing loads unless you call installClickStages(). Honours prefers-reduced-motion: reduce · reveals snap in without the transform when the reader asks for less motion.

Writing a plugin

Both click-stages and shiki extend the deck through a small public contract rather than reaching into engine internals. A plugin is a DeckPlugin object; register it on a deck with deckRoot.use(plugin), which returns an unregister function. The hooks are all optional:

HookWhen
setup(ctx)On register · may return a teardown run on unregister.
steps(slide, ctx)Contributes to the slide's step count · combined with the engine's own as a maximum.
applyStep(step, slide, ctx)After the engine applied a step · toggle your elements here.
navigate(to, ctx, proceed)Around-advice · call proceed() to navigate (optionally wrapped, e.g. a View Transition). Return truthy to take ownership; the first plugin with this hook wins.
a deck-root plugin
import type { DeckPlugin } from './rikiki/dist/index.js';

const stopwatch: DeckPlugin = {
  name: 'stopwatch',
  setup(ctx) {
    const t = setInterval(() => console.log('on slide', ctx.current), 1000);
    return () => clearInterval(t);          // teardown on unregister
  },
  steps: (slide) => slide.querySelectorAll('[data-reveal]').length,
  applyStep(step, slide) { /* toggle your elements for this step */ },
  navigate(to, ctx, proceed) { return false; }, // false = let the engine navigate
};

document.querySelector('deck-root').use(stopwatch);

The ctx (DeckContext) is the only surface a plugin touches: ctx.host, ctx.current, ctx.step, ctx.slides, ctx.requestUpdate(). For deck-code highlighting there's a separate hook, setDeckCodeHighlighter(fn), where fn(code, lang) returns the block's inner HTML or null to fall back to the built-in regex highlighter. DeckPlugin, DeckContext and setDeckCodeHighlighter are re-exported from dist/index.js.

installClickStages() and installShiki() are back-compat shims that attach the plugin to every deck-root already on the page · a deck created dynamically after the call must register the plugin itself with use().

Slide zoom

In the default fixed canvas, Ctrl/ + wheel and trackpad pinch magnify the active slide around the cursor; +/- zoom by steps and 0 resets. While magnified, a plain wheel and pointer drag pan the slide, and changing slide snaps back to fit. It scales the whole slide uniformly (fonts and layout together, no reflow), so it doubles as a "bigger fonts" control. It is a no-op in fluid mode and in overview; set no-zoom on deck-root to leave zoom to the browser.

Cost model

Plugin costs are paid only when you opt in:

  • A deck without transition on its root never fetches deck-transition.js.
  • A deck without an installShiki() call never fetches Shiki.
  • No installClickStages() call means click-stages.js never loads.
  • Speaker who never presses P never fetches deck-presenter.js.
  • deck-notes ships with the core (0.4 KB) but if you don't use the element, it's just one unused custom-element registration.

Core (dist/index.js) stays at ~14 KB gzip regardless of which plugins your deck eventually uses.