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.
| Plugin | Size (gz) | Activated by | Use-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).
<!-- 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
| Value | Effect | Default 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 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-notesinside 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.
<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.
<!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.
<!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
| Attribute | Effect |
|---|---|
data-click | Hidden until the next click, then revealed. |
data-click="N" | Revealed on click N specifically. |
data-click-hide | Visible 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:
| Hook | When |
|---|---|
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. |
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
transitionon its root never fetchesdeck-transition.js. - A deck without an
installShiki()call never fetches Shiki. - No
installClickStages()call meansclick-stages.jsnever loads. - Speaker who never presses P never fetches
deck-presenter.js. deck-notesships 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.