> ## Documentation Index > Fetch the complete documentation index at: https://www.canva.dev/docs/apps/llms.txt > Use this file to discover all available pages before exploring further. # Masonry Masonry layout implementation using the App UI Kit. ## Running this example To run this example locally: 1. If you haven't already, create a new app in the [Developer Portal](https://www.canva.com/developers/apps). For more information, refer to our [Quickstart guide](https://www.canva.dev/docs/apps/quickstart/). 2. In your app's configuration on the [Developer Portal](https://www.canva.com/developers/apps), ensure the "Development URL" is set to `http://localhost:8080`. 3. Clone the starter kit: ```shell git clone https://github.com/canva-sdks/canva-apps-sdk-starter-kit.git cd canva-apps-sdk-starter-kit ``` 4. Install dependencies: ```shell npm install ``` 5. Run the example: ```shell npm run start:example masonry ``` 6. Click the **Preview URL** link shown in the terminal to open the example in the Canva editor. ## Example app source code ```typescript // For usage information, see the README.md file. import { Masonry, MasonryItem, ImageCard, Rows, Text, Placeholder, } from "@canva/app-ui-kit"; import { useState, useRef, useEffect, useCallback } from "react"; import * as styles from "styles/components.css"; import type { QueuedImage } from "@canva/asset"; import { upload } from "@canva/asset"; import type { ImageDragConfig } from "@canva/design"; import { addElementAtCursor, addElementAtPoint, ui } from "@canva/design"; import type { Image } from "./fake_api"; import { getImages } from "./fake_api"; import InfiniteScroll from "react-infinite-scroller"; import { generatePlaceholders } from "./utils"; import { useFeatureSupport } from "@canva/app-hooks"; const TARGET_ROW_HEIGHT_PX = 100; const NUM_PLACEHOLDERS = 10; const uploadImage = async (image: Image): Promise => { // Use Canva's upload API to prepare images for use in designs // This creates a QueuedImage that can be referenced when adding elements to the design const queuedImage = await upload({ type: "image", mimeType: image.url.endsWith(".png") ? "image/png" : "image/jpeg", url: image.url, thumbnailUrl: image.url, width: image.width, height: image.height, aiDisclosure: "none", // Indicates no AI was used to generate this image }); return queuedImage; }; export const Placeholders = generatePlaceholders({ numPlaceholders: NUM_PLACEHOLDERS, height: TARGET_ROW_HEIGHT_PX, }).map((placeholder, index) => ( )); export const App = () => { const [images, setImages] = useState([]); const [isFetching, setIsFetching] = useState(false); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const isSupported = useFeatureSupport(); const addElement = [addElementAtPoint, addElementAtCursor].find((fn) => isSupported(fn), ); const scrollContainerRef = useRef(null); const fetchImages = useCallback(async () => { if (isFetching || !page || !hasMore) { return; } setIsFetching(true); try { const { images: newImages, nextPage } = await getImages(page); setImages((prevImages) => [...prevImages, ...newImages]); setPage(nextPage); setHasMore(nextPage != null); } finally { setIsFetching(false); } }, [isFetching, page, hasMore]); useEffect(() => { fetchImages(); // Only run once on mount }, []); const addImageToDesign = async (image: Image) => { if (!addElement) { return; } // Upload image to Canva's asset system first const queuedImage = await uploadImage(image); // Add the uploaded image as an element to the user's design await addElement({ type: "image", ref: queuedImage.ref, // Reference to the uploaded image asset altText: { text: "an example image", decorative: undefined, }, }); }; const onDragStart = async ( event: React.DragEvent, image: Image, ) => { // Configure drag-and-drop data for Canva's design surface const dragData: ImageDragConfig = { type: "image", resolveImageRef: () => uploadImage(image), // Lazy upload when drag is completed // Our mock API doesn't return a thumbnail/preview image, but for a production app // you should use real lower resolution thumbnail/preview images previewUrl: image.url, previewSize: { width: image.width, height: image.height, }, fullSize: { width: image.width, height: image.height, }, }; // Use feature detection to support different Canva editor versions: // - startDragToPoint: For fixed designs (posters, social media) that use coordinate-based positioning // - startDragToCursor: For responsive documents (presentations, docs) that slot content into text flow if (isSupported(ui.startDragToPoint)) { ui.startDragToPoint(event, dragData); } else if (isSupported(ui.startDragToCursor)) { ui.startDragToCursor(event, dragData); } }; const Images = images.map((image, index) => ( addImageToDesign(image)} disabled={!addElement} thumbnailUrl={image.url} alt={image.title} onDragStart={(event: React.DragEvent) => onDragStart(event, image) } /> )); return (
This example demonstrates how apps can use the Masonry component from the App UI Kit with static example images. scrollContainerRef.current} > {[...Images, ...(isFetching ? Placeholders : [])]}
); }; ```
```typescript export type Image = { title: string; url: string; height: number; width: number; }; export type PaginatedResponse = { nextPage?: number; pageCount: number; images: Image[]; }; // Static images from Canva example assets // In a real app, you would fetch images from your CDN/database instead of using static data const STATIC_IMAGES: Image[] = [ { title: "Bee", url: "https://www.canva.dev/example-assets/images/bee.png", width: 640, height: 640, }, { title: "Dolphin", url: "https://www.canva.dev/example-assets/images/dolphin.jpg", width: 640, height: 640, }, { title: "Canva Logo", url: "https://www.canva.dev/example-assets/images/logo.png", width: 240, height: 240, }, { title: "Puppy", url: "https://www.canva.dev/example-assets/images/puppyhood.jpg", width: 400, height: 300, }, ]; const generateImages = (numImages: number) => { // Create variations of the static images to fill the requested number // In a real app, this function wouldn't be needed - you'd get actual image data from your API const images: Image[] = []; for (let i = 0; i < numImages; i++) { const baseImage = STATIC_IMAGES[i % STATIC_IMAGES.length]; if (baseImage) { images.push({ ...baseImage, title: `${baseImage.title} ${Math.floor(i / STATIC_IMAGES.length) + 1}`, }); } } return images; }; // Paginated api example to demo infinite scrolling. // Uses static Canva example images instead of external API. // In a real app, replace this with actual API calls to your image service export const getImages = async (page: number): Promise => { // Wait 1 second to simulate a fetch request. // In a real app, this would be something like const response = await fetch(`/api/images?page=${page}`) await new Promise((res) => setTimeout(res, 1000)); const imagesPerPage = 10; const totalImages = 50; const totalPages = Math.ceil(totalImages / imagesPerPage); const startIndex = (page - 1) * imagesPerPage; const endIndex = startIndex + imagesPerPage; // In a real app, you would make an API call here and get back the paginated results // For example: const { data: pageImages, hasMore } = await apiClient.getImages({ page, limit: imagesPerPage }) const allImages = generateImages(totalImages); const pageImages = allImages.slice(startIndex, endIndex); return { pageCount: totalPages, nextPage: page < totalPages ? page + 1 : undefined, images: pageImages, }; }; ``` ```typescript // For usage information, see the README.md file. import { AppUiProvider } from "@canva/app-ui-kit"; import { createRoot } from "react-dom/client"; import { App } from "./app"; import "@canva/app-ui-kit/styles.css"; import type { DesignEditorIntent } from "@canva/intents/design"; import { prepareDesignEditor } from "@canva/intents/design"; async function render() { const root = createRoot(document.getElementById("root") as Element); root.render( , ); } const designEditor: DesignEditorIntent = { render }; prepareDesignEditor(designEditor); // Hot Module Replacement for development (automatically reloads the app when changes are made) if (module.hot) { module.hot.accept("./app", render); } ``` ```typescript type Placeholder = { height: number; width: number; }; export function generatePlaceholders({ numPlaceholders, height, }: { numPlaceholders: number; height: number; }): Placeholder[] { return Array.from({ length: numPlaceholders }, (_, i) => { // generate images such that width is 1-1.5x the height const width = Math.floor(height * (Math.random() * 0.5 + 1)); return { height, width, }; }); } ``` ```markdown # UI masonry layout Demonstrates how to implement a masonry grid layout for displaying images with drag-and-drop functionality and infinite scrolling. Shows advanced UI patterns for browsing and selecting assets. For API reference docs and instructions on running this example, see: . Related examples: See drag_and_drop/drag_and_drop_image for image drag-and-drop, or assets_and_media/digital_asset_management for asset browsing patterns. NOTE: This example differs from what is expected for public apps to pass a Canva review: - Mock data and static assets are used for demonstration purposes only. Production apps should host assets on a CDN/hosting service and use the `upload` function from the `@canva/asset` package - Error handling is simplified for demonstration. Production apps must implement comprehensive error handling with clear user feedback and graceful failure modes - Image optimization and loading states are not implemented. Production apps should optimize images and provide loading feedback for better user experience - Internationalization is not implemented. Production apps must support multiple languages using the `@canva/app-i18n-kit` package to pass Canva review requirements - The code structure is simplified: Production apps using [intents](https://www.canva.dev/docs/apps/intents/) are recommended to call the prepareDesignEditor function from src/intents/design_editor/index.tsx ```
## API reference * [App UI Kit](https://www.canva.dev/docs/apps/app-ui-kit/) * [`addElementAtCursor`](https://www.canva.dev/docs/apps/api/latest/design-add-element-at-cursor/) * [`addElementAtPoint`](https://www.canva.dev/docs/apps/api/latest/design-add-element-at-point/) * [`prepareDesignEditor`](https://www.canva.dev/docs/apps/api/latest/intents-design-prepare-design-editor/) * [`ui.startDragToCursor`](https://www.canva.dev/docs/apps/api/latest/design-ui-start-drag-to-cursor/) * [`ui.startDragToPoint`](https://www.canva.dev/docs/apps/api/latest/design-ui-start-drag-to-point/) * [`upload`](https://www.canva.dev/docs/apps/api/latest/asset-upload/) ## Need help? * Join our [Community Forum](https://community.canva.dev/) * Report issues with this example on [GitHub](https://github.com/canva-sdks/canva-apps-sdk-starter-kit/issues)