npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zythum02/pptxgenjsx

v0.1.6

Published

JSX runtime for building PowerPoint presentations with pptxgenjs

Readme

@zythum02/pptxgenjsx

A JSX runtime for building PowerPoint presentations with pptxgenjs. Write your slides as JSX components and render them to .pptx files.

import { Deck, Slide, Text, TextRun, Rect } from "@zythum02/pptxgenjsx";
import { renderPptx } from "@zythum02/pptxgenjsx/render";

await renderPptx(
  <Deck title="My Deck">
    <Slide>
      <Rect x={0} y={0} w={13.333} h={7.5} fill={{ color: "1E1E2E" }} />
      <Text x={1} y={3} w={11} h={1.5}>
        <TextRun options={{ fontSize: 44, color: "FFFFFF", bold: true }}>
          Hello, PowerPoint!
        </TextRun>
      </Text>
    </Slide>
  </Deck>,
  { fileName: "output.pptx" },
);

Fork notice: This package is based on the excellent work of pptxgenjs-jsx by Artifact Kit. It extends the original runtime with additional features and adjustments.

Table of Contents


Quick Start

Use the official starter template pptxgen-ts-starter to quickly scaffold a new project — preconfigured with tsconfig, sample deck, and build script:

npx degit zythum/pptxgen-ts-starter my-presentation
cd my-presentation
npm install
npm run build     # produces output/presentation.pptx

Installation

npm install @zythum02/pptxgenjsx

Peer dependency:

  • pptxgenjs v4.x — automatically installed as a dependency.

TypeScript Configuration

Set jsx and jsxImportSource in your tsconfig.json:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@zythum02/pptxgenjsx"
  }
}

Note: This is not React — the JSX transform produces PptxNode objects, not DOM elements. No React dependency is required.


Component Reference

Deck / Presentation

The root element of every presentation. Maps to new PptxGenJS().

<Deck>
  <Slide>...</Slide>
</Deck>

<Presentation> is an alias for <Deck>.

Layout

Use the layout prop to control slide dimensions. Two forms are supported:

1. Built-in layout name (string) — pptxgenjs provides four standard presets:

| Name | Dimensions | Aspect Ratio | | ---------------- | ------------- | ------------ | | "LAYOUT_4x3" | 10" × 7.5" | 4:3 | | "LAYOUT_16x9" | 10" × 5.625" | 16:9 | | "LAYOUT_16x10" | 10" × 6.25" | 16:10 | | "LAYOUT_WIDE" | 13.33" × 7.5" | 16:9 (wide) |

Default: "LAYOUT_WIDE" (13.33" × 7.5").

<Deck layout="LAYOUT_16x9">
  <Slide>...</Slide>
</Deck>

2. Custom layout (object) — define arbitrary dimensions via a PresLayout object with name, width, and height (in inches):

<Deck layout={{ name: "A4", width: 10.83, height: 7.82 }}>
  <Slide>...</Slide>
</Deck>

For multiple custom layouts, use the layouts prop (array of PresLayout):

<Deck
  layout="A4"
  layouts={[
    { name: "A4", width: 10.83, height: 7.82 },
    { name: "Letter", width: 10, height: 7.5 },
  ]}
>
  <Slide>...</Slide>
</Deck>

The first matching layout name becomes the presentation's active layout.

Slide

A single slide. Maps to pptx.addSlide().

<Slide>
  <Text>A simple slide</Text>
</Slide>

Lazy-loaded slide (see Lazy Slide Loading):

<Slide component={() => import("./slides/chart-slide")} />

Text & TextRun

<Text> — a text box or rich text container. Maps to slide.addText().

// Simple text (string from children)
<Text x={1} y={1} w={8} h={1} fontSize={24} color="333333">
  Hello, World!
</Text>

// Rich text with multiple TextRun elements
<Text x={1} y={2.5} w={8} h={1.5} valign="middle">
  <TextRun options={{ fontSize: 18, color: "666666" }}>Normal text </TextRun>
  <TextRun options={{ fontSize: 18, color: "0066CC", bold: true }}>
    bold and blue
  </TextRun>
</Text>

<TextRun> — a single formatted run inside <Text>. options accepts pptxgenjs TextProps (fontSize, color, bold, italic, fontFace, etc.). The run's text comes from its string children (or the text prop):

<TextRun options={{ fontSize: 18, color: "0066CC", bold: true }}>bold and blue</TextRun>

Text input modes. A <Text> element has four ways to receive content. When more than one is present, this priority applies (higher wins, the rest is ignored):

  1. child <TextRun /> nodes — plain string/number children mixed among them are converted to default-style runs (order preserved)
  2. the runs prop (rich text array — same shape as pptxgenjs TextProps[])
  3. the text prop
  4. plain-string children

Plain strings mix freely with <TextRun /> children — each string segment becomes a default-style run in place:

<Text x={1} y={4} w={8} h={1}>
  {"Normal segment "}
  <TextRun options={{ color: "0066CC", bold: true }}>highlighted</TextRun>
</Text>

Whitespace-only text between elements (e.g. from multi-line JSX formatting) is ignored — attach explicit spaces to a run's text (<TextRun>A </TextRun>) instead. Mixing the other modes (e.g. runs or text props alongside children) still drops the lower-priority content.

// runs prop (imperative rich text)
<Text
  x={1}
  y={1}
  w={8}
  h={1}
  runs={[
    { text: "Normal ", options: { fontSize: 18 } },
    { text: "bold", options: { fontSize: 18, bold: true } },
  ]}
/>

Text with a shape background. shape is a valid pptxgenjs text option and is forwarded to slide.addText():

<Text
  x={1}
  y={1}
  w={4}
  h={2}
  shape="roundRect"
  fill={{ color: "EDE9FE" }}
  margin={18} // text margin uses POINTS (~0.25"), not inches
  valign="middle"
>
  Shaped text box
</Text>

Note: pptxgenjs text margin values are in points (e.g. 18 ≈ 0.25"), not inches.

Shapes

All pptxgenjs shapes are available as JSX components. Each supports standard positioning props (x, y, w, h) plus shape-specific options via options or as top-level props.

Shapes are leaf elements — they do not render children. Nested content (e.g. <RoundRect><Text>…</Text></RoundRect>) is a compile-time error in TSX and a hard runtime error otherwise; it is never silently dropped.

To put text on a shape, use one of:

  1. A single text box with a shape background: <Text shape="roundRect" …> (recommended when the text and shape share one box — see Text & TextRun).
  2. A sibling <Text> layered over the shape (when text and shape need independent styles/shadows/geometry).
  3. A <Group> only when you need a relative coordinate system or want to move/scale the pair together.
<Rect x={0} y={0} w={13.333} h={7.5} fill={{ color: "1E1E2E" }} />
<Ellipse x={1} y={1} w={3} h={2} fill={{ color: "FF6B6B" }} />
<Triangle x={5} y={1} w={2} h={2} fill={{ color: "4ECDC4" }} />
<RoundRect x={1} y={4} w={4} h={2} fill={{ color: "45B7D1" }} rectRadius={0.3} />
<Line x={1} y={1} w={5} h={0} line={{ color: "FF0000", width: 2 }} />
<Cloud x={1} y={1} w={3} h={2} fill={{ color: "E8F4FD" }} />
<Heart x={5} y={1} w={2} h={2} fill={{ color: "FF4081" }} />

<LineBetween> — a line connecting two absolute coordinates. Supports percentage coordinates.

<LineBetween
  x1={0}
  y1={0}
  x2={13.333}
  y2={7.5}
  line={{ color: "999999", width: 1, dashType: "dash" }}
/>

Available shape components:

| Component | pptxgenjs shape | | -------------------------- | ----------------------------------- | | Rect | rect | | RoundRect | roundRect | | Ellipse / Oval | ellipse | | Triangle | triangle | | RightTriangle | rtTriangle | | Diamond | diamond | | Pentagon | pentagon | | Hexagon | hexagon | | Star / Star5 | star5 | | Star4 | star4 | | Star6 | star6 | | Star8 | star8 | | Star10 | star10 | | Line | line | | LineBetween | line (with computed bounding box) | | Arc | arc | | BlockArc | blockArc | | PieShape | pie | | CustomGeometry | custGeom | | LeftArrow / RightArrow | leftArrow / rightArrow | | UpArrow / DownArrow | upArrow / downArrow | | LeftRightArrow | leftRightArrow | | UpDownArrow | upDownArrow | | Chevron | chevron | | Cloud | cloud | | Heart | heart | | Donut | donut | | Plus | plus |

Charts

// Generic chart with explicit `type`
<Chart
  x={1} y={1} w={10} h={5}
  type="bar"
  data={[
    { name: "Q1", labels: ["Jan", "Feb", "Mar"], values: [100, 150, 200] },
    { name: "Q2", labels: ["Jan", "Feb", "Mar"], values: [120, 180, 160] },
  ]}
  showTitle="Quarterly Sales"
  showLegend={true}
/>

// Or use a typed chart component
<BarChart x={1} y={1} w={10} h={5} data={[...]} showValue={true} />
<LineChart x={1} y={1} w={10} h={5} data={[...]} lineSize={3} />
<PieChart x={1} y={1} w={6} h={5} data={[...]} showPercent={true} />

Available chart components: AreaChart, BarChart, Bar3DChart, BubbleChart, DoughnutChart, LineChart, PieChart, RadarChart, ScatterChart.

Tables

<Table x={1} y={1} w={10} h={3} fontSize={12} border={{ type: "solid", color: "CCCCCC" }}>
  <TableRow>
    <TableCell options={{ fill: { color: "4472C4" }, color: "FFFFFF", bold: true }}>Name</TableCell>
    <TableCell options={{ fill: { color: "4472C4" }, color: "FFFFFF", bold: true }}>
      Value
    </TableCell>
  </TableRow>
  <TableRow>
    <TableCell>Item A</TableCell>
    <TableCell>100</TableCell>
  </TableRow>
  <TableRow>
    <TableCell>Item B</TableCell>
    <TableCell>200</TableCell>
  </TableRow>
</Table>

TableToSlides splits an HTML table across multiple slides (browser runtime only).

Images & Media

<Image
  x={1} y={1} w={5} h={3}
  path="https://example.com/image.png"
  sizing={{ type: "contain", w: 5, h: 3 }}
/>

<Media
  x={1} y={1} w={6} h={4}
  path="https://example.com/video.mp4"
  mediaType="video"
/>

Group

A logical container that offsets all child elements relative to the group's position. Child coordinates are relative to the group's virtual canvas.

<Group x={1} y={1} w={10} h={5}>
  {/* (0, 0) inside group → (1, 1) on slide */}
  <Rect x={0} y={0} w={10} h={5} fill={{ color: "F0F0F0" }} />
  {/* "50%" inside group → 5" from group origin → 6" from slide origin */}
  <Text x="50%" y="50%" w={4} h={1}>
    <TextRun options={{ fontSize: 18 }}>Centered in group</TextRun>
  </Text>
</Group>

Key features:

  • Coordinate transformation: All child x, y, w, h values are resolved relative to the group's virtual canvas. Percentage strings are resolved against the group's w (for x/w) or h (for y/h), then offset by the group's absolute position.
  • Nested groups: Groups can be nested — each level accumulates its offset.
  • Context-aware: Children can use useGroupContext() to get the group's virtual canvas dimensions.

Raw (escape hatch)

For pptxgenjs features not covered by a dedicated component.

<Raw
  render={({ pptx, slide, node }) => {
    // Direct access to slide.addShape(), slide.addText(), etc.
    slide.addShape("rect", { x: 1, y: 1, w: 5, h: 3, fill: { color: "FF0000" } });
  }}
/>

Raw children are not auto-rendered — they are only accessible via context.node.children inside the render callback. Use props for configuration; use children only when the callback reads them itself.

Fragment

Groups multiple children without producing a wrapper element. Useful when a component needs to return multiple siblings (e.g., in a lazy-loaded slide or inside a map).

Shorthand form <>...</> — for simple grouping without props:

// slides/title-slide.tsx
export default function TitleSlide() {
  return (
    <>
      <Text x={1} y={3} w={10} h={1.5} fontSize={44} bold>
        Welcome
      </Text>
      <Text x={1} y={4.5} w={10} h={1} fontSize={18} color="666666">
        Subtitle text
      </Text>
    </>
  );
}

Explicit <Fragment> — when you need a key prop (e.g., in a .map() loop):

import { Fragment } from "@zythum02/pptxgenjsx";

<Slide>
  {items.map((item) => (
    <Fragment key={item.id}>
      <Text x={1} y={item.y}>
        {item.name}
      </Text>
      <Text x={5} y={item.y}>
        {item.value}
      </Text>
    </Fragment>
  ))}
</Slide>;

Note: <>...</> is a JSX compile-time syntax — it does not support props like key. For dynamic lists, always use <Fragment key={...}>.


Async Components

Components can be async functions — they are automatically detected and lazily resolved during rendering:

// slides/data-slide.tsx
export default async function DataSlide() {
  const res = await fetch("https://api.example.com/data");
  const data = await res.json();

  return (
    <Slide>
      <Text x={1} y={1} w={8} h={1} fontSize={32} bold>
        {data.title}
      </Text>
      <Text x={1} y={2.5} w={8} h={4} fontSize={16}>
        {data.description}
      </Text>
    </Slide>
  );
}
// main.tsx
await renderPptx(
  <Deck>
    <Slide>{/* ... */}</Slide>
    <DataSlide /> {/* async — resolves automatically */}
  </Deck>,
  { fileName: "output.pptx" },
);

This works because the JSX factory (jsx) wraps async component results in a PptxNodePromise, and the renderer resolves them during tree traversal.


Context Hooks

Context hooks provide runtime information about the current rendering environment.

How it works: When TypeScript compiles your JSX, component factories are NOT called during JSX construction. Instead, they are wrapped in a deferred node and executed later during rendering — at which point the renderer has set up the context store via AsyncLocalStorage. This is why you can write components that call useSlideContext() as direct children of <Slide>, even though the JSX appears to be built "eagerly."

useSlideContext

Exposes the current slide's index and total.

import { useSlideContext } from "@zythum02/pptxgenjsx";

function SlideNumber() {
  const { index, total, sectionTitle } = useSlideContext();

  return (
    <Text x={1} y={6.5} w={10} h={0.5} fontSize={10} color="999999">
      Slide {index} of {total}
      {sectionTitle ? ` · ${sectionTitle}` : ""}
    </Text>
  );
}
// Usage inside a Slide:
<Slide>
  {/* ... slide content ... */}
  <SlideNumber />
</Slide>

useDeckContext

Exposes the deck's slide dimensions (width, height in inches).

import { useDeckContext } from "@zythum02/pptxgenjsx";

function FullBleedBackground() {
  const { width, height } = useDeckContext();
  return <Rect x={0} y={0} w={width} h={height} fill={{ color: "1E1E2E" }} />;
}

useGroupContext

Exposes the current group's absolute offset and virtual canvas dimensions. When called outside a <Group>, falls back to deck dimensions with zero offset.

import { useGroupContext } from "@zythum02/pptxgenjsx";

function ProgressBar() {
  const { width } = useGroupContext();
  return <Rect x={0} y={0} w={width * 0.7} h={0.4} fill={{ color: "4CAF50" }} />;
}

Lazy Slide Loading

Use the component prop on <Slide> to defer loading of slide definitions — analogous to React Router's lazy route loading.

// slides/title-slide.tsx
export default function TitleSlide() {
  return (
    <>
      <Text x={1} y={3} w={10} h={1.5} fontSize={44} bold>
        Welcome
      </Text>
    </>
  );
}
// main.tsx
<Slide component={() => import("./slides/title-slide")} />

The component's return value is rendered inside the <Slide> that declares component, so it should provide slide content only — not another <Slide> element. Context hooks work inside lazy-loaded components.


Percentage Coordinates

x, y, w, h values can be specified as percentage strings (e.g. "50%", "100%"), which are resolved relative to the enclosing context:

  • Inside a <Group>: resolved against the group's w (for x/w) or h (for y/h).
  • Directly inside a <Slide>: resolved against the slide's dimensions from the deck layout.
<Group x={1} y={1} w={10} h={5}>
  {/* 50% of group width (5"), 25% of group height (1.25") */}
  <Rect x="25%" y="25%" w="50%" h="50%" fill={{ color: "4ECDC4" }} />
</Group>

This works for all positioning props: x, y, w, h on all shape/text/image components, and x1, y1, x2, y2 on LineBetween.


Sections & Masters

Sections

Group slides into named sections in the PowerPoint outline view.

<Deck>
  <Slide>...</Slide>
  <Section title="Overview">
    <Slide>...</Slide>
    <Slide>...</Slide>
  </Section>
  <Section title="Details">
    <Slide>...</Slide>
  </Section>
</Deck>

Masters

Define slide masters with reusable layout objects.

<Deck>
  <Master name="myMaster" background={{ fill: "F5F5F5" }}>
    <Text x={1} y={0.3} w={10} h={0.5} fontSize={10} color="999999">
      Confidential
    </Text>
    <Rect x={0} y={7} w={13.333} h={0.5} fill={{ color: "4472C4" }} />
  </Master>
  <Slide masterName="myMaster">...</Slide>
</Deck>

You can also use <Placeholder> inside masters:

<Master name="content">
  <Placeholder options={{ name: "Body", type: "body", x: 1, y: 1, w: 10, h: 5 }} />
</Master>

Validation

The validateDeck() function checks your slide tree for common mistakes before rendering. It is async — always await it:

import { validateDeck } from "@zythum02/pptxgenjsx/render";

const deck = <Deck>{/* ... */}</Deck>;
const issues = await validateDeck(deck);

if (issues.length > 0) {
  for (const issue of issues) {
    console.error(`[${issue.level}] ${issue.message}`);
  }
}

Validation catches:

  • Invalid child types (e.g., a <Text> directly inside a <Deck>)
  • Leaf components with children (child.leaf, e.g. <RoundRect> containing a <Text>)
  • Missing required props (e.g., CustomGeometry without points)
  • Suspicious prop usage (e.g., angleRange on RoundRect)
  • Mixed <Text> input modes (text.input.mixed) — warns whenever a lower-priority source would be ignored and recommends <TextRun /> children for rich text
  • Invalid LineBetween endpoints

Rendering

To a File

import { renderPptx } from "@zythum02/pptxgenjsx/render";

await renderPptx(<Deck>{/* ... */}</Deck>, {
  fileName: "output/presentation.pptx",
});

Additional pptxgenjs writeFile options are also supported:

await renderPptx(<Deck>{/* ... */}</Deck>, {
  fileName: "output.pptx",
  compression: true, // Enable ZIP compression
  zipOptions: { level: 9 }, // Compression level
});

To a Buffer / Blob

import { writePptx } from "@zythum02/pptxgenjsx/render";

const buffer = await writePptx(<Deck>{/* ... */}</Deck>, {
  outputType: "arraybuffer", // "arraybuffer" | "blob" | "uint8array" | "base64" | "nodebuffer"
});

Reuse an Existing PptxGenJS Instance

import PptxGenJS from "pptxgenjs";

const pptx = new PptxGenJS();
// ... configure the instance ...
await renderPptx(<Deck>{/* ... */}</Deck>, { pptx });

Aliases

import { render, write } from "@zythum02/pptxgenjsx/render";

await render(<Deck>{/* ... */}</Deck>, { fileName: "output.pptx" });

License

MIT