Local-first. Open source. One command to start.
- Why Open Carrusel
- See it in action
- Quickstart (60 seconds)
- What you can do
- How the AI agent works
- Slash commands
- Architecture
- Tech stack
- Project structure
- Configuration
- Troubleshooting
- Roadmap
- Contributing
- Acknowledgments
- About the maker
- License
Designing Instagram carousels eats hours. You either:
- Pay $20β60/month for a closed-source tool that limits how creative you can get
- Wrestle Canva templates that everyone else also uses
- Hand-craft slides in Figma and lose your weekend
Open Carrusel takes a different bet. You chat with Claude β the same model many designers already trust β and it generates real HTML/CSS slides that get screenshotted to PNGs at exact Instagram dimensions. Slides are unique, on-brand, and pixel-perfect. Everything runs on your laptop. Nothing is sent to a cloud you don't control.
It's open source under MIT. Fork it, tweak the system prompt, ship your own variant. No accounts. No subscriptions. No vendor lock-in.
Dashboard β your carousels, templates, and one-click export.
Editor β chat panel (left), live preview (center), drag-reorderable filmstrip (bottom).
The slides shown above were generated by chatting with Claude. No templates, no copy-paste β every layout, color, and font choice came from a conversation.
First run takes 1β2 minutes (Puppeteer downloads ~300 MB of Chromium for PNG export). After that, every launch is seconds.
- Install Claude Code and authenticate.
- Clone and open the repo in Claude Code:
git clone https://github.com/Hainrixz/open-carrusel.git cd open-carrusel claude - In the Claude Code prompt, type:
/start
That's it. Dependencies install, the dev server starts, your browser opens. Now design carousels by chatting.
git clone https://github.com/Hainrixz/open-carrusel.git
cd open-carrusel
npm run setup # installs deps + seeds /data/
npm run dev # starts http://localhost:3000You won't get the AI chat without Claude Code installed (the in-app agent shells out to the claude CLI), but the editor and export still work for static slides.
- Three-panel editor designed for flow: chat (left), live preview (center), drag-reorderable slide filmstrip (bottom).
- Generate slides by chatting: "Make me a 5-slide carousel about productivity habits β bold sans-serif, dark mode, accent red." Watch them stream in.
- Iterate per slide: "Make slide 3 more minimal", "Change the accent to teal", "Swap the hook for something punchier."
- Three Instagram aspect ratios ready to go: 1:1 (1080Γ1080), 4:5 (1080Γ1350), 9:16 (1080Γ1920).
- Brand config β name, color palette, fonts, logo, style keywords. Claude reads it before every generation so output stays on-brand.
- Templates β save any carousel as a template, reuse it for the next one.
- Reference images β drop in screenshots of carousels you love. Claude studies them to match style.
- Drag to reorder slides via dnd-kit. Undo per-slide if a tweak goes sideways (version history per slide).
- Safe-zone overlay to verify nothing important crops behind Instagram's UI.
- Fullscreen preview for the final review.
- One-click export β Puppeteer screenshots each slide HTML at the exact pixel dimensions Instagram expects, zips them, downloads.
- Captions + hashtags generator built into the editor.
- All local β slides, brand, uploads, exports all live in
/data/and/public/uploads/. Nothing is sent to a cloud you don't control. The only network call is when Claude Code talks to Anthropic.
The in-app agent is the Claude CLI spawned as a subprocess from /api/chat with --allowedTools Bash WebFetch. Messages stream back to the browser via Server-Sent Events.
When you ask for a slide, Claude:
- Reads your brand config + active carousel state from the system prompt
- Writes the slide as a complete HTML/CSS string
- POSTs it to
/api/carousels/[id]/slidesviacurl(using itsBashtool) - The new slide appears in your filmstrip seconds later
You > Create a 5-slide carousel about β3 morning habits that
actually move the needle.β Punchy, dark mode, accent red,
portrait 4:5.
Claude > Coming up. I'll build a hook slide, three habit slides,
and a CTA. Working...
[streams 5 HTML slides into the filmstrip]
You > Slide 3 β the headline is too long. Cut it in half and
move the icon to the top.
Claude > Done.
[updates that slide; you can /undo if you preferred the old one]
Slides are stored as body-level HTML (no <html>/<head>/<!DOCTYPE>). The shared function wrapSlideHtml() in src/lib/slide-html.ts wraps that body into a full document β adding font loading, dimension constraints, and box-sizing reset β and serves it both:
- to a sandboxed
<iframe>for live preview in the editor - to Puppeteer (headless Chromium) for export, screenshot at exact Instagram pixel dimensions, zipped, downloaded
Because the same wrap function feeds both paths, what you see is exactly what you export. No surprises.
Type these inside Claude Code:
| Command | What it does |
|---|---|
/start [port] |
Install + seed + run + open browser. Idempotent β re-running on a healthy install is seconds. |
/stop [port] |
Kill the dev server. Defaults to :3000, accepts a port arg matching /start. |
/reset |
Wipe local carousels, templates, brand config, uploads, exports β and re-seed defaults. Asks first. |
/doctor |
Run setup diagnostics: Node version, Claude CLI on PATH, deps installed, data files seeded, port free. |
You can also run them outside Claude Code:
npm run setup # equivalent to /start (skips the browser-open + background server bits)
npm run dev # start the dev server
npm run build # production build
npm run doctor # run scripts/doctor.mjs (works pre-`npm install`)flowchart LR
U(["Browser :3000"])
C["Chat Panel"]
P["Slide Preview<br/>(sandboxed iframe)"]
F["Filmstrip<br/>(dnd-kit)"]
API["/api/chat<br/>SSE streaming/"]
CCLI["Claude CLI<br/>subprocess"]
SLIDES["/api/carousels/.../slides/"]
DATA[("/data/*.json<br/>async-mutex<br/>atomic writes")]
EXP["/api/.../export/"]
PUP["Puppeteer<br/>(headless Chromium)"]
ZIP{{"ZIP of PNGs"}}
U --> C & P & F
C -- "POST chat" --> API
API -- "spawn" --> CCLI
CCLI -. "SSE" .-> API
API -. "SSE" .-> C
CCLI -- "curl POST slide HTML" --> SLIDES
SLIDES <--> DATA
P <--> SLIDES
F <--> SLIDES
U -- "Export" --> EXP
EXP --> PUP
PUP --> ZIP
ZIP --> U
Why these choices:
- Local-first, single-user. The whole app is a localhost web app talking to local files. No cloud, no auth, no database.
- Claude CLI as the agent. Lets us reuse the user's existing Claude Code authentication, capabilities, and context. The subprocess gets
Bash(tocurlthe slide-write endpoints) andWebFetch(for research while designing). - Slides as HTML. Claude already writes great HTML/CSS β way more flexible than canvas, way easier to debug than a JSON DSL. The same HTML powers preview and export, so what you see is what you ship.
- Sandboxed iframes. No
<script>tags allowed (enforced by the iframesandbox=""attribute). Slides can't run code or escape their box. - JSON file storage with async-mutex + atomic writes. No SQLite, no Postgres. Reads and writes go through
src/lib/data.tswith proper locking, and writes are tmp-file + rename to avoid torn JSON.
For more, see CLAUDE.md β the architecture doc tuned for AI assistants working on this codebase.
| Layer | Tool |
|---|---|
| Framework | Next.js 16 (Turbopack), React 19 |
| Language | TypeScript 5 |
| Styling | Tailwind CSS v4 (CSS-first config in globals.css) |
| UI primitives | Radix UI, lucide-react |
| Drag/drop | @dnd-kit |
| AI agent | Claude CLI subprocess |
| Image export | Puppeteer, Sharp |
| Zipping | Archiver |
| Storage | JSON files + async-mutex |
| Animation | CSS-first (Emil Kowalski's design philosophy) |
open-carrusel/
βββ .claude/
β βββ commands/ β /start, /stop, /reset, /doctor (Claude Code slash commands)
βββ data/ β user state (gitignored): brand, carousels, templates, exports
βββ docs/screenshots/ β README assets
βββ public/uploads/ β user uploads (gitignored): logos, reference images
βββ scripts/
β βββ setup.mjs β npm install + seed data dirs + Claude CLI detection (cross-platform)
β βββ doctor.mjs β env diagnostic (zero deps, runs pre-install)
βββ src/
β βββ app/
β β βββ api/ β every backend route (chat, carousels, slides, export, brand, ...)
β β βββ carousel/[id]/ β editor page
β β βββ globals.css β Tailwind v4 theme + Emil-style motion tokens
β β βββ layout.tsx
β β βββ page.tsx β dashboard page
β βββ components/
β β βββ brand/ β BrandSetup, ColorPicker, FontSelector, LogoUpload
β β βββ chat/ β ChatPanel, ChatMessage, ChatInput, ReferenceImages
β β βββ editor/ β CarouselPreview, SlideFilmstrip, SlideRenderer, ExportButton, ...
β β βββ layout/ β TopBar
β β βββ templates/ β TemplateGallery, TemplateCard
β β βββ ui/ β Button, Input, Badge, ConfirmDialog, CreateCarouselDialog
β βββ lib/
β β βββ chat-system-prompt.ts β dynamic system prompt (brand + carousel context)
β β βββ slide-html.ts β wrapSlideHtml() β the rendering contract
β β βββ carousels.ts β carousel + slide CRUD with version history
β β βββ data.ts β JSON storage with async-mutex + atomic writes
β β βββ claude-path.ts β portable Claude CLI discovery
β β βββ ...
β βββ types/ β shared TypeScript types
βββ CLAUDE.md β architecture doc for AI assistants working on this code
βββ LICENSE β MIT
βββ README.md β you are here
βββ next.config.ts
βββ package.json
βββ tsconfig.json
Created automatically by scripts/setup.mjs if it can find your Claude CLI. You can override:
CLAUDE_CLI_PATH=/path/to/claude # set if `which claude` doesn't find itOn Windows, run where claude in PowerShell to find the path (typically C:\Users\<you>\AppData\Roaming\npm\claude.cmd), then set CLAUDE_CLI_PATH in .env.local.
Set on first run (or via the gear icon in the top bar). Stored at /data/brand.json. Fields:
- Name β your handle / company / project
- Colors β primary, secondary, accent, background, surface
- Fonts β heading + body (Google Fonts; the
/api/fontsendpoint serves a curated list) - Logo β optional; used by Claude when you ask for branded slides
- Style keywords β free-text style hints ("editorial, minimalist, warm tones") that get injected into Claude's system prompt
Save any carousel as a template via the bookmark icon in the editor toolbar. Templates appear in the dashboard's Templates tab. Stored at /data/templates.json.
Drop screenshots into the chat panel's "Reference Images" section. Stored under /public/uploads/. Claude Code can read them via WebFetch of the local URL when designing.
/start says "Node v18 detected, need β₯20."
Next.js 16 requires Node 20+. Install via nodejs.org or nvm.
/start says "Claude CLI not found."
Install Claude Code and authenticate. The setup script searches ~/.local/bin/claude, /usr/local/bin/claude, /opt/homebrew/bin/claude, ~/.npm-global/bin/claude, and $CLAUDE_CLI_PATH. If yours lives elsewhere, set CLAUDE_CLI_PATH in .env.local.
Port 3000 is in use.
Run /stop to kill whatever's there, or run /start 3001 to use a different port.
Export fails or hangs.
Likely a Puppeteer/Chromium issue. Try rm -rf node_modules && npm install to re-trigger the Chromium download. On Linux you may need apt install of common Chromium dependencies (libnss3, libatk1.0-0, libxss1, etc.).
Slides look fine in preview but export looks different.
That shouldn't happen β both go through wrapSlideHtml(). If it does, file an issue with the slide HTML attached.
The AI keeps generating slides that ignore my brand colors.
Open the brand setup (gear icon) and confirm your colors and style keywords are saved. They're injected into Claude's system prompt on every chat request via chat-system-prompt.ts.
Run /doctor for a full env audit β it'll tell you which of the above applies.
Open ideas β PRs welcome. Tick what you ship, add your own.
- Multi-language slide generation β Spanish-LATAM voice presets so creators don't fight the AI's English defaults
- Reels storyboard mode β vertical 9:16 with optional text-on-clip annotations
- Twitter/X thread export β same brand voice, different surface
- Notion / Linear export β push the carousel as a doc with each slide as a section
- Theme presets gallery β community-curated
style-presets.jsonyou can one-click apply - Per-slide AI chat β a smaller chat thread scoped to a single slide
- Hosted demo β for people who want to try before installing Claude Code
PRs welcome. The bar:
- Run
npm run doctorandnpm run buildbefore opening a PR β both should pass clean. - Follow the file conventions in
CLAUDE.mdβ components β€ 300 lines, types insrc/types/, libs insrc/lib/,cn()fromsrc/lib/utils.tsfor class merging, all data writes throughsrc/lib/data.ts. - Don't touch the slide rendering contract.
wrapSlideHtml()insrc/lib/slide-html.tsis the seam between preview and export. Change it carefully and test the export round-trip. - Animations follow Emil Kowalski's philosophy β CSS-first, custom easings (already defined as CSS variables in
globals.css), respectprefers-reduced-motion. See theoc-*utility classes already inglobals.cssbefore authoring new ones.
Good first contributions: roadmap items above, more brand templates, accessibility audits, real screenshots/demos for the README, translations.
- Emil Kowalski β animation philosophy that shaped the whole motion system. The
oc-*CSS classes encode his design-engineering principles (custom easings, restraint over excess,@starting-styleover JS for entries). - Anthropic β Claude (the model) and Claude Code (the CLI) are the brain of the in-app agent.
- Vercel β Next.js + Turbopack make local-first React apps feel snappy.
- Radix UI + shadcn/ui β the patterns underneath the dialog/button/input primitives.
- dnd-kit β the only sane drag-and-drop story in React.
- Puppeteer + Sharp β the export pipeline.
Open Carrusel is built and maintained by tododeia β a content + AI lab building tools for creators who want leverage without lock-in.
Founder: Enrique Rocha (@soyenriquerocha) β Tijuanense, content creator, builds AI tools so creators don't fall behind. Also behind @tododeia and @metara.ai.
If Open Carrusel saves you time, the best support is:
- β Star this repo to help others find it
- π Open an issue when something breaks (or send a PR)
- π² Tag @soyenriquerocha or @tododeia when you ship a carousel made with it β we love seeing it
- π Visit tododeia.com for more AI-for-creators content
MIT β do anything you want with it. Attribution appreciated, never required.
Built with β€οΈ in Tijuana by tododeia.
Hecho para creadores que construyen el futuro.

