facebook-carousel: cards for a Facebook carousel
Goal: produce the PNG assets of the N cards of a Facebook carousel ad, at the
chosen Meta ratio, coherent with each other and aligned with the
guidelines. A carousel is not N independent images: it is a series that tells
something. Coherence across cards is the heart of the deliverable.
This is a specialization of new-template. Same conventions: the Template
contract (from brand-artisan), assets through brand(), fonts discovered in
fonts/, export default ... satisfies Template.
Status of the dimensions. The specs come from Meta's Ads Manager (Ads
Guide + the carousel help pages); there is no organic post spec. The ratios are
official Meta; the "1080×1080" is a convention (Meta documents 1024×1024 as the
recommended minimum at 1:1).
0. Prerequisites (blocking)
The chain project -> template folder -> guidelines must exist:
- Resolve
<project> (from the arguments, otherwise ask).
- Check
templates/<project>/ and brands/<project>/brand.md.
- Missing guidelines or project -> STOP: ask for
/new-project <project>
first. No visual without guidelines (CLAUDE.md rule).
1. Frame it (ask, don't guess)
- Name of the carousel: a kebab-case slug, default
carousel. It is the
subfolder templates/<project>/<name>/. Check that it does not exist.
- Narrative mechanic (see §3): a story, one card per product, a top N, a
tutorial, before/after, or seamless. It dictates the whole design.
- Number of cards: 2 to 10, sweet spot 3-5. If a card adds nothing, drop
it.
- Ratio: default 1:1 (1080×1080). Otherwise 4:5 (1080×1350) or 9:16
(1080×1920). The same for every card (mandatory).
- Content of each card: a short message + its role in the series (hook,
development, CTA). Plus the final CTA.
- Read
brands/<project>/brand.md: palette, type, logo variants, don'ts. If
the project already holds a .tsx, read it as a reference.
- Read
brands/<project>/project.md if it exists: set the tone and the
claims of the cards (don't invent figures or promises). If absent -> ask for
the tone and the message rather than guessing.
2. Facebook constraints (to respect)
| Point |
Value |
Status |
| Number of cards |
2 to 10 (sweet spot 3-5) |
Official Meta |
| Ratio (identical throughout) |
1:1, 4:5 or 9:16 (3 % tolerance) |
Official Meta |
| Size 1:1 |
1080x1080 (Meta recommended min 1024) |
Convention / Meta |
| Size 4:5 |
1080x1350 |
Official Meta |
| Size 9:16 |
1080x1920 |
Official Meta |
| Format |
PNG or JPG |
Official Meta |
| Weight / image |
≤ 30 MB (aim for < 1 MB) |
Official Meta |
| Headline / card |
≤ 40 chars (≤ 25 to avoid truncation) |
Official Meta |
| Primary text |
≤ 125 chars (lives outside the image) |
Official Meta |
- The same ratio on every card: mixing 1:1 and 4:5 is not supported.
- The primary text and the headline/link are set in Ads Manager, not in
the image: don't write everything into the visual.
- The old 20 % text rule was dropped (2021), but an overloaded card is still
illegible on mobile: one idea per card.
3. The spirit: arrange it so it lands
Narrative mechanics (pick ONE)
- Sequential story: hook -> problem -> solution -> proof -> CTA. Each card
moves the story forward.
- One card = one product: a catalog, a range, variants. Constant product
position and background; each card legible on its own.
- Top N / tips: card 1 = the title of the list, cards 2..N = one item each,
the last = the CTA. Number the cards.
- Step-by-step tutorial: one step per card, each visual self-supporting
(understandable without reading the text).
- Before / after: strong contrast between the first and the last card.
- Seamless (panoramic): see the dedicated warning below.
Card 1 = the hook (decisive)
It is the only one visible before the first swipe. A single message, 3-5
words, strong contrast, one element that creates curiosity (a figure with no
context, a visual that "spills over", a question). Don't explain everything here:
that kills the swipe.
Coherence across cards (the heart of it)
- Identical palette and type on every card (a single accent that varies if
needed).
- The logo in a fixed, discreet position (the same corner throughout), never
as the centerpiece: enlarging it on every card is a common mistake.
- Constant placements: if the headline is at the bottom, it is always at the
bottom.
- A visual through-line: an element or a color that links the cards; a shade
that evolves from card to card signals the progression.
- Each card legible on its own (a user can enter in the middle).
Rising CTA
Soft at the start ("Find out more"), direct next, conversion on the last card
("Request a quote"). Each card has its own link in Meta.
Mistakes to avoid
Card 1 too explanatory; cards siloed with no through-line; repeating the same
message; text too dense; mixing ratios; logo too big.
4. Write the cards
Structure: one subfolder per carousel, one .tsx per card, plus a shared
theme.ts.
templates/<project>/<name>/
theme.ts <- shared palette + layout (pure TS, NO JSX)
card-1.tsx <- one card = one PNG
card-2.tsx
card-3.tsx
theme.ts factors out the coherence: palette taken from the guidelines,
ratio/SIZE, margins. Pure TS with no JSX: discovery only loads .tsx files,
so theme.ts is ignored as a template. Do not create a frame component as a
.tsx in this folder: it would be discovered as a fake card and break the
build. The coherence travels through the constants; the little bit of frame JSX
gets repeated in each card (they differ anyway).
- Naming and order: the sort is natural, so
card-2 comes before
card-10. Number the cards plainly, no padding needed.
- Colors as constants (from
theme.ts), assets through
brand("<project>/..."), fonts discovered in fonts/; a missing font -> the
"missing font" appendix in new-template, never silently.
theme.ts (skeleton)
import type { CSSProperties } from "react";
export const SIZE = { width: 1080, height: 1080 }; // 1:1 carousel
// <Project> guideline palette (from brands/<project>/brand.md).
export const INK = "#......";
export const ACCENT = "#......";
export const PAPER = "#......";
// Frame shared by every card: same background, same safe area.
export const frame: CSSProperties = {
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
padding: 96, // safe area (~10%)
backgroundColor: INK, // opaque background, identical everywhere
};
card-1.tsx (skeleton)
import type { ReactNode } from "react";
import type { Template } from "brand-artisan";
import { SIZE, frame, PAPER, ACCENT } from "./theme";
function render(): ReactNode {
return (
<div style={frame}>
{/* logo in a fixed position (same corner on every card) */}
{/* the hook: a single message, strong contrast */}
{/* progress marker (e.g. 1/3) */}
</div>
);
}
export default { size: SIZE, render } satisfies Template;
(With no title, the PNG comes out under the file's name:
out/<project>/<name>/card-1.png.)
Warning: seamless mode (panoramic)
Facebook inserts gutters (a border plus a shadow) between the cards, so a
panoramic image is never perfectly continuous on screen. If the user wants
seamless with this model (N files):
- Design a mural of
N×1080 × 1080 in your head, and offset the background by
-1080*(i-1) px in card i (each card renders its own slice).
- No critical element (text, a face, the logo) within about 100 px of a
left or right edge: the gutter would cut it.
- In Ads Manager, turn off automatic optimization ("show the best-performing
cards first") and upload left to right, otherwise Meta reorders them and
destroys the mural.
Seamless is demanding: by default, prefer a per-card mechanic (product, story, top
N) that tolerates the gutters.
5. Verify
npm run typecheck -> green.
npm run build -> writes out/<project>/<name>/card-*.png.
- Check each PNG: exact dimensions for the chosen ratio, the same ratio
throughout, opaque background.
- Check the visual coherence across the series (palette, type, logo position,
through-line) and that each card is legible on its own.
- Preview:
npm run dev then /<project>/<name> (lists the carousel's cards).
Success criterion: N cards (2-10) at the same Meta ratio, opaque, visually
coherent, card 1 catchy, the last card carrying the CTA, and using only colors and
type from brand.md.
1---2name: facebook-carousel3description: Creates the cards of a Facebook carousel ad at Meta's dimensions, as a series of coherent visuals. Use when the user wants a "Facebook carousel", a "carousel ad" or "several cards that swipe" for a project. Produces N .tsx templates (one card = one PNG) in a dedicated subfolder, sharing a common theme, aligned with brand.md. Requires a project with its brand.md in place.4---56# facebook-carousel: cards for a Facebook carousel78Goal: produce the **PNG assets of the N cards** of a Facebook carousel ad, at the9chosen **Meta ratio**, **coherent with each other** and aligned with the10guidelines. A carousel is not N independent images: it is a **series** that tells11something. Coherence across cards is the heart of the deliverable.1213This is a specialization of `new-template`. Same conventions: the `Template`14contract (from `brand-artisan`), assets through `brand()`, fonts discovered in15`fonts/`, `export default ... satisfies Template`.1617> **Status of the dimensions.** The specs come from Meta's **Ads Manager** (Ads18> Guide + the carousel help pages); there is no organic post spec. The ratios are19> official Meta; the "1080×1080" is a convention (Meta documents 1024×1024 as the20> recommended minimum at 1:1).2122## 0. Prerequisites (blocking)2324The chain **project -> template folder -> guidelines** must exist:2526- Resolve `<project>` (from the arguments, otherwise ask).27- Check `templates/<project>/` **and** `brands/<project>/brand.md`.28- Missing guidelines or project -> **STOP**: ask for `/new-project <project>`29 first. No visual without guidelines (CLAUDE.md rule).3031## 1. Frame it (ask, don't guess)3233- **Name of the carousel**: a kebab-case slug, default `carousel`. It is the34 **subfolder** `templates/<project>/<name>/`. Check that it does not exist.35- **Narrative mechanic** (see §3): a story, one card per product, a top N, a36 tutorial, before/after, or seamless. It dictates the whole design.37- **Number of cards**: 2 to 10, **sweet spot 3-5**. If a card adds nothing, drop38 it.39- **Ratio**: default **1:1 (1080×1080)**. Otherwise 4:5 (1080×1350) or 9:1640 (1080×1920). **The same for every card** (mandatory).41- **Content of each card**: a short message + its role in the series (hook,42 development, CTA). Plus the final **CTA**.43- Read `brands/<project>/brand.md`: palette, type, logo variants, **don'ts**. If44 the project already holds a `.tsx`, read it as a reference.45- Read `brands/<project>/project.md` if it exists: set the **tone** and the46 **claims** of the cards (don't invent figures or promises). If absent -> ask for47 the tone and the message rather than guessing.4849## 2. Facebook constraints (to respect)5051| Point | Value | Status |52|---|---|---|53| Number of cards | 2 to 10 (sweet spot 3-5) | Official Meta |54| Ratio (identical throughout) | 1:1, 4:5 or 9:16 (3 % tolerance) | Official Meta |55| Size 1:1 | **1080x1080** (Meta recommended min 1024) | Convention / Meta |56| Size 4:5 | 1080x1350 | Official Meta |57| Size 9:16 | 1080x1920 | Official Meta |58| Format | PNG or JPG | Official Meta |59| Weight / image | ≤ 30 MB (aim for < 1 MB) | Official Meta |60| Headline / card | ≤ 40 chars (≤ 25 to avoid truncation) | Official Meta |61| Primary text | ≤ 125 chars (lives **outside the image**) | Official Meta |6263- **The same ratio on every card**: mixing 1:1 and 4:5 is not supported.64- The **primary text** and the **headline/link** are set in Ads Manager, **not in65 the image**: don't write everything into the visual.66- The old **20 % text rule** was dropped (2021), but an overloaded card is still67 illegible on mobile: **one idea per card**.6869## 3. The spirit: arrange it so it lands7071### Narrative mechanics (pick ONE)72- **Sequential story**: hook -> problem -> solution -> proof -> CTA. Each card73 moves the story forward.74- **One card = one product**: a catalog, a range, variants. Constant product75 position and background; each card legible on its own.76- **Top N / tips**: card 1 = the title of the list, cards 2..N = one item each,77 the last = the CTA. Number the cards.78- **Step-by-step tutorial**: one step per card, each visual self-supporting79 (understandable without reading the text).80- **Before / after**: strong contrast between the first and the last card.81- **Seamless (panoramic)**: see the dedicated warning below.8283### Card 1 = the hook (decisive)84It is the only one visible before the first swipe. **A single message**, 3-585words, strong contrast, one element that creates curiosity (a figure with no86context, a visual that "spills over", a question). Don't explain everything here:87that kills the swipe.8889### Coherence across cards (the heart of it)90- **Identical palette and type** on every card (a single accent that varies if91 needed).92- **The logo in a fixed, discreet position** (the same corner throughout), never93 as the centerpiece: enlarging it on every card is a common mistake.94- **Constant placements**: if the headline is at the bottom, it is always at the95 bottom.96- **A visual through-line**: an element or a color that links the cards; a shade97 that evolves from card to card signals the progression.98- **Each card legible on its own** (a user can enter in the middle).99100### Rising CTA101Soft at the start ("Find out more"), direct next, **conversion on the last card**102("Request a quote"). Each card has its own link in Meta.103104### Mistakes to avoid105Card 1 too explanatory; cards siloed with no through-line; repeating the same106message; text too dense; mixing ratios; logo too big.107108## 4. Write the cards109110Structure: **one subfolder per carousel**, **one `.tsx` per card**, plus a shared111`theme.ts`.112113```114templates/<project>/<name>/115 theme.ts <- shared palette + layout (pure TS, NO JSX)116 card-1.tsx <- one card = one PNG117 card-2.tsx118 card-3.tsx119```120121- **`theme.ts` factors out the coherence**: palette taken from the guidelines,122 ratio/SIZE, margins. **Pure TS with no JSX**: discovery only loads `.tsx` files,123 so `theme.ts` is ignored as a template. **Do not** create a frame component as a124 `.tsx` in this folder: it would be discovered as a fake card and break the125 build. The coherence travels through the constants; the little bit of frame JSX126 gets repeated in each card (they differ anyway).127- **Naming and order**: the sort is **natural**, so `card-2` comes before128 `card-10`. Number the cards plainly, no padding needed.129- Colors as constants (from `theme.ts`), assets through130 `brand("<project>/...")`, fonts discovered in `fonts/`; a missing font -> the131 "missing font" appendix in `new-template`, never silently.132133### `theme.ts` (skeleton)134135```ts136import type { CSSProperties } from "react";137138export const SIZE = { width: 1080, height: 1080 }; // 1:1 carousel139140// <Project> guideline palette (from brands/<project>/brand.md).141export const INK = "#......";142export const ACCENT = "#......";143export const PAPER = "#......";144145// Frame shared by every card: same background, same safe area.146export const frame: CSSProperties = {147 width: "100%",148 height: "100%",149 display: "flex",150 flexDirection: "column",151 justifyContent: "space-between",152 padding: 96, // safe area (~10%)153 backgroundColor: INK, // opaque background, identical everywhere154};155```156157### `card-1.tsx` (skeleton)158159```tsx160import type { ReactNode } from "react";161import type { Template } from "brand-artisan";162import { SIZE, frame, PAPER, ACCENT } from "./theme";163164function render(): ReactNode {165 return (166 <div style={frame}>167 {/* logo in a fixed position (same corner on every card) */}168 {/* the hook: a single message, strong contrast */}169 {/* progress marker (e.g. 1/3) */}170 </div>171 );172}173174export default { size: SIZE, render } satisfies Template;175```176177(With no `title`, the PNG comes out under the file's name:178`out/<project>/<name>/card-1.png`.)179180### Warning: seamless mode (panoramic)181182Facebook inserts **gutters** (a border plus a shadow) between the cards, so a183panoramic image is **never** perfectly continuous on screen. If the user wants184seamless with this model (N files):185- Design a mural of `N×1080 × 1080` in your head, and **offset the background** by186 `-1080*(i-1)` px in card `i` (each card renders its own slice).187- **No critical element** (text, a face, the logo) within about **100 px** of a188 left or right edge: the gutter would cut it.189- In Ads Manager, **turn off automatic optimization** ("show the best-performing190 cards first") and upload **left to right**, otherwise Meta reorders them and191 destroys the mural.192Seamless is demanding: by default, prefer a per-card mechanic (product, story, top193N) that tolerates the gutters.194195## 5. Verify196197- `npm run typecheck` -> green.198- `npm run build` -> writes `out/<project>/<name>/card-*.png`.199- Check **each** PNG: exact dimensions for the chosen ratio, **the same ratio200 throughout**, opaque background.201- Check the **visual coherence** across the series (palette, type, logo position,202 through-line) and that **each card is legible on its own**.203- Preview: `npm run dev` then `/<project>/<name>` (lists the carousel's cards).204205**Success criterion**: N cards (2-10) at the same Meta ratio, opaque, visually206coherent, card 1 catchy, the last card carrying the CTA, and using only colors and207type from `brand.md`.