Files
AISkills/remotion-aspect-ratios/SKILL.md
T
Soroush Asadi aea3b4f800 feat: add FlatRender Remotion template-creation skill suite
10 skills for building high-quality video templates with Remotion/Three.js:
design-styles, character-design, aspect-ratios, template-composition,
sound-effects, music-picker, template-catalog, svg-colors, persian-fonts,
and flatrender-template-seo (category/tags/SEO/related, code-verified).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 18:50:25 +03:30

92 lines
6.2 KiB
Markdown

---
name: remotion-aspect-ratios
description: How to design ONE Remotion template that genuinely fits all three FlatRender aspects — 16:9, 1:1, 9:16 — without text cropping, off-screen elements, or a layout that is really just the 16:9 version letterboxed. Use whenever building or reviewing a template's layout. Read this BEFORE positioning any text or element.
---
# Designing for 16:9 / 1:1 / 9:16 (do this right)
Every template registers in all three aspects (`ASPECTS` in `src/lib/aspect.ts`). A common mistake (made in early FlatRender templates) is to design for 16:9 and just let the same coordinates render in 9:16 — which **crops text, pushes elements off-screen, and looks broken**. A template must be *re-laid-out* per aspect, not scaled.
## Two strategies — responsive component OR per-aspect components
There are TWO legitimate ways to support the three aspects; pick per template:
1. **One responsive component** (default) — a single composition that adapts via `useLayout()` (`isWide/isSquare/isTall`, `pick()`). Use when the design is fundamentally the same and only positions/sizes change. Less code, stays in sync.
2. **A dedicated component per aspect** — when the design must differ STRUCTURALLY (different layout, different scene, different element set), not just reposition. e.g. a cinematic wide hero vs a stacked vertical story vs a centered square badge can be genuinely different scenes.
The registry supports both. In `services/remotion/src/templates.tsx` a `TemplateDef` has `component` (shared default) plus an optional `componentsByAspect` map keyed by aspect id:
```tsx
{
id: "MyTemplate",
component: MyTemplateWide, // fallback for any aspect not overridden
componentsByAspect: {
"1x1": MyTemplateSquare, // dedicated square design
"9x16": MyTemplateTall, // dedicated vertical design
},
schema, durationSec, defaultProps, // SHARED across aspects — keep the editable
} // fields, props and duration identical
```
`Root.tsx` picks `componentsByAspect[aspectId] ?? component`. **Keep `schema`, `defaultProps`, and `durationSec` shared** so the studio shows the same editable fields and the same composition ids (`${id}-${aspect}`) regardless — only the visual layout differs. Reuse shared sub-components (background, characters, text overlay) across the per-aspect files so they don't drift.
Guideline: start with one responsive component; split into per-aspect components only when responsive branching gets gnarly or the designs truly diverge. Don't duplicate three files when `pick()` would do.
## Why the naive approach breaks
`useLayout().vmin(n)` sizes off the SHORT side (1080 in all three aspects), so a `vmin(92)` font is the same pixel size everywhere. But the WIDTH differs hugely: **1920px (16:9) vs 1080px (9:16)**. A headline that fits 1920 wide overflows/crops at 1080 wide. Likewise positioning at `width*0.34` puts an element in a totally different place relative to its own size when width changes.
## The rules
1. **Design 9:16 (tall) first.** It's the tightest. If it fits there, widening to 1:1 and 16:9 is easy. Building 16:9-first guarantees the tall version breaks.
2. **Branch layout on `L.isWide / L.isSquare / L.isTall`** — don't just scale. Things that sit side-by-side in 16:9 should STACK vertically in 9:16:
```tsx
const L = useLayout();
// hero element position differs per aspect
const heroX = L.isTall ? L.width * 0.5 : L.width * 0.34; // centered in tall, left in wide
// layout direction
flexDirection: L.isTall ? "column" : "row"
```
Add a tiny helper to `aspect.ts` and use it everywhere:
```ts
pick: <T,>(wide: T, square: T, tall: T): T =>
kind === "wide" ? wide : kind === "tall" ? tall : square,
```
Then: `fontSize: L.pick(L.vmin(92), L.vmin(84), L.vmin(72))`.
3. **Cap font size to the WIDTH, not just the short side.** Headlines must wrap, never crop. Always set `maxWidth` and let text wrap:
```tsx
maxWidth: L.width * 0.86, // safe text column
// and scale type DOWN in tall:
fontSize: L.pick(L.vmin(90), L.vmin(80), L.vmin(64)),
wordBreak: "normal", lineHeight: 1.15,
```
Test with the LONGEST realistic Persian string for that field, not the short default.
4. **Respect SAFE ZONES.** Keep all meaningful content inside the central safe area; give tall more vertical margin:
- 16:9: ~5% horizontal / 8% vertical padding.
- 9:16: ~8% horizontal, and keep the hero in the middle 60% vertically (top/bottom of phones get UI chrome).
Anchor text blocks to a zone (top third / bottom third), put the hero visual in the center.
5. **Reposition the hero per aspect.** A character/object that's at `x=34%` and text on the right in 16:9 should become hero-centered with text above/below in 1:1 and 9:16. Use `pick()` for x/y and for `justifyContent`/`alignItems`.
6. **Scale element COUNT/spread, not just size.** A row of 5 floating shapes that spans 1920 looks sparse/clipped at 1080 — reduce spread radius or count in tall (`L.pick(...)`).
7. **3D:** adjust `camera.fov` / `position.z` per aspect so the subject fills the frame (a tall frame needs the camera pulled back or a narrower fov). Keep the 2D text overlay using the same `pick()` rules.
## Mandatory verification (the step that was skipped before)
Render a still in **all three aspects** at a frame where text is visible, with a LONG test string, and LOOK at each:
```
npx remotion still src/index.ts "<Comp>-16x9" out/_a.png --frame=NN
npx remotion still src/index.ts "<Comp>-1x1" out/_b.png --frame=NN
npx remotion still src/index.ts "<Comp>-9x16" out/_c.png --frame=NN
```
Reject if: text is clipped at any edge, an element is off-frame, the hero is tiny/huge, or the tall version is obviously "the wide one squished".
## Checklist
- [ ] Designed tall-first; used `pick()`/`isTall` to branch layout (not just scale).
- [ ] Headlines wrap with `maxWidth`; tested with long Persian text — no cropping.
- [ ] Hero repositioned/centered per aspect; content in safe zones.
- [ ] Spread/count adjusted for narrow frames; 3D fov/camera tuned per aspect.
- [ ] Eyeballed stills in ALL THREE aspects.
Related: `remotion-template-composition`, `remotion-design-styles`.