App video elements

Create video elements inside app elements.

Running this example

To run this example locally:

  1. If you haven't already, create a new app in the Developer Portal(opens in a new tab or window). For more information, refer to our Quickstart guide.

  2. In your app's configuration on the Developer Portal(opens in a new tab or window), ensure the "Development URL" is set to http://localhost:8080.

  3. Clone the starter kit:

    git clone https://github.com/canva-sdks/canva-apps-sdk-starter-kit.git
    cd canva-apps-sdk-starter-kit
    SHELL
  4. Install dependencies:

    npm install
    SHELL
  5. Run the example:

    npm run start app_video_elements
    SHELL
  6. Click the Preview URL link shown in the terminal to open the example in the Canva editor.

Example app source code

// For usage information, see the README.md file.
import {
Box,
Button,
FormField,
Grid,
VideoCard,
NumberInput,
Rows,
Text,
} from "@canva/app-ui-kit";
import { type AppElementOptions, initAppElement } from "@canva/design";
import React from "react";
import * as styles from "styles/components.css";
import { upload } from "@canva/asset";
type AppElementData = {
title: string;
videoId: string;
width: number;
height: number;
rotation: number;
};
type AppElementChangeEvent = {
data: AppElementData;
update?: (opts: AppElementOptions<AppElementData>) => Promise<void>;
};
const videos = {
building: {
title: "Pinwheel on building",
url: "https://www.canva.dev/example-assets/video-import/video.mp4",
thumbnailImageUrl:
"https://www.canva.dev/example-assets/video-import/thumbnail-image.jpg",
thumbnailVideoUrl:
"https://www.canva.dev/example-assets/video-import/thumbnail-video.mp4",
width: 405,
height: 720,
videoRef: undefined,
},
beach: {
title: "A beautiful beach scene",
url: "https://www.canva.dev/example-assets/video-import/beach-video.mp4",
thumbnailImageUrl:
"https://www.canva.dev/example-assets/video-import/beach-thumbnail-image.jpg",
thumbnailVideoUrl:
"https://www.canva.dev/example-assets/video-import/beach-thumbnail-video.mp4",
width: 320,
height: 180,
videoRef: undefined,
},
};
const initialState: AppElementChangeEvent = {
data: {
title: "Pinwheel on building",
videoId: "building",
width: 405,
height: 720,
rotation: 0,
},
};
const appElementClient = initAppElement<AppElementData>({
render: (data) => {
return [
{
type: "video",
top: 0,
left: 0,
altText: {
text: `a video of ${data.title}`,
decorative: undefined,
},
ref: videos[data.videoId].videoRef,
...data,
},
];
},
});
export const App = () => {
const [loading, setLoading] = React.useState(false);
const [state, setState] = React.useState<AppElementChangeEvent>(initialState);
const {
data: { videoId, width, height, rotation },
} = state;
const disabled = loading || !videoId || videoId.trim().length < 1;
const items = Object.entries(videos).map(([key, value]) => {
const { title, thumbnailImageUrl, thumbnailVideoUrl, width, height } =
value;
return {
key,
title,
thumbnailImageUrl,
thumbnailVideoUrl,
active: videoId === key,
onClick: () => {
setState((prevState) => {
return {
...prevState,
data: {
...prevState.data,
videoId: key,
width,
height,
},
};
});
},
};
});
const addOrUpdateVideo = React.useCallback(async () => {
setLoading(true);
try {
if (!videos[state.data.videoId].videoRef) {
const item = videos[state.data.videoId];
const { ref } = await upload({
type: "video",
mimeType: "video/mp4",
url: item.url,
thumbnailImageUrl: item.thumbnailImageUrl,
thumbnailVideoUrl: item.thumbnailVideoUrl,
aiDisclosure: "none",
});
videos[state.data.videoId].videoRef = ref;
}
// Add or update app element
if (state.update) {
state.update({ data: state.data });
} else {
appElementClient.addElement({ data: state.data });
}
} finally {
setLoading(false);
}
}, [state]);
React.useEffect(() => {
appElementClient.registerOnElementChange((appElement) => {
setState(
appElement
? {
data: appElement.data,
update: appElement.update,
}
: initialState,
);
});
}, []);
return (
<div className={styles.scrollContainer}>
<Rows spacing="2u">
<Text>
This example demonstrates how apps can create video elements inside
app elements. Using an app element makes the video element re-editable
and lets apps control additional properties, such as the width and
height.
</Text>
<FormField
label="Select a video"
control={(props) => (
<Box {...props} padding="1u">
<Grid columns={2} spacing="1.5u">
{items.map((item) => (
<VideoCard
ariaLabel={item.title}
mimeType="video/mp4"
key={item.key}
thumbnailUrl={item.thumbnailImageUrl}
videoPreviewUrl={item.thumbnailVideoUrl}
onClick={item.onClick}
selectable={true}
selected={item.active}
borderRadius="standard"
thumbnailHeight={150}
/>
))}
</Grid>
</Box>
)}
/>
<FormField
label="Width"
value={width}
control={(props) => (
<NumberInput
{...props}
min={1}
onChange={(value) => {
setState((prevState) => {
return {
...prevState,
data: {
...prevState.data,
width: value || 0,
},
};
});
}}
/>
)}
/>
<FormField
label="Height"
value={height}
control={(props) => (
<NumberInput
{...props}
min={1}
onChange={(value) => {
setState((prevState) => {
return {
...prevState,
data: {
...prevState.data,
height: value || 0,
},
};
});
}}
/>
)}
/>
<FormField
label="Rotation"
value={rotation}
control={(props) => (
<NumberInput
{...props}
min={-180}
max={180}
onChange={(value) => {
setState((prevState) => {
return {
...prevState,
data: {
...prevState.data,
rotation: value || 0,
},
};
});
}}
/>
)}
/>
<Button
variant="primary"
onClick={addOrUpdateVideo}
disabled={disabled}
stretch
>
{`${state.update ? "Update" : "Add"} video`}
</Button>
</Rows>
</div>
);
};
TYPESCRIPT
import { AppUiProvider } from "@canva/app-ui-kit";
import { createRoot } from "react-dom/client";
import { App } from "./app";
import "@canva/app-ui-kit/styles.css";
const root = createRoot(document.getElementById("root") as Element);
function render() {
root.render(
<AppUiProvider>
<App />
</AppUiProvider>,
);
}
render();
if (module.hot) {
module.hot.accept("./app", render);
}
TYPESCRIPT
# App video elements
Demonstrates how to create video elements inside app elements, making them re-editable with controllable properties like dimensions and rotation. Users can select from predefined videos and customize their appearance.
For API reference docs and instructions on running this example, see: https://www.canva.dev/docs/apps/examples/app-video-elements/.
Related examples: See app_image_elements for images within app elements, or design_elements/video_elements for direct video insertion.
NOTE: This example differs from what is expected for public apps to pass a Canva review:
- Static video URLs are used for demonstration purposes only. Production apps should host videos on a CDN/hosting service and use the `upload` function from the `@canva/asset` package for user uploads
- Error handling is simplified for demonstration. Production apps must implement comprehensive error handling with clear user feedback and graceful failure modes
- Internationalization is not implemented. Production apps must support multiple languages using the `@canva/app-i18n-kit` package to pass Canva review requirements
MARKDOWN

API Reference

Need Help?