JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 50
  • Score
    100M100P100Q81420F
  • License MIT

Low-level JS library for generating beautiful .pptx files — pptxgenjs-compatible API with designer-grade primitives (global theme, parallel raster pipeline, LRU cache, addTable/Quote/Divider/Avatar, Lucide icons by name, Prism syntax highlighting, blobs, mesh gradients, glass-morphism, grain, gradient text, isometric grids, editorial charts, dot grids, progress rings, sparklines, badges, timelines) + live SVG preview.

Package Exports

  • slidewave
  • slidewave/package.json

Readme

Slidewave

Low-level JS library for generating beautiful .pptx files. pptxgenjs-compatible API, but with designer-grade primitives that LLMs can't uniformize.

npm version license

What is Slidewave?

Slidewave is a JavaScript library that generates .pptx files by code — openable in PowerPoint, Keynote and Google Slides. It's a thin layer on top of pptxgenjs: same low-level API (inches, addText, addShape…) but with extra designer-grade primitives that are impossible to click in PowerPoint.

Why

  1. Programmatic generation — 100 slides in 1 second, dynamic templates, real-time data.
  2. Modern visuals — mesh gradients, organic blobs, glassmorphism, editorial charts, syntax-highlighted code, Lucide icons.
  3. Still editable — text, shapes and charts remain native PPT objects, modifiable inside PowerPoint.
  4. Zero lock-in — standard .pptx output. Works everywhere.
  5. Live SVG preview — see the result in-browser without opening PowerPoint.
  6. LLM-friendly — Claude / GPT / Gemini emit editorial-grade slides instead of the usual rectangles.

Feature catalogue (v0.3)

Primitive Mode What it does
addText native Editable text (fontFace, charSpacing, shadow)
addRect, addShape, addLine native Standard shapes + radius + border + shadow
addImage native URL / base64 / dataURL
addChart native Editorial-styled bar/line/pie/area/doughnut
addBadge native Pill (rounded rect + centered text)
addProgressBar native Horizontal progress bar (+ % label)
addTimeline native Horizontal timeline with dot milestones
addConnector native Arrow between two points (auto-oriented head)
addBackground raster Linear / radial / mesh gradient fill
addGradientText raster Text with multi-stop gradient
addBlob raster Organic seeded blob shapes
addGlassCard raster Glassmorphism card (tint + border + highlight)
addGrain raster Cinema grain texture
addNoiseGradient raster Risograph-style noise gradient
addIsometricGrid raster Isometric grid (3 axes)
addDotGrid raster Notion/Linear-style dot grid background
addStripes raster Diagonal stripe pattern
addWaveDivider raster Sinusoidal section divider
addProgressRing raster Circular progress ring + center label
addSparkline raster Mini inline line chart (smoothed)
addCodeBlock raster Syntax-highlighted code (js/ts/py/json/sh)
addIcon raster SVG / Lucide / raw paths

native = XML PPT (editable) · raster = SVG→PNG (pixel-perfect visuals, await required)

Architecture

    Your code (recipe.js)
            │
            ▼
         ┌──────┐
         │ Pres │   ← layout, title, save()
         └──┬───┘
            │ addSlide()
            ▼
        ┌───────┐
        │ Slide │   ← fluent API + ops[]
        └──┬─┬──┘
           │ │
    ┌──────┘ └──────┐
    ▼               ▼
  NATIVE         RASTERISED
 (PPT XML)       (SVG → PNG)
  addText         addBlob
  addRect         addGradientText
  addChart        addGlassCard
  addShape        addCodeBlock …
    │               │
    └───────┬───────┘
            ▼
       ┌──────────┐
       │ pptxgenjs│   serialises → .zip → .pptx
       └────┬─────┘
            ▼
       hello.pptx

Each Slide method is also recorded in slide._ops so pres.renderAllSvg() can reproduce the slide as an SVG — that's the live preview, 100% offline, no PowerPoint needed.

Install

npm install slidewave pptxgenjs

pptxgenjs is a peer dependency so you control its version.

Browser-only. Slidewave uses Canvas, Image and Blob to rasterize SVG primitives. It does not run in Node without a DOM shim.

Quick start

import { Pres } from 'slidewave'

const pres = new Pres({ layout: 'LAYOUT_WIDE', title: 'Demo' })
const slide = pres.addSlide()

// Rich background (mesh / aurora)
await slide.addBackground({
  gradient: {
    type: 'mesh',
    base: '#0b0b0f',
    colors: ['#6366f1', '#8b5cf6', '#ec4899'],
    blur: 80,
  },
})

// Native editable text (stays editable in PowerPoint)
slide.addText('Hello world', {
  x: 1, y: 1, w: 11, h: 2,
  fontFace: 'Fraunces', fontSize: 140,
  color: '#ffffff', italic: true,
  charSpacing: -1,       // POINTS — tight spacing
  lineSpacingMultiple: 1.05,
})

// Gradient-filled text (rasterized, non-editable but visually impossible otherwise)
await slide.addGradientText('Redesigned.', {
  x: 1, y: 3.2, w: 11, h: 2,
  fontFamily: 'Fraunces, serif',
  fontSize: 180, fontWeight: 500,
  gradient: ['#ffffff', '#a5b4fc'],
  angle: 180,
})

// Organic blob
await slide.addBlob({
  x: 8.5, y: 0.5, w: 4.5, h: 4.5,
  seed: 7, points: 9, irregularity: 0.35,
  fillGradient: ['#ec4899', '#f59e0b'],
})

await pres.save('demo.pptx')

API

new Pres(options)

new Pres({
  layout?: 'LAYOUT_WIDE' | 'LAYOUT_16x9' | 'LAYOUT_16x10' | 'LAYOUT_4x3',
  title?: string, author?: string, company?: string, subject?: string,
})
Method Returns Description
addSlide(opts?) Slide Adds a new slide
size() { width, height } Slide dimensions in inches
save(filename) Promise<void> Triggers browser download
toBlob() Promise<Blob> Returns the raw .pptx blob
toArrayBuffer() Promise<ArrayBuffer> Returns raw bytes
.ShapeType, .AlignH, .AlignV pptxgenjs constants

Slide — native primitives (editable in PowerPoint)

All coordinates are in inches. 16:9 wide = 13.333 × 7.5.

slide.addText(text, opts)

slide.addText('Title', {
  x: 1, y: 1, w: 10, h: 1.2,
  fontFace: 'Fraunces',
  fontSize: 72,
  color: '#0b0b0f',
  bold, italic, underline,
  align: 'left' | 'center' | 'right',
  valign: 'top' | 'middle' | 'bottom',
  charSpacing: 2,                  // POINTS — typical 2..6, negative = tighter
  lineSpacingMultiple: 1.1,
  shadow: { type: 'outer', blur: 20, offset: 4, angle: 90, color: '#000', opacity: 0.3 },
  glow:   { size: 8, opacity: 0.5, color: '#6366f1' },
  outline:{ size: 1, color: '#000' },
})

charSpacing is in POINTS. Typical values: 2 to 6. Negative tightens. Setting it to 300 will break your layout.

slide.addRect(opts)

slide.addRect({
  x: 1, y: 3, w: 5, h: 2,
  fill: '#818cf8',                 // or { type: 'solid', color }
  radius: 0.2,                     // inches
  borderColor: '#1e1b4b', borderWidth: 1,
  shadow: { type: 'outer', blur: 40, offset: 20, angle: 90, color: '#000', opacity: 0.15 },
  rotate: 0,
})

Other native methods

slide.addShape('ellipse' | 'triangle' | ..., opts)   // any pptxgenjs shape
slide.addLine({ x1, y1, x2, y2, color, width })
slide.addImage({ x, y, w, h, data, path })
slide.raw()                                          // underlying pptxgenjs slide

Slide — rich primitives (rasterized)

These are async because they render SVG → PNG before embedding.

slide.addBackground(opts)

await slide.addBackground({ color: '#fafaf7' })
await slide.addBackground({
  gradient: {
    type: 'mesh' | 'linear' | 'radial',
    colors: ['#6366f1', '#8b5cf6', '#ec4899'],
    base: '#0b0b0f',   // mesh only — backdrop
    angle: 135,        // linear only
    blur: 80,          // mesh only
  },
})

slide.addGradientText(text, opts)

await slide.addGradientText('Hello', {
  x, y, w, h,
  fontFamily: 'Fraunces, serif',
  fontSize: 140, fontWeight: 500,
  fontStyle: 'normal' | 'italic',
  letterSpacing: -0.03,      // em units
  lineHeight: 1.1,
  align: 'left' | 'center' | 'right',
  gradient: ['#6366f1', '#ec4899'],
  angle: 135,
})

slide.addBlob(opts)

await slide.addBlob({
  x, y, w, h,
  seed: 42,                  // deterministic PRNG
  points: 8,                 // control points
  irregularity: 0.4,         // 0..1
  fill: '#6366f1',
  fillGradient: ['#ec4899', '#f59e0b'],
  gradientAngle: 135,
})

slide.addGlassCard(opts)

await slide.addGlassCard({
  x, y, w, h,
  tint: '#ffffff', tintOpacity: 0.12,
  borderColor: '#ffffff', borderOpacity: 0.2, borderWidth: 2,
  radius: 24,
  highlight: true,           // inner top reflection
})

slide.addGrain(opts) · slide.addGradientRect(opts)

await slide.addGrain({ x, y, w, h, opacity: 0.08, monochrome: true })

await slide.addGradientRect({
  x, y, w, h,
  type: 'linear', colors: ['#0b0b0f', '#1e1b4b'], angle: 135,
})

Low-level escape hatch

Nothing from pptxgenjs is hidden:

const rawSlide = slide.raw()
rawSlide.addChart(pres._pptx.ChartType.bar, data, chartOpts)

pres._pptx.defineSlideMaster({ ... })

Exposed utilities

For advanced usage — build your own primitives:

import {
  blobSvg, gradientSvg, gradientTextSvg, glassSvg,
  svgToPngDataUrl, generateGrainPng,
  normalizeHex, hexToRgb, rgbToHex, mix,
} from 'slidewave'

Design rules

  • Sync methods → native PPT XML → text + shapes stay editable
  • Async methods → rasterized PNG → visually impossible effects, non-editable
  • All sizes in inches (pptxgenjs-compatible)
  • All colors as #RRGGBB or RRGGBB
  • pptxgenjs is a peer dep — use any compatible version you want

Use with LLMs

Slidewave is designed so agents (Claude, GPT, Gemini, Mistral) can produce richer output than pptxgenjs allows. Give an LLM the API reference above as part of your system prompt, and it will emit code that produces editorial-grade slides.

Development

git clone https://github.com/NdandaClaude/slidewave
cd slidewave
npm install
npm run dev              # demo playground at localhost:5173
npm run build:lib        # build library to dist/
npm run pack:check       # verify npm package contents

Roadmap

  • More primitives: addIsometricGrid, addCodeBlock (syntax-highlighted), addIcon (Lucide)
  • Editorial-grade chart styles
  • Node-compatible rasterizer (via @napi-rs/canvas) for SSR use
  • Monaco-based playground

License

MIT © 2026 Claude Ndanda