A self-hostable WYSIWYG presentation editor powered by reveal.js. Build, present, and broadcast slides in the browser — no account, no cloud, no tracking. Also available as a standalone desktop app via Electron.
The current published release is v1.16.2. Published history also retains
v1.16.0 and v1.16.1. The next release candidate is the untagged
v1.17.0 product version; package manifests own that candidate version, while
runtime-versions.json separately owns runtime and toolchain pins.
Why NavSlides · Quick start · Visual tour · Documentation · Maintenance · Features · Contributing
Most presentation tools trade away privacy, technical authoring, or deployment control. NavSlides provides an open browser-based alternative for people who need all three:
- Local ownership — presentations stay on the operator's filesystem; the default deployment requires no NavSlides account, hosted cloud, or telemetry.
- Technical authoring — code, Markdown, LaTeX/TikZ, charts, diagrams, HTML, media, and interactive elements live beside ordinary slide content.
- Portable delivery — decks can run in the browser, broadcast to live viewers, or move through HTML, offline HTML, PDF, PPTX, and project archives.
- Deployment choice — the same project runs through Docker, Node.js, or an Electron desktop package.
The primary users are academics and researchers, educators and students, developer speakers, and privacy-conscious operators. The scope is deliberate: NavSlides is a single-user, self-hosted editor rather than a hosted SaaS or real-time collaborative document service. One application process is supported; horizontal workers, cluster mode, and tenant isolation are not. Internet-facing deployments therefore need the external authentication boundary described in the Security Model and deployment guide.
Requires Docker 20.10+ and Docker Compose v2+.
git clone https://github.com/xuan2261/NavSlidesEditor.git && cd NavSlidesEditor
docker compose up -dOpen http://127.0.0.1:3002 by default. Docker listens on 0.0.0.0 inside
the container but publishes to host loopback unless NAVSLIDES_PUBLISH_HOST
is set. Use docker compose logs -f to inspect the service, or
docker compose up -d --build after pulling updates. Internet-facing
deployments require an external authentication layer and reverse proxy.
The image runs as fixed non-root UID/GID 10001:10001; Compose reports healthy
only after GET /health/ready confirms storage, package ownership, and recovery.
Earlier tagged releases, including v1.16.2, may offer Windows packages on Releases. The untagged v1.17.0 candidate does not authorize a publicly distributed Windows EXE or Windows Authenticode signing. Windows remains a private qualification host for unpacked runtime closure and exact-source physical OfficeCLI G1 evidence. The selected native edited-PPTX gates are G0/G1/G2/G4, not Windows executable G3 or PowerPoint G5. Importer-corpus and diagnostic local COM screenshots do not qualify PowerPoint fidelity. The proposed release deliverable is the exact prebuilt Docker image with subject-bound receipts; see the release contract for pending gates and accepted container vulnerability risk.
Linux, macOS, and Windows desktop packages can still be built locally with Node.js >=22.22.2, >=24.15.0, or >=26; local build commands are not release distribution promises:
git clone https://github.com/xuan2261/NavSlidesEditor.git && cd NavSlidesEditor && npm install
npm run electron:build:linux # .AppImage + .deb
npm run electron:build:mac # .zip
npm run electron:build:win # .exe, local only
npm run electron:dev # development modeDesktop data is stored under ~/.config/NavSlides Editor/ on Linux,
~/Library/Application Support/NavSlides Editor/ on macOS, and
%APPDATA%/NavSlides Editor/ on Windows.
Requires Node.js >=22.22.2, >=24.15.0, or >=26 and npm. CI/container use Node.js 22.23.3; the container runs Debian Trixie Slim and omits npm from its final runtime.
git clone https://github.com/xuan2261/NavSlidesEditor.git && cd NavSlidesEditor && npm install
npm run devDevelopment uses Vite on http://localhost:5173 and the Express API on port
3002. For a production-style local run:
npm run build
npm start- Open the dashboard and select New Presentation.
- Start with a blank deck or choose a template.
- Click a text element to edit it; use the Home and contextual Format tabs for styling.
- Add slides or content from the Insert tab. Changes auto-save after a short delay.
- Select Present, Share, or File → Export when the deck is ready.
| Resource | English | Tiếng Việt |
|---|---|---|
| Get started | Getting Started | Bắt đầu |
| Installation | Installation Guide | Hướng dẫn cài đặt |
| First deck | First Presentation Tutorial | Bài trình chiếu đầu tiên |
| Shortcuts | Keyboard Shortcuts | Phím tắt |
Maintainer and AI navigation: project intent, architecture, code standards, deployment, export/import limits, and current roadmap / changelog.
NavSlides is a public AGPL-3.0 project maintained by @xuan2261, the repository owner and package author. Releases, verification, architectural decisions, and known limits are kept inspectable in the repository rather than represented by private service state.
| Maintenance signal | Evidence |
|---|---|
| Public source and copyleft license | Repository · LICENSE |
| Tagged release history and candidate contract | GitHub Releases · release policy · release guide |
| Continuous verification | CI workflow · testing guide |
| Current architecture and trust boundaries | System architecture · Security Model · deployment guide |
| Planning and change history | Roadmap · changelog |
Maintenance spans the React editor, Express and Socket.IO services, shared rendering/export code, the Electron shell, and the documentation site. Automation has the highest leverage in:
- pull-request review and cross-runtime impact analysis;
- issue triage, reproduction, and regression-test design;
- security review of uploads, imports, exports, live capabilities, and dependency changes;
- CI failure analysis, release preparation, and documentation-drift checks.
- WYSIWYG editing — click and type directly on slides with TipTap rich text
- Tab-based ribbon UI — Home / Insert / Design / Transitions / Animations / View tabs replace the old toolbar/menu system; active tab persists across sessions (
Ctrl+Alt+Rtoggles). Primary actions (Paste, Text Box, Picture) use large icon-over-label buttons, while constrained widths place lower-frequency groups in an explicit More menu - Contextual Format tab — appears only when an element is selected and relabels itself to the selection type (Shape Format / Picture Format / Table Design / Chart Design / Code / Media); auto-activates on the first selection and hides again when the selection clears
- PowerPoint-style status bar — zoom slider with −/+/Fit controls, current slide position (Slide X / Y), and a Normal / Slide Sorter / Present view switcher; status bar, ribbon, canvas controls, keyboard shortcuts, and command palette share one zoom state
- Adaptive editor workspace — the canvas remains primary across compact, standard, and wide tiers; the slide navigator docks from 1024 px, and Properties / Design Ideas share one right inspector that docks on wide screens and opens as an overlay at narrower widths
- Accessible slide navigator — slide thumbnails expose list semantics, stable selection, keyboard focus, and named actions for reordering and vertical slides
- Rich formatting — headings, bold/italic/underline/strikethrough, text color, highlight, font family, font size, font weight, line height, alignment, lists, tables, code blocks, links, images, inline math
- Multi-select — shift-click to select multiple elements, move or delete them together
- Group / ungroup — group multiple elements so they select, move, and resize as a unit (
Ctrl+G/Ctrl+Shift+G) - Align & distribute — align selected elements left/center/right/top/middle/bottom, or distribute evenly
- Element rotation — rotate any element by dragging the rotation handle or entering a degree value (
Shiftsnaps to 15°) - Smart guides & snapping — alignment lines appear when dragging near other elements' edges or the canvas center; toggle with the magnet icon
- Rulers & guides — toggle pixel rulers on the top/left edges; drag from a ruler onto the canvas to place persistent guide lines; double-click a guide to remove it
- Element controls — resize, reposition, lock, z-order, drop shadow, aspect-ratio lock (
Shiftwhile resizing) - Round corners — adjustable border radius on images and code blocks
- Find & replace —
Ctrl+Fto search text across all slides with case-sensitive matching, navigate matches, replace one or all - Undo / redo —
Ctrl+Z/Ctrl+Ywith 50-step bounded history - Clipboard —
Ctrl+C/X/VandCtrl+Dto copy/cut/paste/duplicate elements - Auto-save — debounced saves every 1.5 s with last-saved timestamp;
Ctrl+S, the quick-access control, File menu, and command palette dispatch the same immediate save command, while visible transient failures retain a separateRetryaction - Command palette —
Ctrl+Kfor quick command lookup - Pointer and touch editing — mouse, pen, and touch share Pointer Events for selection, drag, resize, rotate, crop, rulers, and guides; 2-finger pinch zoom remains available on tablets and trackpads
- Translucent presenter UI — floating tools and slide navigation dim to 15% opacity when idle
- Interactive onboarding — step-by-step product tour via React-Joyride
- Copy URL — right-click images/videos to copy their resolved media URL
19 element types: text (TipTap rich text), image (upload/URL, crop, filters, round corners), shape (rectangle, circle, triangle, arrow, star), code (10 themes, 25+ languages), LaTeX / TikZ (KaTeX + TikZJax), HTML embeds, Markdown, Chart.js charts (bar, line, pie, doughnut, radar, polar area), video / audio (with start/end trim, playback speed), table (drag-resize, inline editing), QR code, icon (60+ Lucide icons), callout, drawing, line, SVG, timeline, and game (10 interactive game types). The Insert ribbon shows 30+ actions because shapes, technical symbol packs, and games (10 variants) expose sub-variants from existing element types. Action/Hotspot is validated base-element metadata; Smart Connector extends line with same-slide endpoint attachments, not a new type. The 19 canonical types are listed in client/src/data/element-defaults.js.
Image alternatives (including decorative and long-description metadata) and video/audio captions, transcripts, audio descriptions, and safe media tracks are authorable browser semantics. Blank non-decorative alternatives and malformed media metadata warn rather than changing legacy decks; transcript text is user-authored, not generated.
35 layouts across 6 categories (basic, content, layout, data, structure, ending) + 20+ full-deck templates including interactive simulations and quiz decks. Reusable layout masters resolve fixed elements, bound placeholders, and slide-owned content into one effective slide; built-in templates remain materialized insertion presets, not masters. Per-slide backgrounds (solid, gradient, image, animated FX), first-class vertical (child) slides — create, select, edit, and export nested slides from the slide panel — fragment animations with visual timeline editor and preview modal, per-slide page numbers, hidden slides, footer system (basic / sequence modes), and global presentation settings (auto-slide, loop, navigation modes).
Broadcast to viewers via Socket.IO with server-issued capabilities. Includes a separate speaker view (notes, next-slide preview, timer), remote control from a phone or second device, annotation tools (pen, laser pointer, highlighter, eraser) that sync to viewers in real time and persist per slide on rejoin, black/white screen overlays (B / W), shared live timer, and PowerPoint-style navigation (F5, Home, End, arrows). Viewer links use /live/:roomCode; privileged remote/speaker links carry their capability in the URL fragment. Capability-bearing REST calls use an Authorization: Bearer` header; URL fragments are never sent in HTTP requests. For multi-user or internet-facing deployments, place them behind the external authentication layer described in the security model below.
10 interactive game element types with a dedicated player join page (/player/:slideId/:elementId), game-specific socket handler, leaderboard, scoring, and presenter shortcuts (HUD, timer, reveal, leaderboard, pause, team select).
The generic POST /api/games endpoint is intentionally an unauthenticated local bootstrap boundary; host and player joins or mutations still require server-issued capability and session checks, and multi-user or internet-facing deployments require external authentication.
AI copywriter (rewrite slide text), AI generator (full presentation drafts from a prompt), AI translate (translate slide content), and a media library with Unsplash and Giphy search.
11 reveal.js base themes (black, white, league, beige, sky, night, serif, simple, solarized, moon, dracula), 6 transitions (none, fade, slide, convex, concave, zoom), 39 token-based design presets across 7 categories (minimal, editorial, developer, corporate, creative, earthy, bold) surfaced in the Design ribbon ThemeGallery with live-switch and "Apply to all", 8 animated canvas FX backgrounds (gradient-blob, starfield, matrix-rain, constellation, particle-burst, knowledge-graph, orbit-ring, sparkle-trail) for type: 'fx' slide backgrounds, Design Ideas panel (heuristic layout + theme suggestions, no AI), custom user templates, and a dark/light editor theme toggle.
Present mode (Reveal.js 6.0.1, press S for speaker notes), export HTML (the canonical manifest-owned /vendor/reveal.js/dist runtime), export offline HTML (the same runtime assets inlined), export PDF (one page per slide with expanded fragments), export PPTX (hybrid: editable primitives + Playwright-rasterized fallback for unsupported elements), shareable links with optional password, GitHub push with auto-generated README, Markdown import, project export/import (.navslides archive with manifest v1.1). Browser actions remain browser-only and are omitted from PPTX with a warning; connectors export as resolved native lines with an attachment-semantics warning; layout masters flatten to resolved objects without native PowerPoint masters. Image alt text is native where supported, while decorative/long-description/media-track/transcript semantics have explicit PPTX warnings or fallbacks rather than unsupported preservation claims.
Dashboard imports wait for a shared import slot separately from the admitted job's bounded wait. ZIP structure, entry count, declared decompressed size, streamed decompressed-byte budget, and per-entry CRC32 are validated before package mapping. If the final outcome cannot be confirmed, the editor asks you to check existing presentations before retrying rather than making destructive recovery. A completed package job can remain pending visibility until it is safe to open. Imported external media is blocked unless a server administrator explicitly allows its origin; EMF/WMF conversion remains off until that administrator configures its guarded policy.
The import is parser-backed application behavior, not a native PowerPoint/OfficeCLI or pixel-perfect fidelity claim. See PPTX import lifecycle and evidence and deployment policy.
rclone-based sync to Proton Drive or any rclone-supported provider (Google Drive, S3, etc.). Configure credentials in-app; sync a single presentation or all at once. Docker image ships with rclone preinstalled.
Named snapshots saved per presentation, restore any previous version, delete individual snapshots.
All data lives in server/data/ (presentations, templates, share tokens, GitHub config, settings, analytics, media metadata, history snapshots, rclone config) and server/uploads/ (media). Docker uses named volumes revealjs-data and revealjs-uploads. All locations are created automatically on first run.
Back up both volumes at one stopped consistency point with
npm run backup:docker; restore only into empty volumes with
npm run restore:docker -- -BackupDirectory <path>. The manifest verifies both
archives before extraction. See the deployment guide.
NavSlides Editor is designed as a single-user, self-hosted presentation tool where rich programmable content is a core feature. HTML embeds, custom CSS, inline SVG, Markdown-generated HTML, and exported reveal.js HTML are treated as trusted author content.
Code reviews and security scans should not flag trusted author-controlled HTML/CSS/JS execution as a blocking XSS issue by itself. This behavior is intentional because the editor must support interactive embeds, simulations, diagrams, and custom presentation styling.
Still review issues that cross a trust boundary, including:
- untrusted uploads or imported files executing outside the author's intent
- Uploaded SVG content is sanitized on upload and again at the serving boundary, including legacy files, then served with sandbox CSP,
nosniff, and same-origin resource policy headers. - public share links exposing admin/editor capabilities
- stored content from one user/session affecting another user
- credential leakage, path traversal, SSRF, command injection, or data loss
- missing auth protections when deploying beyond local/private single-user use
NavSlides has no built-in authentication and no multi-tenant isolation. For
internet-facing use, place the whole editor/API behind an external authentication
layer and treat all content as privileged. /api/analytics/:id is an
owner/editor route: share tokens do not authorize it, and its response exposes
only aggregate link labels plus timestamp/referrer-host events. If a proxy makes
/share/:token public, keep /api/analytics, presentation APIs, and editor
routes behind operator authentication.
- Create a fine-grained PAT with repository contents read/write.
- Click GitHub in the editor, enter owner, repo name, and token → Save Settings.
- Click Push to GitHub (optionally with a commit message).
Output structure:
my-repo/
├── README.md ← auto-generated
├── my_first_talk/
│ ├── presentation.html ← viewable in browser
│ └── presentation.json ← full project data
└── another_presentation/
Sync via in-app Sync button. Configure Proton Drive (or any rclone provider) credentials, then use Sync This Presentation or Sync All. Docker includes rclone; for the desktop app, install rclone separately.
Legacy presentations are exported as HTML + JSON and uploaded via rclone. Package-backed presentations additionally include package/manifest.json and verified content-addressed PPTX blobs, so the native package can be recovered from the sync output. Each request uses an isolated staging directory and syncs to a serialized destination. The Docker image includes rclone pre-installed. For the desktop app, install rclone separately on your system.
Nginx:
server {
listen 443 ssl;
server_name slides.example.com;
ssl_certificate /etc/letsencrypt/live/slides.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/slides.example.com/privkey.pem;
client_max_body_size 100M;
location / {
proxy_pass http://localhost:3002;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Caddy:
slides.example.com {
reverse_proxy localhost:3002
}
| Shortcut | Action |
|---|---|
Ctrl+Z |
Undo |
Ctrl+Y / Ctrl+Shift+Z |
Redo |
Ctrl+C / Ctrl+X / Ctrl+V |
Copy / cut / paste element |
Ctrl+D |
Duplicate element |
Ctrl+F |
Find & replace |
Ctrl+K |
Command palette |
Ctrl+S |
Save now |
Ctrl+M |
Insert new slide |
Ctrl+G / Ctrl+Shift+G |
Group / ungroup elements |
Ctrl+] / Ctrl+[ |
Bring forward / send backward |
Ctrl+0 / Ctrl++ / Ctrl+- |
Zoom fit / zoom in / zoom out |
Tab / Shift+Tab |
Cycle through elements |
Delete / Backspace |
Delete selected element(s) |
Escape |
Deselect / stop editing / close panel |
Shift+drag |
Maintain aspect ratio while resizing |
Shift+rotate |
Snap rotation to 15-degree increments |
Ctrl+Alt+R |
Toggle ribbon panel |
| Shortcut | Action |
|---|---|
F5 / Shift+F5 |
Start presentation / from current slide |
Arrow Left / Arrow Right |
Previous / next slide |
Home / End |
First / last slide |
B / W |
Black / white screen overlay |
Escape |
End presentation |
S |
Open speaker notes view |
Ctrl+P |
Pen annotation |
Ctrl+I |
Highlighter |
Y |
Laser pointer |
E |
Eraser |
Only shortcuts implemented by the active game are enabled.
| Shortcut | Action |
|---|---|
G |
Toggle HUD |
Space |
Start timer |
Enter |
Next question or phase |
R |
Reveal supported game results |
L |
Show leaderboard |
P |
Pause timer |
+ / - |
Adjust timer |
| Method | Requirement |
|---|---|
| Desktop app | Node.js >=22.22.2, >=24.15.0, or >=26 (build only) |
| Docker | Docker 20.10+ and Docker Compose v2+ |
| Node.js | Node.js >=22.22.2, >=24.15.0, or >=26 and npm |
| Load Testing | k6 |
Verification typically runs in this order:
-
Lint and build:
npm run lint npm run build
-
Unit tests:
npm run test -
Browser tests:
npm run test:e2e
-
Parser-relative PPTX corpus metrics:
npm run test:pptx:corpus-metrics # `npm run test:corpus` is the compatibility alias npm run test:pptx:best-effort # non-importer-strict metrics plus strict browser smoke
This best-effort regression lane measures semantic fidelity and production round-trip stability. It does not qualify native importer coverage.
-
Manifest-bound PPTX importer qualification:
npm run test:pptx:importer-qualification npm run test:pptx:strict # deprecated alias for importer qualificationThis fail-closed two-pass gate verifies the checked-in 11-deck manifest and every source SHA-256, then uses one hash-checked temporary snapshot for best-effort native evidence and
{ strict: true }. The current report passes all 11 decks with zero blockers, zero unmapped scene-graph nodes, and zero permanent placeholders; this qualifies the importer corpus only, not OfficeCLI or Microsoft PowerPoint fidelity claims. Future known EMF/native-node blockers still fail closed rather than producing a false release pass. -
PPTX real-browser layout audit:
npm run test:pptx:browser-audit # strict smoke subset for PR/runtime-sensitive checks npm run test:pptx:browser-audit:full # strict full 5-deck release gate npm run test:pptx:browser-audit:headed # headed full audit for manual inspection
-
Microsoft PowerPoint visual oracle:
npm run test:pptx:oracle:capture -- --base-url http://127.0.0.1:3202 --corpus-manifest server/data/test-corpus/importer-qualification-manifest.json --actuals-dir <actuals-dir> npm run test:pptx:oracle:integrity -- --evidence-manifest <manifest> --role-receipts <receipts> --goldens-dir <goldens-dir> --actuals-dir <actuals-dir> npm run test:pptx:oracle:qualify -- --evidence-manifest <manifest> --role-receipts <receipts> --goldens-dir <goldens-dir> --actuals-dir <actuals-dir>
Only Microsoft PowerPoint goldens can satisfy this gate. Integrity success does not imply fidelity success; qualification enforces the fixed
phase08_fullmean/minimum SSIM policy and fails closed. -
Load tests with
k6:npm run test:load:api npm run test:load:ws
Install k6 from the official guide if you want to run the load suite locally.
PPTX browser audit artifacts are written under plans/reports/pptx-import-real-browser-audit-runs/, which is ignored by git because screenshots may contain slide content.
- Use
testPresentationfromtests/e2e/fixtures/test-fixtures.jsfor presentation create/cleanup. - Prefer
data-testidselectors for editor controls, canvas handles, and repeated UI; keep page objects intests/e2e/pages/using kebab-case filenames. - Use state-based waits:
expect.poll, locator assertions with timeouts, andwaitForResponse. Do not addwaitForTimeout. - Reuse helper modules such as
tests/e2e/pages/wait-helpers.jsinstead of duplicating timing logic.
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite 8, React Router 7 |
| State management | Zustand (3 stores: editor, presentation, UI) |
| Rich text editor | TipTap 2 |
| Presentation engine | reveal.js 6.0.1 (canonical manifest-owned /vendor/reveal.js/dist assets) |
| Math rendering | KaTeX |
| Diagrams | TikZJax |
| Charts | Chart.js 4 |
| Syntax highlighting | highlight.js |
| Markdown | Built-in converter + marked.js (export) |
| Icons | Lucide (editor UI) + inline SVG (slide icons) |
| PowerPoint export | pptxgenjs + Playwright raster fallback |
| PowerPoint import | pptxtojson runtime parser; pptx2json benchmark-sandbox-only |
| Backend | Node.js >=22.22.2, >=24.15.0, or >=26 (CI/container: 22.23.3), Express 4 |
| Real-time transport | Socket.IO |
| Desktop app | Electron 42 |
| Cloud sync | rclone |
| Validation | Zod (mutation endpoints) |
| Testing | Vitest, Playwright, k6 |
| Linting & Formatting | ESLint 9 (flat config), Prettier |
| Storage | JSON files + local filesystem |
Contributions are welcome through issues and pull requests. For a large API, persistence, security, or format-compatibility change, open an issue first so the contract and migration risk are explicit before implementation.
- Fork the repository and branch from
master. - Install dependencies with
npm install. - Read the relevant code standards and system architecture, then change the executable owner instead of duplicating behavior in documentation.
- Run the focused checks for the changed surface and the applicable baseline
from Testing & Performance.
package.jsonowns the exact commands. - Open a focused pull request that explains the problem, decision, behavioral and security impact, and verification evidence. Include before/after images for visible UI changes and call out persistence or compatibility effects.
Maintainer triage and release ownership currently sit with @xuan2261.
NavSlides Editor is licensed under the GNU Affero General Public License v3.0. See LICENSE.



