Tutorial Docusaurus React Live

1. Why I added a playground
I wanted readers to learn faster by allowing them to modify examples inline. The @docusaurus/theme-live-codeblock theme integrates react-live to evaluate JSX in real time.
Benefits I gained:
- Instant feedback for readers
- Pedagogical demos without an external sandbox
- Visual consistency with the site
- No page context switching
2. Install the live theme
npm install @docusaurus/theme-live-codeblock --save
Ensure all @docusaurus/* versions are aligned (e.g. 3.9.1).
3. Configure docusaurus.config.js
Add (or extend) the themes key:
export default {
// ...other options
themes: [
'@docusaurus/theme-live-codeblock'
],
};
Optional: adjust styling with custom CSS (Infima is bundled).
4. Create your first jsx live blocks
A MDX fenced block becomes interactive by adding live after the language:
```jsx live
() => <button style={{padding:8}}>Click me</button>
```
You can also write a function:
```jsx live
function Counter(){
const [n,setN] = React.useState(0);
return <button onClick={()=>setN(n+1)}>Count: {n}</button>;
}
<Counter />
```
5. Effective patterns
| Pattern | Use case | Benefit | Example |
|---|---|---|---|
| Final expression | Tiny demo | Ultra concise | (<Badge>Hi</Badge>) |
| Const + final identifier | Readable snippet | Shows value | const el=<div/>; el |
| Function + call | Internal logic | Reusable | function Demo(){...}; <Demo /> |
| Inline hooks | Small interactions | Direct | const [x,setX]=useState(0); <button onClick={()=>setX(x+1)}>{x} |
| Export default (avoid) | Legacy habit | Noise here | (do not use) |
Key rule: the last expression of the block is rendered (no export).
6. Swizzle ReactLiveScope
Problem: inside a live block, your custom components (Tooltip, LogoIcon, etc.) are not known by default.
Solution: create src/theme/ReactLiveScope/index.js to inject a global scope.
import * as React from "react";
// @ts-ignore
import Tooltip from "@site/src/components/Tooltip";
import LogoIcon from "@site/src/components/LogoIcon";
import Skill from "@site/src/components/Skill";
import Contributor from "@site/src/components/Contributor";
import Columns from "@site/src/components/Columns";
import TimeTimer, {
FallbackBefore,
FallbackAfter,
} from "@site/src/components/TimeTimer";
import Column from "@site/src/components/Column";
import Card from "@site/src/components/Card";
import CardBody from "@site/src/components/Card/CardBody";
import CardFooter from "@site/src/components/Card/CardFooter";
import CardHeader from "@site/src/components/Card/CardHeader";
import CardImage from "@site/src/components/Card/CardImage";
// Export only the symbols the react-live runtime needs to evaluate snippets.
const {
useState,
useEffect,
useRef,
useMemo,
useCallback,
Fragment,
forwardRef,
createElement,
Children,
} = React;
export default {
// Keep the complete React object accessible if needed (without spread to avoid 'default' key)
React,
// Frequently used hooks in snippets
useState,
useEffect,
useRef,
useMemo,
useCallback,
Fragment,
forwardRef,
createElement,
Children,
// Custom components exposed in live blocks
Tooltip,
LogoIcon,
Skill,
Contributor,
TimeTimer,
FallbackBefore,
FallbackAfter,
Columns,
Column,
Card,
CardBody,
CardFooter,
CardHeader,
CardImage,
// HTML elements
center: "center",
};
From there on:
<Tooltip model="teacher" text="DocuxLab"> Swizzle ready! </Tooltip>
7. Advanced examples: interactive Tooltip
Simple model
<Tooltip text="Simple tooltip">Short content.</Tooltip>
Two tooltips side by side
<div style={{display:'flex', gap:16}}> <Tooltip model="teacher" text="Teacher"> Example with model. </Tooltip> <Tooltip model="suricate" text="Pépé" position="bottom"> Bottom position. </Tooltip> </div>
Skill (progress bar)
<div style={{display:'flex', flexDirection:'column', gap:8}}> <Skill name={<><LogoIcon name="react" size='24' /> React</>} value={88} type="bar" color="#61DAFB" height={25} /> <Skill name={<><LogoIcon name="vue" size='24' /> Vue.js</>} value={75} type="bar" color="#4FC08D" height={25} /> <Skill name={<><LogoIcon name="nodejs" size='24' /> Node.js</>} value={82} type="bar" color="#339933" height={25} /> <Skill name={<><LogoIcon name="python" size='24' /> Python</>} value={78} type="bar" color="#3776AB" height={25} /> </div>
Skill (animated circle)
<div style={{display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: '20px', marginTop: '20px'}}> <Skill name={<><LogoIcon name="html-5" size='24' /> </>} value={95} type="circle" color="#E34F26" valuePosition="center" size={100} /> <Skill name={<><LogoIcon name="css-3" size='24' /> </>} value={90} type="circle" color="#1572B6" valuePosition="center" size={100} /> <Skill name={<><LogoIcon name="javascript" size='24' /> </>} value={85} type="circle" color="#F7DF1E" valuePosition="center" size={100} /> </div>
Contributor (card)
<Contributor name="Docux" github="Juniors017" website="https://github.com/Juniors017" avatarUrl="/img/docux.webp" components={['Tooltip','Skill','etc...']} description="Author of several UI components for this site." />
LogoIcon (variants)
<div style={{display:'flex',gap:24,alignItems:'center'}}> <LogoIcon name="docusaurus" size={40} /> <LogoIcon name="javascript" size={40} /> <LogoIcon name="css-3" size={40} /> </div>
8. Best practices
- Keep snippets short and progressive.
- Do not overload the scope (clarity + performance).
- Watch for CSS collisions (avoid heavy globals).
- Prefer an explicit final expression for readability.
- Update
@docusaurus/*dependencies together.
9. Quick debugging
| Issue | Likely cause | Fix |
|---|---|---|
X is not defined | Component not in scope | Add to ReactLiveScope |
| Nothing displayed | No final expression | End with JSX or identifier |
| React import crash | Unnecessary import | Remove import React |
| Broken style | Infima/custom conflict | Wrap / isolate |
| Versions mismatch | Misaligned packages | Align versions |
10. Going further
Ideas:
- Add a light/dark theme toggle inside demos.
- Persist state (localStorage) in a live block.
- Generate dynamic component variations.
This article is part of the Design your site series:
Loading comments…
No related posts.