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

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:

  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:

{
  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:

    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:

    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:

    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.