Install
openclaw skills install corespeed-slideGenerate professional PowerPoint (.pptx) presentations using JSX/TSX with Deno. Supports slides, text, shapes, tables, charts (bar, line, pie, donut), images, gradients, shadows, and flexible layouts. Use when a user asks to create presentations, slide decks, pitch decks, reports, or any PPTX file.
openclaw skills install corespeed-slideGenerate professional .pptx files using TypeScript JSX via @pixel/pptx.
.tsx file that exports a deck variable.pptxdeno run --allow-read --allow-write --config {baseDir}/scripts/deno.json {baseDir}/scripts/generate.ts slides.tsx output.pptx [--json]
.tsx slide file (must export const deck = ...).pptx filename--json — structured JSON output for agent consumptionThese rules are distilled from the three masters of presentation design:
Follow these rules for every deck:
layout defaults on <Presentation>.slideWidth={u.in(13.33)} slideHeight={u.in(7.5)} on <Presentation>. The library defaults to 4:3 which looks wrong on modern screens.The library uses fixed-size containers. Text that exceeds the container will overflow and be clipped. Follow these rules strictly:
grow instead of fixed h whenever possible. Inside <Row> or <Column>, prefer grow={1} over explicit h={u.in(X)}. The layout engine will calculate the correct size.0.3 (title) + 4×0.3 (bullets) + 0.6 (padding) = 1.8in. Always round up.<Align> with explicit h: count lines × 0.3in + padding × 2. If the result > h, increase h or cut text.<Align> with tight heights inside <Stack>. Use <Column> with grow instead — it flexes to fit content.{/* ✅ GOOD: grow handles height automatically */}
<Column>
<Shape preset="roundRect" style={cardStyle} grow={1}>
<Text.P style={titleStyle}>Title</Text.P>
<Text.P style={bodyStyle}>Body text here</Text.P>
</Shape>
</Column>
{/* ❌ BAD: fixed h too small, text will overflow */}
<Align x="center" y="center" w={u.in(3)} h={u.in(1.5)}>
<Text>
<Text.P>Title</Text.P>
<Text.P>Line 1</Text.P>
<Text.P>Line 2</Text.P>
<Text.P>Line 3</Text.P>
<Text.P>Line 4</Text.P> {/* overflows! */}
</Text>
</Align>
Tables are the most common source of overflow. Follow these rules:
Column widths must sum to ≤ content width. For 16:9 slide with 1in padding each side: content width = 11.33in. All cols values must sum to ≤ 11.33in.
// ✅ GOOD: 2.5 + 4.0 + 4.0 = 10.5in < 11.33in
<Table cols={[u.in(2.5), u.in(4.0), u.in(4.0)]}>
// ❌ BAD: 3.0 + 5.0 + 5.0 = 13.0in > 11.33in → overflows right
<Table cols={[u.in(3.0), u.in(5.0), u.in(5.0)]}>
Give tables grow weight in <Column>. <Column> distributes height equally by default. A table with 5 rows needs more space than a title. Use grow to allocate proportionally:
// ✅ GOOD: table gets 4x the space of title
<Column>
<Text.P style={titleStyle}>Title</Text.P> {/* grow=1 default */}
<Table cols={[...]} grow={4}> {/* gets 4x height */}
<Table.Row>...</Table.Row>
...
</Table>
</Column>
// ❌ BAD: Column splits equally, table gets squeezed
<Column>
<Text.P>Label</Text.P>
<Text.P>Title</Text.P>
<Table cols={[...]}> {/* gets only 1/3 of height → rows overflow */}
...5 rows...
</Table>
</Column>
Budget row height. Each row of 12pt text needs ~0.45in (text + padding). Header row: 0.45in. A 5-row table needs: 0.45 + 4×0.45 = 2.25in minimum.
Keep cell text short. Max ~40 chars per cell. If text wraps to 2 lines, the row needs 0.65in instead of 0.45in.
Prefer fewer rows. Max 5-6 data rows per slide. If you need more, split into two slides.
| Style | Background | Primary | Accent | Text |
|---|---|---|---|---|
| Dark (OpenAI) | 0D0D0D | 10A37F | 6E42D3 | FFFFFF |
| Warm (Anthropic) | FDF6EE | D97706 | 1F4E79 | 1C1917 |
| Clean (Apple) | FFFFFF | 007AFF | FF3B30 | 1D1D1F |
| Academic (Stanford) | FFFFFF | 8C1515 | 2E2D29 | 2E2D29 |
| Neutral (Stripe) | F6F9FC | 635BFF | 0A2540 | 0A2540 |
Pick one palette. Don't mix.
The library has no built-in page numbers. Use <Positioned> on every slide:
// Helper — put at top of every .tsx file
const TOTAL = 10; // update to actual slide count
// ⚠️ CRITICAL: <Positioned> coordinates are RELATIVE TO CONTENT AREA (after slide padding).
// For 16:9 slide (13.33 x 7.5in) with padding 0.8/1.0/0.7/1.0:
// Content area = (13.33 - 1.0 - 1.0) x (7.5 - 0.8 - 0.7) = 11.33 x 6.0in
// Bottom-right page number: x = contentWidth - 1.0, y = contentHeight - 0.4
const PageNum = ({ n }: { n: number }) => (
<Positioned x={u.in(10.33)} y={u.in(5.6)} w={u.in(1)} h={u.in(0.4)}>
<Text.P style={{ fontSize: u.font(12), fontColor: textColor, align: "right" }}>
{n} / {TOTAL}
</Text.P>
</Positioned>
);
// Use on every slide — LAST child of <Slide> (renders on top, never occluded)
<Slide background={bg}>
<Column>
{/* ...slide content... */}
</Column>
<PageNum n={1} /> {/* ← MUST be last child for highest z-order */}
</Slide>
Rules:
<Positioned> is relative to content area, NOT the slide origin. Calculate: slideWidth - leftPadding - rightPadding = contentWidth. Position within those bounds.<PageNum> as the last child of <Slide> — PPTX has no z-index, rendering order = declaration order. Last element renders on top, so page numbers are never occluded by other content.Create a .tsx file. It must export a deck variable:
/** @jsxImportSource @pixel/pptx */
import { Align, clr, Presentation, Slide, Text, u } from "@pixel/pptx";
export const deck = (
<Presentation title="My Deck" slideWidth={u.in(13.33)} slideHeight={u.in(7.5)}>
<Slide background={{ kind: "solid", color: clr.hex("F7F4EE") }}>
<Align x="center" y="center" w={u.in(8)} h={u.in(1.5)}>
<Text.P style={{ fontSize: u.font(32), bold: true }}>
Hello, World!
</Text.P>
</Align>
</Slide>
</Presentation>
);
| Component | Purpose |
|---|---|
<Presentation> | Root container. Props: title, layout |
<Slide> | Single slide. Props: background, layout |
<Row> | Horizontal flex layout. Has <Row.Start>, <Row.End> |
<Column> | Vertical flex layout. Has <Column.Start>, <Column.End> |
<Stack> | Overlapping layers |
<Align x y w h> | Center/align a single child |
<Positioned x y w h> | Absolute positioning |
| Component | Purpose |
|---|---|
<Text> | Multi-paragraph text body. Props: gap, style |
<Text.P> | Single paragraph |
<Text.Span> | Inline text run |
<Text.Bold>, <Text.Italic>, <Text.Underline> | Inline formatting |
<Text.Link href="..."> | Hyperlink |
<Shape preset="..."> | Shape: rect, roundRect, ellipse, etc. |
<Image src={bytes} w={...} h={...} /> | Embed image (Uint8Array) |
<Table cols=[...]> | Table with <Table.Row> and <Table.Cell> |
| Component | Purpose |
|---|---|
<Chart.Bar data={[...]} category="key" series={[...]} /> | Bar chart |
<Chart.Line data={[...]} category="key" series={[...]} /> | Line chart |
<Chart.Pie data={[...]} category="key" series={[...]} /> | Pie chart |
<Chart.Donut data={[...]} category="key" series={[...]} /> | Donut chart |
import { u, clr } from "@pixel/pptx";
u.in(1) // inches
u.cm(2.5) // centimeters
u.pt(12) // points
u.pct(50) // percentage
u.font(24) // font size (hundredths of a point)
clr.hex("1F4E79") // hex color (no #)
Style props are plain objects. Use style on any component:
const style = {
fill: { kind: "solid", color: clr.hex("1F4E79") },
fontSize: u.font(24),
fontColor: clr.hex("FFFFFF"),
bold: true,
italic: false,
align: "center",
verticalAlign: "middle",
padding: u.in(0.2),
shadow: {
color: clr.hex("000000"),
blur: u.emu(12000),
distance: u.emu(4000),
angle: 50,
alpha: u.pct(18),
},
bullet: { kind: "char", char: "•" },
};
Backgrounds support solid, linear-gradient, and image.
/** @jsxImportSource @pixel/pptx */
import {
Align, Chart, clr, Column, Presentation, Row, Shape, Slide,
Stack, Table, Text, u, type Style,
} from "@pixel/pptx";
// --- Design tokens (pick ONE palette, use everywhere) ---
const bg = clr.hex("FDF6EE"); // warm cream
const card = clr.hex("FFFFFF");
const primary = clr.hex("D97706"); // amber
const dark = clr.hex("1C1917"); // near-black
const muted = clr.hex("78716C"); // warm gray
const accent = clr.hex("1F4E79"); // deep blue for charts
// --- Reusable styles ---
const s = {
heroBar: {
fill: { kind: "solid", color: primary },
verticalAlign: "middle",
padding: { top: u.in(0.3), right: u.in(0.5), bottom: u.in(0.3), left: u.in(0.5) },
} satisfies Style,
h1: { fontSize: u.font(36), fontColor: card, bold: true } satisfies Style,
subtitle: { fontSize: u.font(16), fontColor: clr.hex("FEF3C7") } satisfies Style,
h2: { fontSize: u.font(24), fontColor: dark, bold: true } satisfies Style,
body: { fontSize: u.font(14), fontColor: muted } satisfies Style,
bigNum: { fontSize: u.font(48), fontColor: primary, bold: true, align: "center" } satisfies Style,
bigLabel: { fontSize: u.font(14), fontColor: muted, align: "center" } satisfies Style,
cardBox: {
fill: { kind: "solid", color: card },
padding: u.in(0.3),
shadow: { color: clr.hex("000000"), blur: u.emu(8000), distance: u.emu(2000), angle: 90, alpha: u.pct(8) },
} satisfies Style,
};
export const deck = (
<Presentation title="Q2 Review" slideWidth={u.in(13.33)} slideHeight={u.in(7.5)} layout={{
slidePadding: u.in(0.6),
rowGap: u.in(0.35),
columnGap: u.in(0.35),
}}>
{/* Slide 1: Title — one idea only */}
<Slide background={{ kind: "solid", color: bg }}>
<Column>
<Shape preset="roundRect" h={u.in(1.6)} style={s.heroBar}>
<Text.P style={s.h1}>Q2 2025 Review</Text.P>
<Text.P style={s.subtitle}>Growth ahead of plan · 15% QoQ</Text.P>
</Shape>
<Row>
{/* Big number cards — Tufte: let the data speak */}
{[
{ num: "$1.2M", label: "Revenue" },
{ num: "15%", label: "Growth" },
{ num: "61", label: "NPS" },
].map(({ num, label }) => (
<Stack grow={1}>
<Shape preset="roundRect" style={s.cardBox} />
<Align x="center" y="center" w={u.in(2.5)} h={u.in(2.5)}>
<Text gap={u.in(0.1)}>
<Text.P style={s.bigNum}>{num}</Text.P>
<Text.P style={s.bigLabel}>{label}</Text.P>
</Text>
</Align>
</Stack>
))}
</Row>
</Column>
</Slide>
{/* Slide 2: Chart — one chart, full focus */}
<Slide background={{ kind: "solid", color: bg }}>
<Column>
<Text.P style={s.h2}>Revenue by Quarter</Text.P>
<Stack grow={1}>
<Shape preset="roundRect" style={s.cardBox} />
<Align x="center" y="center" w={u.in(8)} h={u.in(4)}>
<Chart.Bar
data={[
{ q: "Q1", rev: 0.8 }, { q: "Q2", rev: 1.2 },
{ q: "Q3", rev: 1.0 }, { q: "Q4", rev: 1.5 },
]}
category="q"
series={[{ name: "Revenue ($M)", value: "rev", color: accent }]}
labels
valueAxis={{ min: 0, max: 2 }}
/>
</Align>
</Stack>
</Column>
</Slide>
</Presentation>
);
@pixel/pptx from JSR on first run..tsx file must export const deck = ... (the JSX Presentation element).--json for structured output: {"ok": true, "file": "...", "size": 1234}yyyy-mm-dd-hh-mm-ss-name.pptx.Built by Corespeed. If you need help or run into issues: