- .claude/skills/flat-artist: the bundled FlatRender template-creation suite (orchestrator + 16 sub-skills + design/motion R&D), mirrors the Gitea AISkills repo. - services/remotion Root.tsx/templates.tsx: register the 3D templates + Three3DTest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.2 KiB
name, description
| name | description |
|---|---|
| remotion-aspect-ratios | 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:
-
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. -
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:
{
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
-
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.
-
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: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.tsand use it everywhere: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)). -
Cap font size to the WIDTH, not just the short side. Headlines must wrap, never crop. Always set
maxWidthand let text wrap: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.
-
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.
-
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. Usepick()for x/y and forjustifyContent/alignItems. -
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(...)). -
3D: adjust
camera.fov/position.zper 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 samepick()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()/isTallto 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/SKILL.md, ../remotion-design-styles/SKILL.md.