Claude Code skill: convert text posts into visual carousel images for Threads, Instagram, LinkedIn, TikTok
npx skills add https://github.com/itchernetski/threads-carousel-claude-skill --skill threads-carouselInstall this skill with the CLI and start using the SKILL.md workflow in your workspace.
A Claude Code skill that converts text posts into visual carousel images for Threads, Instagram, LinkedIn, TikTok, and Stories.

Paste a text post (or a Markdown file) into Claude, say "сделай карусель" / "make a carousel", and get a browser preview with exportable PNGs or a single PDF. Composable design system with four independent style axes, 12 slide types, 8 background decorations, and multi-platform format presets.
points variant of body for ✓/✗ pro/con lists with SVG iconsitalic-box style (Playfair italic on a colored rectangle)template/public/images/ and reference via imageSrc: "/images/file.png" in any image slide01, 02, TIP, NEW)text-wrap: balance on hooks and titles to prevent orphan wordssrc/slides.ts, engine in src/app/CarouselApp.tsx + src/lib/html-to-imageClone the repo into your Claude skills directory:
mkdir -p ~/.claude/skills
git clone https://github.com/itchernetski/threads-carousel-claude-skill.git ~/.claude/skills/threads-carousel
Install template dependencies (one-time):
cd ~/.claude/skills/threads-carousel/template
bun install # or pnpm / npm
In Claude Code, trigger the skill by pasting a post and saying:
Сделай карусель из этого поста
or: Make a Threads carousel from this text
Claude reads the text, splits it into slides, edits src/slides.ts in a temporary working copy of the template, launches bun dev on port 3333, and hands you the preview URL.
You can also use the template directly as a Next.js carousel generator:
cd template
bun install
# Edit src/slides.ts with your content
bun dev --port 3333
# Open http://localhost:3333
# Click "Export All" to download PNGs
template/
├── public/
│ └── images/ ← 📷 Drop PNG/JPG here for `image` slide type
├── src/
│ ├── slides.ts ← ✏️ Edit this: your SLIDES array + defaults
│ ├── lib/
│ │ ├── types.ts ← Shared type definitions
│ │ └── presets.ts ← FONT_STYLES + SURFACES + ACCENTS + composePreset + FORMAT_PRESETS
│ └── app/
│ ├── CarouselApp.tsx ← Rendering engine (all slide components + toolbar)
│ ├── page.tsx ← Dynamic client-only wrapper around CarouselApp
│ ├── layout.tsx ← Next.js root layout + font loading
│ └── globals.css ← Minimal global styles + toolbar transitions
├── package.json ← Dependencies (Next.js, React, html-to-image, jspdf)
├── tsconfig.json
├── next.config.ts
└── postcss.config.mjs
The golden rule: to change carousel content, only touch src/slides.ts. Everything else is the engine.
Each slide is an object in the SLIDES array with a type field and type-specific fields:
// hook — opening slide
{ type: "hook", text: "Claude Code\nis smarter with skills", highlight: "skills" }
// body — title + paragraph
{ type: "body", badge: "01", title: "Title", text: "Body text", highlight: "key" }
// body with points — pros/cons list with ✓/✗ SVG icons
{ type: "body", badge: "03", title: "Pros & Cons", points: [
{ type: "plus", text: "One click to register" },
{ type: "plus", text: "Works on any background" },
{ type: "minus", text: "Requires provider setup" },
]}
// list — numbered items
{ type: "list", title: "Steps", items: ["First", "Second", "Third"] }
// stats — big numbers
{ type: "stats", title: "Impact", stats: [
{ value: "3×", label: "More saves" },
{ value: "40%", label: "Faster" },
]}
// quote — pulled quote
{ type: "quote", text: "Quote text", author: "Someone", role: "2026" }
// checklist — checkmarks
{ type: "checklist", title: "Pre-flight", items: ["One", "Two", "Three"] }
// process — numbered steps with connector
{ type: "process", title: "How it works", steps: [
{ title: "Step 1", text: "Description" },
{ title: "Step 2", text: "Description" },
]}
// comparison — two-column VS
{ type: "comparison", title: "Before vs after",
leftLabel: "Before", leftItems: ["Slow", "Manual"],
rightLabel: "After", rightItems: ["Fast", "Automated"],
}
// cta — final call to action
{ type: "cta", text: "Follow for more", handle: "@username" }
// image — text + photo/screenshot (file in template/public/images/)
{ type: "image", badge: "02", title: "GitHub Trending",
imageSrc: "/images/screenshot.png",
imageCaption: "source: github.com/trending" }
// emoji — giant 360px emoji illustration + title + text
{ type: "emoji", emoji: "🚀", title: "Ship it", text: "Done beats perfect." }
// number — hero digit/string (up to 560px, auto-scales) + title + text
{ type: "number", bigNumber: "88", title: "color combos",
text: "8 surfaces × 11 accents — independent axes." }
Any slide with a highlight field can opt into a stylized highlight — Playfair italic on a colored rectangle (Karpathy-style):
{ type: "hook", text: "Carousels that\nactually pop",
highlight: "pop",
highlightStyle: "italic-box" }
minimal (Unbounded throughout)dark (black background, white text)yellow (#FACC15 highlighted words)carousel (bold 44px uppercase titles with divider)glow (soft radial gradient in alternating corners)threads-4x5 (1080×1350)Change any of these in src/slides.ts via DEFAULT_FONT, DEFAULT_SURFACE, DEFAULT_ACCENT, DEFAULT_PURPOSE, DEFAULT_BG, and DEFAULT_FORMAT. For a presentation deck, set DEFAULT_PURPOSE = "presentation" and DEFAULT_FORMAT = "wide-16x9".
Great combos to try: dark + teal (noir tech), paper + orange (literary warm), ember + lime (dramatic announcement), white + coral (sharp editorial), pastel + fuchsia (playful), gradient + amber (radiant).
Sizes below are for the default carousel purpose. The presentation purpose overrides titles to 72px / weight 700 / sentence case / no divider, and body to weight 400 / textSecondary / line-height 1.45. Fonts follow the active font axis — Unbounded display accents are used only when the font axis provides hookFontFamily (Minimal preset).
| Element | Size | Weight | Font axis |
|---|---|---|---|
| Hook | 88–170px (adaptive) | 800 | hookFontFamily ?? fontFamily |
| Title | 44px | 800 uppercase | fontFamily |
| Body | 48–88px | 600 | fontFamily |
| Points (pros/cons) | 44–62px | 600 | fontFamily |
| Badge | 26px | 800 uppercase | fontFamily |
| Stats value | 140–170px | 900 | fontFamily |
| Quote | 62px | 600 | fontFamily |
| List item | 46px | 600 | fontFamily |
| Handle | 36px | 500 | fontFamily |
| Emoji hero | 360px | — | OS emoji font |
| Big number | 320–560px (auto) | 900 | hookFontFamily ?? fontFamily |
| Image caption | 40px | 500 | fontFamily |
next/font/google for font loadingAdd an entry to FONT_STYLES in src/lib/presets.ts and extend the FontId union in src/lib/types.ts:
brand: {
id: "brand",
name: "Brand",
fontFamily: "var(--font-your-font)", // applied to titles, body, badges, stats, handles
hookFontFamily: "var(--font-your-display)", // optional, hook-only display font
},
Add an entry to SURFACES and extend the SurfaceId union in src/lib/types.ts:
midnight: {
id: "midnight",
name: "Midnight",
bg: "#0A0A1F",
bgGradient: undefined, // optional CSS gradient (overrides bg)
textColor: "#E0E7FF",
textSecondary: "rgba(224,231,255,0.5)",
accentColor: "#E0E7FF", // titles, dividers, decorative elements
},
Add an entry to ACCENTS and extend the AccentId union:
rose: { id: "rose", name: "Rose", color: "#F43F5E" },
Available font CSS variables (already loaded via Google Fonts):
--font-unbounded — geometric display, bold--font-space-grotesk — modern grotesque--font-inter — neutral sans-serif--font-playfair — classic serif--font-jetbrains-mono — monospace, tech feel--font-oswald — narrow condensed, poster feelThen set DEFAULT_FONT / DEFAULT_SURFACE / DEFAULT_ACCENT in src/slides.ts to your new ids. Font × surface × accent × purpose are composed into a final StylePreset at runtime by composePreset().
To add a new font: import it in src/app/layout.tsx via next/font/google, add a CSS variable, and reference it in a FONT_STYLES entry.
Add a new entry to FORMAT_PRESETS in src/lib/presets.ts and update the FormatId union type in src/lib/types.ts.
html-to-image with headless PNG generation for sharper output and CLI use. See Slashgear/linkedin-carousel-gen for reference.Feature requests and ideas: Telegram discussion
Thanks to everyone who shipped improvements:
points slide type, 3-axis style system, wide-16x9 format, PDF export, runtime format switcher, RU/EN toolbar (#1)MIT — see LICENSE.
Built with Claude Code.