Examples
App elements
Assets and media
Fundamentals
Intents
Design interaction
Drag and drop
Design elements
Localization
Content replacement
App element children
Build composite app elements from multiple child elements.
Running this example
To run this example locally:
-
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.
-
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
. -
Clone the starter kit:
git clone https://github.com/canva-sdks/canva-apps-sdk-starter-kit.gitcd canva-apps-sdk-starter-kitSHELL -
Install dependencies:
npm installSHELL -
Run the example:
npm run start app_element_childrenSHELL -
Click the Preview URL link shown in the terminal to open the example in the Canva editor.
Example app source code
import {Button,FormField,NumberInput,Rows,Text,Title,} from "@canva/app-ui-kit";import type {AppElementRendererOutput,ShapeElementAtPoint,AppElementOptions,} from "@canva/design";import { initAppElement } from "@canva/design";import { useEffect, useState } from "react";import * as styles from "styles/components.css";// The data that will be attached to the app elementtype AppElementData = {rows: number;columns: number;width: number;height: number;spacing: number;rotation: number;};// The state of the user interface. In this example,// we have data representing AppElementData, but it *could* be different.// We also store an update function that can be used to update the app element.type AppElementChangeEvent = {data: AppElementData;update?: (opts: AppElementOptions<AppElementData>) => Promise<void>;};// The default values for the UI components.const initialState: AppElementChangeEvent = {data: {rows: 3,columns: 3,width: 100,height: 100,spacing: 25,rotation: 0,},};const appElementClient = initAppElement<AppElementData>({// This callback runs when the app sets the element's data. It receives// the data and must respond with an array of elements.render: (data) => {const elements: AppElementRendererOutput = [];// For each row and column, create a shape element. The positions of the// elements are offset to ensure that none of them overlap.for (let row = 0; row < data.rows; row++) {for (let column = 0; column < data.columns; column++) {const { width, height, spacing, rotation } = data;const top = row * (height + spacing);const left = column * (width + spacing);const element = createSquareShapeElement({width,height,top,left,rotation,});elements.push(element);}}return elements;},});export const App = () => {const [state, setState] = useState<AppElementChangeEvent>(initialState);const {data: { width, height, rows, columns, spacing, rotation },} = state;const disabled = width < 1 || height < 1 || rows < 1 || columns < 1;// This callback runs when the app element's data is modified or when the// user selects an app element. In both situations, we can use this callback// to update the state of the UI to reflect the latest data.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 app elements can be made up of one ormore elements, and how those elements can be positioned relatively toone another.</Text><Title size="small">Grid</Title><FormFieldlabel="Rows"value={rows}control={(props) => (<NumberInput{...props}min={1}onChange={(value) => {setState((prevState) => {return {...prevState,data: {...prevState.data,rows: Number(value || 0),},};});}}/>)}/><FormFieldlabel="Columns"value={columns}control={(props) => (<NumberInput{...props}min={1}onChange={(value) => {setState((prevState) => {return {...prevState,data: {...prevState.data,columns: Number(value || 0),},};});}}/>)}/><FormFieldlabel="Spacing"value={spacing}control={(props) => (<NumberInput{...props}min={1}onChange={(value) => {setState((prevState) => {return {...prevState,data: {...prevState.data,spacing: Number(value || 0),},};});}}/>)}/><Title size="small">Squares</Title><FormFieldlabel="Width"value={width}control={(props) => (<NumberInput{...props}min={1}onChange={(value) => {setState((prevState) => {return {...prevState,data: {...prevState.data,width: Number(value || 0),},};});}}/>)}/><FormFieldlabel="Height"value={height}control={(props) => (<NumberInput{...props}min={1}onChange={(value) => {setState((prevState) => {return {...prevState,data: {...prevState.data,height: Number(value || 0),},};});}}/>)}/><FormFieldlabel="Rotation"value={rotation}control={(props) => (<NumberInput{...props}min={-180}max={180}onChange={(value) => {setState((prevState) => {return {...prevState,data: {...prevState.data,rotation: Number(value || 0),},};});}}/>)}/><Buttonvariant="primary"stretchonClick={() => {// This method attaches the provided data to the app element,// triggering the `registerRenderAppElement` callback.if (state.update) {state.update({ data: state.data });} else {appElementClient.addElement({ data: state.data });}}}disabled={disabled}>{`${state.update ? "Update" : "Add"} element`}</Button></Rows></div>);};const createSquareShapeElement = ({width,height,top,left,rotation,}: {width: number;height: number;top: number;left: number;rotation: number;}): ShapeElementAtPoint => {return {type: "shape",paths: [{d: `M 0 0 H ${width} V ${height} H 0 L 0 0`,fill: {dropTarget: false,color: "#ff0099",},},],viewBox: {width,height,top: 0,left: 0,},width,height,rotation,top,left,};};
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 element childrenDemonstrates how app elements can contain multiple child elements positioned relative to each other. Creates a customizable grid of square shapes where users can control rows, columns, spacing, and individual element properties.For API reference docs and instructions on running this example, see: https://www.canva.dev/docs/apps/examples/app-element-children/.Related example: See app_image_elements for working with single elements within app elements.**NOTE**: This example differs from what is expected for public apps to pass a Canva review:- Error handling is simplified for demonstration. Production apps must implement comprehensive error handling with clear user feedback and graceful failure modes- Accessibility features are not fully implemented. Production apps must meet WCAG 2.0 AA standards with proper keyboard navigation and ARIA labels- Input validation is minimal for demonstration. Production apps must validate all user inputs and provide clear error messaging- 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?
- Join our Community Forum(opens in a new tab or window)
- Report issues with this example on GitHub(opens in a new tab or window)