| 1 | <p align="center"> |
| 2 | <a href="https://revealjs.com"> |
| 3 | <img src="https://hakim-static.s3.amazonaws.com/reveal-js/logo/v1/reveal-black-text-sticker.png" alt="reveal.js" width="500"> |
| 4 | </a> |
| 5 | </p> |
| 6 | |
| 7 | # @revealjs/react |
| 8 | |
| 9 | `@revealjs/react` is a thin React wrapper around the [Reveal.js](https://revealjs.com) presentation framework. Describe your slides as React components and let the wrapper handle the rest. |
| 10 | |
| 11 | ## Installation |
| 12 | |
| 13 | Install the package along with its peer dependencies: |
| 14 | |
| 15 | ```bash |
| 16 | npm i @revealjs/react reveal.js react react-dom |
| 17 | # or |
| 18 | yarn add @revealjs/react reveal.js react react-dom |
| 19 | ``` |
| 20 | |
| 21 | The package ships only the React bindings. You still need to import Reveal CSS, themes, and any plugins your deck uses. |
| 22 | |
| 23 | ## Set up a deck |
| 24 | |
| 25 | Render a `Deck` with one or more `Slide` children and import the core Reveal styles: |
| 26 | |
| 27 | ```tsx |
| 28 | import { Deck, Slide } from '@revealjs/react'; |
| 29 | import 'reveal.js/reveal.css'; |
| 30 | import 'reveal.js/theme/black.css'; |
| 31 | |
| 32 | export function Presentation() { |
| 33 | return ( |
| 34 | <Deck> |
| 35 | <Slide> |
| 36 | <h1>Hello</h1> |
| 37 | <p>My first Reveal deck in React.</p> |
| 38 | </Slide> |
| 39 | |
| 40 | <Slide background="#111827"> |
| 41 | <h2>Second slide</h2> |
| 42 | </Slide> |
| 43 | </Deck> |
| 44 | ); |
| 45 | } |
| 46 | ``` |
| 47 | |
| 48 | ## Components |
| 49 | |
| 50 | Alongside `Deck` and `Slide`, the package ships a few components for common slide patterns. `Fragment` reveals content one step at a time, `Code` renders a syntax-highlighted block via the highlight plugin, `Stack` groups slides into a vertical column, and `Markdown` renders Reveal-compatible markdown slides without registering the Reveal markdown plugin: |
| 51 | |
| 52 | ```tsx |
| 53 | import { Deck, Slide, Stack, Markdown, Fragment, Code } from '@revealjs/react'; |
| 54 | import RevealHighlight from 'reveal.js/plugin/highlight'; |
| 55 | import 'reveal.js/plugin/highlight/monokai.css'; |
| 56 | |
| 57 | export function Presentation() { |
| 58 | return ( |
| 59 | <Deck plugins={[RevealHighlight]}> |
| 60 | <Slide> |
| 61 | <h2>Step by step</h2> |
| 62 | <Fragment animation="fade-up" as="p">First point</Fragment> |
| 63 | <Fragment animation="fade-up" asChild> |
| 64 | <div>Second point</div> |
| 65 | </Fragment> |
| 66 | <Code language="javascript" lineNumbers> |
| 67 | {`console.log('Hello, world!');`} |
| 68 | </Code> |
| 69 | </Slide> |
| 70 | |
| 71 | <Stack> |
| 72 | <Slide>Vertical 1</Slide> |
| 73 | <Slide>Vertical 2</Slide> |
| 74 | </Stack> |
| 75 | |
| 76 | <Markdown |
| 77 | separator="^\n---\n$" |
| 78 | verticalSeparator="^\n--\n$" |
| 79 | options={{ smartypants: true, animateLists: true }} |
| 80 | > |
| 81 | {` |
| 82 | ## Markdown 1.1 |
| 83 | - First item <!-- .element: class="fragment" --> |
| 84 | - Second item <!-- .element: class="fragment" --> |
| 85 | |
| 86 | -- |
| 87 | |
| 88 | ## Markdown 1.2 |
| 89 | |
| 90 | Notes: |
| 91 | These become speaker notes. |
| 92 | |
| 93 | --- |
| 94 | |
| 95 | <!-- .slide: data-background="#111827" --> |
| 96 | ## Markdown 2 |
| 97 | `} |
| 98 | </Markdown> |
| 99 | </Deck> |
| 100 | ); |
| 101 | } |
| 102 | ``` |
| 103 | |
| 104 | `Markdown` accepts string children, a `markdown` prop, or `src` for external markdown. Use the first-class `separator`, `verticalSeparator`, `notesSeparator`, `elementAttributesSeparator`, and `slideAttributesSeparator` props to mirror Reveal's markdown options, and pass any other markdown/Marked settings through `options`. |
| 105 | |
| 106 | ## Configure Reveal |
| 107 | |
| 108 | Pass any Reveal configuration through the `config` prop on `Deck`. Plugins are registered separately via `plugins` and are applied once at initialization time, matching Reveal's plugin lifecycle. |
| 109 | |
| 110 | ```tsx |
| 111 | import { Deck, Slide } from '@revealjs/react'; |
| 112 | import 'reveal.js/reveal.css'; |
| 113 | import 'reveal.js/theme/black.css'; |
| 114 | import 'reveal.js/plugin/highlight/monokai.css'; |
| 115 | import RevealHighlight from 'reveal.js/plugin/highlight'; |
| 116 | |
| 117 | export function Presentation() { |
| 118 | return ( |
| 119 | <Deck |
| 120 | config={{ |
| 121 | width: 1280, |
| 122 | height: 720, |
| 123 | hash: true, |
| 124 | controls: true, |
| 125 | progress: true, |
| 126 | transition: 'slide', |
| 127 | }} |
| 128 | plugins={[RevealHighlight]} |
| 129 | > |
| 130 | <Slide>Configured deck</Slide> |
| 131 | </Deck> |
| 132 | ); |
| 133 | } |
| 134 | ``` |
| 135 | |
| 136 | `config` maps directly to [Reveal's configuration object](https://revealjs.com/config/). `Slide` and `Markdown` both support convenient Reveal slide props such as `background`, `backgroundImage`, `backgroundColor`, `visibility`, `autoAnimate`, `transition`, `transitionSpeed`, `autoSlide`, `notes`, `backgroundInteractive`, and `preload`, while still passing through raw `data-*` attributes to the rendered `<section>` element. |
| 137 | |
| 138 | ## Subscribe to events |
| 139 | |
| 140 | Use event props on `Deck` to respond to Reveal lifecycle and navigation events: |
| 141 | |
| 142 | ```tsx |
| 143 | import { Deck, Slide } from '@revealjs/react'; |
| 144 | |
| 145 | export function Presentation() { |
| 146 | return ( |
| 147 | <Deck |
| 148 | onReady={(deck) => { |
| 149 | console.log('Reveal ready', deck); |
| 150 | }} |
| 151 | onSync={() => { |
| 152 | console.log('Deck synced'); |
| 153 | }} |
| 154 | onSlideChange={(event) => { |
| 155 | console.log('Slide changed', event.indexh, event.indexv); |
| 156 | }} |
| 157 | onFragmentShown={(event) => { |
| 158 | console.log('Fragment shown', event.fragment); |
| 159 | }} |
| 160 | > |
| 161 | <Slide>Intro</Slide> |
| 162 | <Slide>Next</Slide> |
| 163 | </Deck> |
| 164 | ); |
| 165 | } |
| 166 | ``` |
| 167 | |
| 168 | ## Access the Reveal API |
| 169 | |
| 170 | Use `useReveal()` inside the deck tree to call the Reveal API from your own components: |
| 171 | |
| 172 | ```tsx |
| 173 | import { Deck, Slide, useReveal } from '@revealjs/react'; |
| 174 | |
| 175 | function NextButton() { |
| 176 | const deck = useReveal(); |
| 177 | |
| 178 | return <button onClick={() => deck?.next()}>Next slide</button>; |
| 179 | } |
| 180 | |
| 181 | export function Presentation() { |
| 182 | return ( |
| 183 | <Deck> |
| 184 | <Slide> |
| 185 | <h2>Controlled from React</h2> |
| 186 | <NextButton /> |
| 187 | </Slide> |
| 188 | </Deck> |
| 189 | ); |
| 190 | } |
| 191 | ``` |
| 192 | |
| 193 | To access the Reveal instance outside of the component tree, pass a `deckRef` to `Deck`: |
| 194 | |
| 195 | ```tsx |
| 196 | import { useRef } from 'react'; |
| 197 | import { Deck, Slide } from '@revealjs/react'; |
| 198 | import type { RevealApi } from 'reveal.js'; |
| 199 | |
| 200 | export function Presentation() { |
| 201 | const deckRef = useRef<RevealApi | null>(null); |
| 202 | |
| 203 | return ( |
| 204 | <Deck deckRef={deckRef}> |
| 205 | <Slide>Hello</Slide> |
| 206 | </Deck> |
| 207 | ); |
| 208 | } |
| 209 | ``` |
| 210 | |
| 211 | ## How it works |
| 212 | |
| 213 | - `Deck` creates one Reveal instance on mount and destroys it on unmount. Initialization is asynchronous — `onReady` fires once `reveal.initialize()` resolves, after which the instance is also accessible via `useReveal()` and `deckRef`. |
| 214 | - `Deck` calls `reveal.sync()` when the rendered slide structure changes, such as slides being added, removed, reordered, or regrouped into stacks. |
| 215 | - `Slide` handles slide-level `data-*` attribute updates locally with `reveal.syncSlide()`, so ordinary React content updates inside a slide do not trigger a full deck sync. |
| 216 | - `Markdown` uses `marked` plus the same separator and comment-attribute conventions as Reveal's core markdown plugin, including `.slide:` and `.element:` comment syntax. |
| 217 | - `config` is shallow-compared on each render so that `reveal.configure()` is only called when a value actually changes. |
| 218 | - `plugins` are initialization-only, matching Reveal's plugin lifecycle. The prop is captured once on first mount and ignored on subsequent renders. |
| 219 | - Event props are wired with `deck.on()` after initialization and cleaned up with `deck.off()`. Changing a callback between renders swaps the listener automatically. |
| 220 | |
| 221 | --- |
| 222 | |
| 223 | <div align="center"> |
| 224 | MIT licensed | Copyright © 2011-2026 Hakim El Hattab, https://hakim.se |
| 225 | </div> |
| 226 |