Reference
Optional modules
The parts a deck imports on demand · activated by an attribute on deck-root or by an explicit import. None of them are in the core bundle; each fetches on first use.
What loads on demand, and what triggers it
Rikiki ships optional runtime modules and optional component modules. The initial load · the engine (dist/index.js, 26 KB gzip) plus Lit and marked, 43 KB gzip together · doesn't include any of them. Each one fetches separately the first time it is needed, so a deck that never uses a module pays nothing for it.
| Module | 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 | 6 KB | Press P | Speaker window with notes + next-slide preview + timer |
shiki | 0.4 KB loader + 113 KB runtime | Explicit import + installShiki() | Production-grade syntax highlighting for deck-code |
click-stages | 2.6 KB | Explicit import + installClickStages() | Reveal elements one click at a time with data-click |
deck-overview | 5 KB | Press O | Every slide as a thumbnail, with search · path layout up to 60 slides, two columns above |
deck-help | 1.4 KB | Press ? or H | Keyboard shortcut overlay |
deck-notes is not on this list: it ships inside the core bundle, so a slide can carry speaker notes with nothing imported.Speaker notes below covers it.
Optional component modules
The elements below are not imported by the default bundle and ship as separate files. Import the file only when the deck uses that element; the complete catalogue, attributes, and examples live in thecomponent reference.
deck-agenda ·deck-annotate ·deck-bar ·deck-checklist ·deck-figure ·deck-flow ·deck-graph ·deck-icon ·deck-kpi-grid ·deck-persona ·deck-pull ·deck-quote ·deck-table ·deck-timeline ·deck-versus
deck-transition · animations between slides
Setting a transition attribute on deck-roottriggers a lazy import of the module on first navigation. The module then animates both the outgoing and incoming slides with transforms only, so no background shows through whatever colors the two slides use.
<!-- 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.
Second screen (Chromium)
With an external screen attached, pressing P sends the deck fullscreen to that screen (the projector) and opens the speaker window on the screen you are working on. The audience sees the slides, you keep the presenter view on your laptop; closing the presenter releases the deck's fullscreen.
The first press asks for Window Management
Placing the two windows needs the Window Management permission, so the firstP press prompts for it. The speaker window opens either way, and the slides move to the projector once the screen layout is known · the very first time, that can take a second P press right after you grant the permission.
Without the API or a second screen
On Firefox and Safari, or with the permission refused, or on a single screen, the deck stays where it is and the speaker window opens at the browser's default placement. Everything else · notes, previews, timer, sync · works the same.
While the speaker window is open
The Current and Next previews are constrained to a 16:9 box, so a thumbnail keeps the projected slide's geometry whatever shape the window takes. The projected deck also auto-hides its key-hint chips and nav arrows (the same effect as no-hint and no-arrows) and restores them when the presenter closes.
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 aswhite-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 over a fixed keyword list, with no template-literal awareness and no JSX. Good enough for blog snippets, brittle for serious prod code.
Opt in to Shiki when you need TextMate-quality highlighting for TypeScript, JavaScript, HTML, CSS or JSON. Rikiki vendors only those five grammars and theone-dark-pro theme, which keeps the optional runtime at 113 KB gzip. The module loads dist/vendor/shiki.js on first use, from the deck's own folder rather than a CDN.
<!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', 'js', '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. Request one or more of ts,typescript, js, javascript,html, css and json; unsupported names are rejected during installation instead of silently growing the bundle. A code block whose language was not preloaded falls back to the regex highlighter. Under the hood it registers a highlighter through deck-code's publicsetDeckCodeHighlighter hook (see Writing a plugin) rather than patching the component.
click-stages · reveal elements on click
Click-through mode. 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-down, slide-left, slide-right, scale, blur, flip-up, draw (strokes an SVG on). |
data-anim-duration="600" | Entry duration in ms · default 320. |
data-anim-delay="120" | Delay before the entry starts, in ms · default 0. |
data-anim-ease="spring" | out (default), spring, in-out, or a raw cubic-bezier(…). |
Honours prefers-reduced-motion: reduce · reveals snap in without the transform, and the delay drops to zero, 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. The contract is DeckPlugin, an object you register 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) | Wraps the navigation · the engine hands you the move and you decide when it happens, by calling proceed() (optionally inside something of your own, a View Transition for instance). 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(). Fordeck-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, DeckContextand setDeckCodeHighlighter are re-exported fromdist/index.js.
installClickStages() and installShiki() are back-compat shims that attach the plugin to every deck-rootalready 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.
What opting in costs
The modules in the table at the top of this page are small. Two of them pull a vendored third-party payload behind them, which is where the real weight is · measured from the shipped files:
| Opting into | Module (gz) | Vendored payload (gz) |
|---|---|---|
deck-mermaid · a diagram on a slide | 1.7 KB | 975 KB · dist/vendor/mermaid.min.js |
installShiki() · TextMate highlighting | 0.4 KB | 113 KB · dist/vendor/shiki.js |
Neither payload is fetched by a deck that does not use the feature, and the engine (dist/index.js) stays at 26 KB gzip whichever modules the deck ends up importing.
Next
- The component catalogue · every optional element with its attributes, slots and tokens.
- API reference · the
DeckPlugincontract and thedeck-rootsurface it uses. - Navigation · the overview and help modules, and the keys that open them.