The approved Instagram and LinkedIn post set (1080×1350) with the design language behind it: frame, tokens, components, rules and a spec any AI model can follow to make new posts that match.
All coordinates are in post pixels on the 1080×1350 canvas. Inside posts use this frame; covers add a left rail (see Covers in the library).
Chrome bar · 0–72White, dashed bottom rule. Three grey dots #DCDDDF (r6, x 34/55/76). URL pill 360×38 centred, lock icon in blue, 17px. Handle @builtforsaas 16px #8A9097, right 28.
Header · x 104Pillar pill: 32px white tile r8 with a 16px blue line icon, then "Pillar · label" 18px/600. Headline 66px, weight 680, line-height 1.02, tracking −0.045em, #141618. Optional grey second line #9AA0A7.
Subtext25px, line-height 1.45, #5C6169, 22px under the headline, max about 800px wide (1–2 lines). Says the point or the source. On every inside post.
Visual area · x 64–1016, ends at y 1270Dashed 1.5px (5/6) rect, fill #EFEFED + fine grain, 9px white corner handles. Top depends on the header: 300 (1 line), 360 (headline + 1-line subtext), 420 (3 lines), 470 (headline + grey line + 2-line subtext). Must start ≥24px under the header.
Main objectOne hero object, 40–56px from the area edges (windows usually at x 120, width 840). Radius 16 on all four corners, layered navy shadow.
Footer · centre y 1307"Remote design team for SaaS" 19px #5C6169 at x 144; "Save" + blue bookmark on the right. Carousels: 5-segment progress (active blue), "01 / 09", "Swipe →"; last slide "Link in bio ↗".
Design the 1080×1350 post once; it ships unchanged to Instagram, LinkedIn, X and Threads. Stories, link cards and blog images get their own frames, built from the same parts. Blue dashed area = safe zone for text and the main object.
| Channel | Use | Export size | Ratio | File | Notes |
|---|---|---|---|---|---|
| Feed post, carousel slide | 1080×1350 | 4:5 | PNG | The master size. The profile grid shows a centred 3:4 crop, which trims about 34px from each side: keep everything inside the dashed guides (x 64–1016), as the frame already does. Every carousel slide the same size, max 20 slides. | |
| Story, Reel cover | 1080×1920 | 9:16 | PNG | Keep text out of the top ~250px and bottom ~340px (app UI sits there). The grid shows the Reel cover as a centred 3:4 crop. | |
| Image post | 1080×1350 | 4:5 | PNG | Same file as Instagram. Taller than 4:5 gets cropped. | |
| Carousel (document post) | 1080×1350 per page | 4:5 | One PDF, every page identical in size (the first page sets the size for all). Under 100 MB, 8–12 pages. Cover first, CTA last. | ||
| Link preview | 1200×630 | 1.91:1 | PNG/JPG | Comes from the page's og:image, so the blog OG image covers it. | |
| X | Single image | 1080×1350 | 4:5 | PNG ≤ 5 MB | Shows uncropped as a single image. Posts with 2–4 images get cropped tiles, so use one image per post. |
| Landscape / link card | 1200×675 | 16:9 | PNG/JPG | For a wide visual or the summary_large_image card (set twitter:card). | |
| Threads | Image post, carousel | 1080×1350 | 4:5 | PNG | Same file as Instagram. |
| Blog | Card thumbnail | 1600×1000 | 16:10 | WebP/AVIF | Matches the 16:10 thumbs in the BFS blog. Shown small: one headline, one visual, no body text. |
| Article hero | 2400×1350 | 16:9 | WebP/AVIF | 2× of a 1200px column. Also the BlogPosting image (Google wants ≥ 1200px wide; 16:9, 4:3 or 1:1). | |
| In-article figure | 1600×1000 or 1600×900 | 16:10 / 16:9 | WebP/AVIF | One real screenshot or diagram per figure, with a caption below in HTML (not in the image). | |
| OG / share image | 1200×630 | 1.91:1 | PNG | Generated per post at build time. Absolute https URL in og:image; add twitter:card = summary_large_image. | |
| Video | YouTube / video thumbnail | 1280×720 | 16:9 | JPG/PNG ≤ 2 MB | Bottom-right corner is covered by the duration badge. |
| Step | Setting | Why |
|---|---|---|
| Render | node export.mjs S3-<name> 2 → 2160×2700 PNG (use 3 → 3240×4050 for fine UI detail) | Platforms downscale a larger source more cleanly than they upscale; text and 1.5px lines stay crisp. |
| Compress | python compress.py png@2x upload@2x | Writes upload-ready files next to the masters. |
| Format | JPEG quality 90, progressive | Measured on W1: 2× PNG 1.6 MB → JPEG 0.49 MB, PSNR 46.7 dB against the PNG, so no visible difference. 3× ≈ 0.93 MB. |
| Colour | 4:4:4 chroma (no subsampling) + embedded sRGB profile | Default 4:2:0 smears blue pins, red/green tags and coloured text edges; the profile keeps colours identical on every phone. |
| LinkedIn carousel | PDF from the 2× JPEGs, one page each | Same pages, small file (8–12 pages ≈ 4–6 MB). |
| Blog | WebP/AVIF quality ~80 at 2× of display size, PNG for og:image | Smallest files for the site; og:image stays PNG for previews. |
| Never | Upscale a 1× export, use 4:2:0 JPEG, strip the colour profile, or save below quality 85 | Each one blurs text or shifts BFS blue. |
Blue is the only brand accent. State colours appear only when they carry meaning (before/after, myth/fact, live, waiting).
| d1 | 0 0 0 1px rgba(20,22,24,.08), 0 1px 1px rgba(16,40,96,.04), 0 2px 4px rgba(16,40,96,.05) |
| d3 | 0 0 0 1px rgba(20,22,24,.08), 0 2px 3px rgba(16,40,96,.06), 0 12px 24px -8px rgba(16,40,96,.18), 0 40px 80px -24px rgba(16,40,96,.28) |
| Radius | 16 windows, cards, panels, phones (screen r = width × .118) · 12 buttons · 10 pins, highlight rings · 8 tags |
| Light | One light from above. Navy-tinted shadows, never pure black. |
| Type role | Size / line | Weight · tracking | Colour | Where |
|---|---|---|---|---|
| Inside headline | 66 / 1.02 | 680 · −0.045em | #141618 | Every inside post |
| Cover headline | 56 / 1.04 | 680 · −0.04em | #141618 + grey line | First slide |
| No-visual headline | 80 / 1.0 | 700 · −0.045em | #141618 + #9AA0A7 | D1, H2 |
| Subtext | 25 / 1.45 | 400 | #5C6169 | Under every headline |
| Pillar label | 18 | 560–600 | #141618 / blue on covers | Header pill |
| Callout title | 23–26 | 650 · −0.02em | #141618 | Pins, legends |
| Callout text | 18–20 / 1.4 | 400 | #5C6169 | Pins, legends, captions |
| Mono labels | 14–17 Geist Mono | 500 | #8A9097 / blue | Pins, code, axis, meta |
| Footer | 19 | 400 / 600 | #5C6169 / #141618 | Every post |
| Minimum | 16px | – | – | Anything outside a real screenshot (QA fails below 16) |
| Spacing | Value |
|---|---|
| Grid | 8pt (4 for small details). Text x 104 inside, 144 on covers. |
| Content to visual-area edge | 40–56px (never touching, nothing cut by accident) |
| Header end → visual area | ≥ 24px (QA gate), usually 40–56 |
| Title → its text | 4–8px; groups 20–40px; ruled rows 24–32px padding |
| Window chrome | Top bar 40px, dots 11px #DCDDDF gap 8, URL pill 24px r7 13px |
| Legend / rule lines | 1.5px dashed rgba(20,22,24,.16) |
Real UI is the subject; blue is the spec layer drawn on top of it.
Browser window. 16px radius, d3 shadow, 40px #F4F4F2 bar, grey dots, centred URL pill. Holds a real screenshot from the case, full section, never cut mid-row.
Pin + leader + callout. Pin 30px r10 blue, Geist Mono 16 white. Leader 1.5px dashed rgba(0,88,246,.6) 4/4. Dot 10px blue with 3px white ring. Callout title 23–26/650, text 18–20 #5C6169.
Part outline (anatomy v2). 1.5px blue border r10, fill rgba(0,88,246,.05), 4px halo rgba(0,88,246,.08), 6–8px padding around the real element. The leader starts on its edge.
Spotlight. Dims only the UI surface: rgba(12,18,34,.30) + brightness .76 + saturate .25, no blur. Holes get a 1.5px white ring and a 20px pin on the corner. 1 hole, 2 at most.

Mobile screen. Real capture (iPhone 13, 390×797), screen only: no bezel, notch or home bar. Hairline + soft shadow, status bar at iOS proportions reading 7:04.
State tags. 30px tall, r8, 7px dot, text + tint from one state. Only when the state is the point.
Every line here came from a round of feedback on the set.
Each card says what the post is, what it is built from and when to reuse it. Click a post for full size.
Source lives in agency\clients\built-for-saas\social-posts; templates in designs-v16.js (set S3) and covers in set S3C.
Pick the pillar and the closest templateWork, Design, Dev, Conversion, Process, Wins, Learn, or a text post. Reuse a template; add a new one only when none fits.
Write the copy from real sourcesHeadline (one idea, ≤ 7 words), subtext (the point or the source, 1–2 lines). Facts only from builtforsaas.com or Sam.
Collect real visualsDesktop: BFS case shots (assets/work). Mobile: Playwright iPhone 13 capture, cookie banners rejected or hidden, never accepted. Chats: Sam's screenshot.
Buildpost(T(pillar, label, headline, "", subtext), inner, areaTop); annotate with spot(), pins or outlines.
Export and QAnode export.mjs S3-<name>, then node qa.mjs S3- must print "QA passed" (text ≥ 16px, nothing clipped, nothing outside the area, header ≥ 24px above it, no placeholder copy).
Look at it at full sizeZoom into small parts: pins, tags, status bar, highlight edges. Fix, re-export, then share one change at a time.
Also at /skill/references/full-spec.md for tools that read a URL. A self-contained brief: paste it into Claude, ChatGPT or any model, add the topic, and attach 1–3 of the post images above as reference.