Keboola UI Video Architecture
The video system creates feature highlight videos for the Keboola platform using Remotion — a React-based video framework where each frame is a pure function of time.
Core Architecture
VideoScript (data) → Composition (React component) → Remotion Renderer (MP4/WebM)
- Data files define the conversation, timing, and scene structure as TypeScript constants
- Composition components read
useCurrentFrame()and render the correct UI for each frame - Remotion renders each frame independently — there is NO runtime state, NO effects, NO DOM mutations
CRITICAL: Remotion renders frames independently. CSS transitions, useEffect, scrollTop, scrollIntoView, and any DOM mutation APIs DO NOT WORK in rendered video. All animation must be pure functions of useCurrentFrame(). Use interpolate() for linear animations, spring() for physics-based animations, and transform: translateY() for scrolling.
Directory Structure
apps/kai-video/
├── remotion.config.ts # Webpack aliases, Tailwind, output format
├── src/
│ ├── Root.tsx # Composition registration (entry point)
│ ├── index.css # Tailwind imports
│ ├── KaiVideoV9.tsx # Latest composition (scene orchestration)
│ ├── data/
│ │ ├── chatConversation.ts # Base types (ChatMessage, ChatConversation)
│ │ ├── revenueNarrativeConversation.ts # V9 script data + extended types
│ │ └── financeSqlSnippets.ts # SQL code for transformations
│ └── components/
│ ├── v9/ # Latest components using real production code
│ │ ├── ChatPanelV9.tsx
│ │ ├── TransformationEditorV9.tsx
│ │ ├── FlowPreviewV9.tsx
│ │ ├── ToastV9.tsx
│ │ └── index.ts
│ ├── v8/
│ │ └── CursorV8.tsx # Animated cursor with click ripple
│ ├── core/
│ │ └── CenteredStage.tsx # Layout helpers (CenteredStage, CenteredCard)
│ ├── transitions/
│ │ └── ScaleAway.tsx # ScaleIn entrance animation
│ └── ui/
│ └── LogoReveal.tsx # End-card logo animation
Video Specifications
| Property | Value | | ---------------- | ----------------- | | Resolution | 1280 x 720 | | Frame rate | 30 fps | | Typical duration | 60s = 1800 frames | | Format | MP4 (JPEG frames) |
How Remotion Works
Remotion renders React components as video frames. Each frame is a pure render — the component receives frame from useCurrentFrame() and returns the exact visual for that moment.
import { useCurrentFrame, interpolate } from "remotion";
export const MyScene: React.FC = () => {
const frame = useCurrentFrame();
const opacity = interpolate(frame, [0, 30], [0, 1], {
extrapolateRight: "clamp",
});
return <div style={{ opacity }}>Hello</div>;
};
Key Remotion APIs:
useCurrentFrame()— current frame number (0-indexed)useVideoConfig()—{ fps, width, height, durationInFrames }interpolate(value, inputRange, outputRange, options)— linear interpolationspring({ frame, fps, config })— physics-based 0→1 animation<Sequence from={frame}>— delay child rendering<Composition>— registers a renderable video in Root.tsx
Composition Registration (Root.tsx)
Every video must be registered as a <Composition> in Root.tsx:
import { Composition } from "remotion";
import { ThemeProvider } from "@keboola/design";
import { KaiVideoV9, V9_TOTAL_FRAMES, V9_FPS } from "./KaiVideoV9";
export const RemotionRoot: React.FC = () => (
<ThemeProvider>
<Composition
id="KaiVideoV9"
component={KaiVideoV9}
durationInFrames={V9_TOTAL_FRAMES}
fps={V9_FPS}
width={1280}
height={720}
/>
</ThemeProvider>
);
The ThemeProvider wrapper is required for @keboola/design components to render correctly.
Webpack Configuration
remotion.config.ts configures:
- Tailwind CSS via
@remotion/tailwind @kbc-uialias →../kbc-ui/src/scripts(enables importing real kbc-ui components)
Config.overrideWebpackConfig((currentConfig) => {
const tailwindConfig = enableTailwind(currentConfig);
return {
...tailwindConfig,
resolve: {
...tailwindConfig.resolve,
alias: {
...tailwindConfig.resolve?.alias,
"@kbc-ui": path.resolve(process.cwd(), "../kbc-ui/src/scripts"),
},
},
};
});
References
For deeper Remotion API details, see references/remotion-fundamentals.md. For dev environment setup, see references/project-setup.md.
Scan to join WeChat group