Text clips are rasterized to a Canvas2D surface, uploaded as a GPU texture, and then treated like regular timeline layers for compositing, effects, masks, transforms, and keyframes.
- Text clips are added through the timeline text action and require a video track.
- New text clips default to a 5 second duration.
- The default content is
Enter text. - New 2D text clips default to area text, with a full-composition paragraph box that supports wrapping and hard line breaks.
- New and edited 2D text clips use the active composition resolution for their text canvas. When that resolution changes, the text bounds and legacy box fields are rescaled during the edit.
- Text clips use
src/services/textRenderer.tsto generate their canvas content.
- The clip bar shows a
Ticon and a truncated preview of the text content. - The preview updates when the underlying text changes.
src/components/panels/TextTab.tsx exposes:
- Multi-line text input
- Compact font family selection
- Font-weight preview pills with all standard slots visible; unavailable weights are dark and disabled
- Normal/italic style preview pills
- Font size
- Line height
- Letter spacing
- Fill color
- Stroke enable toggle, stroke color, and stroke width
- Horizontal alignment
- Vertical alignment
- Area Text toggle with box X/Y and width/height controls
- Shadow enable toggle, shadow color, shadow offsets, and shadow blur
Text content updates are debounced briefly so typing stays responsive.
Font changes trigger async font loading through googleFontsService.
The ordinary Text tab shares the same compact typography controls as Dynamic
Captions without exposing any caption-specific source, timing, line, or word-
highlight settings.
Area-text bounds can be keyframed from the Area Text section.
Text content can print a live number that changes every frame, so counters and speed readouts need one clip instead of many static ones.
| Token | Output |
|---|---|
{value} |
Value with up to 2 decimals, trailing zeros trimmed (6.83, 1) |
{value:N} |
Value with exactly N decimals (0-6), e.g. {value:2} → 1.00 |
{value*K:N} |
Value scaled by K first, e.g. {value*100:0}% → 683% |
{time} / {time:N} |
Clip-local seconds |
Unknown {...} text stays literal. The Content section shows:
- Value: a keyframeable number (
text.value) with slider, numeric field and stopwatch; animate it like any other property for a counting number. - Follow: optionally drive Value from another clip's numeric property at
the same timeline time (
valueLink: { clipId, property }), e.g. a video'sspeedto show its speed ramp live, oropacity/position.x. When a link is set, the Property picker lists that clip's animatable numeric properties and the own Value row is hidden. A missing linked clip falls back to Value.
Formatting happens in src/services/text/textValueTemplate.ts; per-frame
resolution (own keyframes or link) happens in renderTextFrame
(src/services/text/textFrameRuntime.ts + textValueLink.ts), which the
preview, nested compositions, node previews, and export share. A template clip
re-rasterizes only when the formatted string or its styling changes. Static
rasters (thumbnails, inactive compositions) print the base Value.
When a 2D text clip is selected and the preview is in Edit mode, the preview shows an AE-style text bounds editor over the rendered text.
- Click the active text bounds to type directly in the preview.
- Drag in empty preview space to define a new paragraph bounds rectangle for the selected text clip.
- Drag red vertices to reshape the text bounds.
- Hold Shift while creating, moving, or resizing bounds to snap to the source canvas edges and center guides.
- Hold Ctrl or Command while dragging to move the whole text bounds path.
- The bounds are stored as
textBoundsusing mask-style vertices plus legacyboxX,boxY,boxWidth, andboxHeightfallback values. - Preview text editing is disabled during playback, source monitor, scene navigation, and mask navigation.
src/services/textRenderer.ts renders text with Canvas2D and supports:
- Multi-line text
- Area text wrapping and clipping inside the paragraph bounds
- Shape-aware line wrapping for slanted text bounds, using the available polygon width at each line's Y position
- Left, center, and right alignment
- Top, middle, and bottom vertical alignment
- Letter spacing
- Stroke outlines
- Shadows
- Text-on-path rendering through
pathEnabledandpathPoints
Text inside nested compositions is render-only, so its raster keeps only the pixels the text covers (measured glyph, outline, and shadow bounds plus a small margin) instead of a composition-sized canvas. src/services/text/textCanvasFrameRegistry.ts records where that crop sits in the composition-sized source; the compositor samples it through a texture rect, and passes that read a whole source (effects, color correction, edge fill, 3D planes, worker previews) get a composition-sized copy. Path text, captions, and text covering most of the frame stay full-size. A project with hundreds of nested labels therefore no longer holds gigabytes of canvas and GPU memory.
Static text uploads to the GPU only when its raster changes, and animated text stops re-rasterizing once its keyframes hold.
The AI tool surface can create and update editable text clips, set an area-text box, inspect text properties, add text-bounds keyframes, and supply pathEnabled and pathPoints.
src/services/googleFontsService.ts exposes 50 Google Font families across:
- Sans-serif
- Serif
- Display
- Handwriting
- Monospace
Fonts are loaded by injecting Google Fonts CSS and waiting on document.fonts.load(...).
Text clips persist their text properties. On load, the text canvas is recreated from those properties at the active composition resolution.
Relevant files:
src/stores/timeline/textClipSlice.tssrc/stores/timeline/constants.tssrc/services/textRenderer.tssrc/services/textLayout.tssrc/components/panels/TextTab.tsxsrc/components/preview/TextPreviewEditor.tsxsrc/services/aiTools/definitions/text.tssrc/services/aiTools/handlers/text.tssrc/types/index.ts