Drag and drop
Something that Canva users love is the ability to drag and drop content users straight into their designs. Apps can use the Apps SDK to support this behavior, ensuring that the user experience is delightfully consistent.
Our design guidelines help you create a high-quality app that easily passes app review.
Content types
Apps can enable drag and drop for various types of content, including:
Additional content types may be supported in the future.
Rendering the UI
Apps need to render something in their UI that can be dragged.
This can be as simple as an HTMLDivElement
with a draggable
attribute:
<div draggable>This text can be dragged.</div>
In the App UI Kit though, we've also provided a number of card components that are designed to work seamlessly with drag and drop. The components include:
AudioCard
EmbedCard
ImageCard
TypographyCard
VideoCard
To see how to use these components, see Code samples.
Handling drag events
Apps can handle drag events by registering an onDragStart
callback and passing the drag event into either of the following methods:
startDragToPoint
startDragToCursor
Both methods add content to the user's design, but:
startDragToPoint
is only compatible with design types that support absolute positions, which is all design types except for documents.startDragToCursor
is only compatible with design types that contain streams of text, which is only the document design type.
The supported content types also depend on the method being called:
Content Type | startDragToPoint | startDragToCursor |
---|---|---|
Audio tracks | ✅ | ❌ |
Embeds | ✅ | ✅ |
Images | ✅ | ✅ |
Text | ✅ | ❌ |
Videos | ✅ | ✅ |
Where possible, apps should determine the context in which the app is running and either call the compatible method or make it obvious when functionality isn't available. To learn more, see Feature support.
Handling clicks
For accessibility reasons, drag and drop should not be the only way that users can add content to a design. Apps should also support click events. This behavior is built into the App UI Kit components and demonstrated below.
Code samples
import React from "react";import { AudioCard, AudioContextProvider, Rows } from "@canva/app-ui-kit";import { upload } from "@canva/asset";import { addAudioTrack, ui } from "@canva/design";import { useFeatureSupport } from "utils/use_feature_support"; // https://github.com/canva-sdks/canva-apps-sdk-starter-kit/blob/main/utils/use_feature_support.tsimport * as styles from "styles/components.css";export function App() {const isSupported = useFeatureSupport();async function handleClick() {if (isSupported(addAudioTrack)) {const asset = await upload({type: "audio",title: "Example audio",mimeType: "audio/mp3",url: "https://www.canva.dev/example-assets/audio-import/audio.mp3",aiDisclosure: "none",});addAudioTrack({ref: asset.ref,});}}function handleDragStart(event: React.DragEvent<HTMLElement>) {if (isSupported(ui.startDragToPoint)) {ui.startDragToPoint(event, {type: "audio",title: "Example audio",resolveAudioRef: () => {return upload({type: "audio",title: "Example audio",mimeType: "audio/mp3",url: "https://www.canva.dev/example-assets/audio-import/audio.mp3",aiDisclosure: "none",});},});}if (isSupported(ui.startDragToCursor)) {ui.startDragToCursor(event, {type: "audio",title: "Example audio",resolveAudioRef: () => {return upload({type: "audio",title: "Example audio",mimeType: "audio/mp3",url: "https://www.canva.dev/example-assets/audio-import/audio.mp3",aiDisclosure: "none",});},});}}return (<div className={styles.scrollContainer}><Rows spacing="1u">{/* A single `AudioContextProvider` component must be an ancestor to all `AudioCard` components*/}<AudioContextProvider><AudioCardtitle="Example audio"audioPreviewUrl="https://www.canva.dev/example-assets/audio-import/audio.mp3"durationInSeconds={86}onClick={handleClick}onDragStart={handleDragStart}/></AudioContextProvider></Rows></div>);}
import React from "react";import { EmbedCard, Rows } from "@canva/app-ui-kit";import { addElementAtCursor, addElementAtPoint, ui } from "@canva/design";import { useFeatureSupport } from "utils/use_feature_support"; // https://github.com/canva-sdks/canva-apps-sdk-starter-kit/blob/main/utils/use_feature_support.tsimport * as styles from "styles/components.css";export function App() {const isSupported = useFeatureSupport();function handleClick() {if (isSupported(addElementAtPoint)) {addElementAtPoint({type: "embed",url: "https://www.youtube.com/embed/L3MtFGWRXAA",});}if (isSupported(addElementAtCursor)) {addElementAtCursor({type: "embed",url: "https://www.youtube.com/embed/L3MtFGWRXAA",});}}function handleDragStart(event: React.DragEvent<HTMLElement>) {if (isSupported(ui.startDragToPoint)) {ui.startDragToPoint(event, {type: "embed",embedUrl: "https://www.youtube.com/embed/L3MtFGWRXAA",previewSize: { width: 300, height: 200 },previewUrl: "https://www.canva.dev/example-assets/images/puppyhood.jpg",});}if (isSupported(ui.startDragToCursor)) {ui.startDragToCursor(event, {type: "embed",embedUrl: "https://www.youtube.com/embed/L3MtFGWRXAA",previewSize: { width: 300, height: 200 },previewUrl: "https://www.canva.dev/example-assets/images/puppyhood.jpg",});}}return (<div className={styles.scrollContainer}><Rows spacing="1u"><EmbedCardtitle="Heartwarming Chatter: Adorable Conversation with a Puppy"description="Puppyhood"ariaLabel="Add embed to design"thumbnailUrl="https://www.canva.dev/example-assets/images/puppyhood.jpg"onClick={handleClick}onDragStart={handleDragStart}/></Rows></div>);}
import React from "react";import { ImageCard, Rows } from "@canva/app-ui-kit";import { upload } from "@canva/asset";import { addElementAtCursor, addElementAtPoint, ui } from "@canva/design";import { useFeatureSupport } from "utils/use_feature_support"; // https://github.com/canva-sdks/canva-apps-sdk-starter-kit/blob/main/utils/use_feature_support.tsimport * as styles from "styles/components.css";export function App() {const isSupported = useFeatureSupport();async function handleClick() {if (isSupported(addElementAtPoint)) {const asset = await upload({mimeType: "image/jpeg",thumbnailUrl:"https://www.canva.dev/example-assets/image-import/grass-image-thumbnail.jpg",type: "image",url: "https://www.canva.dev/example-assets/image-import/grass-image.jpg",width: 320,height: 212,altText: {text: "Example grass image",decorative: false},aiDisclosure: "none",});addElementAtPoint({type: "image",ref: asset.ref,altText: {text: "Example grass image",decorative: false},});}if (isSupported(addElementAtCursor)) {const asset = await upload({mimeType: "image/jpeg",thumbnailUrl:"https://www.canva.dev/example-assets/image-import/grass-image-thumbnail.jpg",type: "image",url: "https://www.canva.dev/example-assets/image-import/grass-image.jpg",width: 320,height: 212,altText: {text: "Example grass image",decorative: false},aiDisclosure: "none",});addElementAtCursor({type: "image",ref: asset.ref,altText: {text: "Example grass image",decorative: false},});}}function handleDragStart(event: React.DragEvent<HTMLElement>) {if (isSupported(ui.startDragToPoint)) {ui.startDragToPoint(event, {type: "image",resolveImageRef: () => {return upload({mimeType: "image/jpeg",thumbnailUrl:"https://www.canva.dev/example-assets/image-import/grass-image-thumbnail.jpg",type: "image",url: "https://www.canva.dev/example-assets/image-import/grass-image.jpg",width: 320,height: 212,altText: {text: "Example grass image",decorative: false},aiDisclosure: "none",});},previewUrl:"https://www.canva.dev/example-assets/image-import/grass-image.jpg",previewSize: {width: 320,height: 212,},fullSize: {width: 320,height: 212,},altText: {text: "Example grass image",decorative: false},});}if (isSupported(ui.startDragToCursor)) {ui.startDragToCursor(event, {type: "image",resolveImageRef: () => {return upload({mimeType: "image/jpeg",thumbnailUrl:"https://www.canva.dev/example-assets/image-import/grass-image-thumbnail.jpg",type: "image",url: "https://www.canva.dev/example-assets/image-import/grass-image.jpg",width: 320,height: 212,altText: {text: "Example grass image",decorative: false},aiDisclosure: "none",});},previewUrl:"https://www.canva.dev/example-assets/image-import/grass-image.jpg",previewSize: {width: 320,height: 212,},fullSize: {width: 320,height: 212,},altText: {text: "Example grass image",decorative: false},});}}return (<div className={styles.scrollContainer}><Rows spacing="1u"><ImageCardariaLabel="Add image to design"alt="Grass image"thumbnailUrl="https://www.canva.dev/example-assets/image-import/grass-image-thumbnail.jpg"onDragStart={handleDragStart}onClick={handleClick}/></Rows></div>);}
import React from "react";import { Rows, Text, TypographyCard } from "@canva/app-ui-kit";import { addElementAtCursor, addElementAtPoint, ui } from "@canva/design";import { useFeatureSupport } from "utils/use_feature_support"; // https://github.com/canva-sdks/canva-apps-sdk-starter-kit/blob/main/utils/use_feature_support.tsimport * as styles from "styles/components.css";export function App() {const isSupported = useFeatureSupport();function handleClick() {if (isSupported(addElementAtPoint)) {addElementAtPoint({type: "text",children: ["This is some text"],});}if (isSupported(addElementAtCursor)) {addElementAtCursor({type: "text",children: ["This is some text"],});}}function handleDragStart(event: React.DragEvent<HTMLElement>) {if (isSupported(ui.startDragToPoint)) {ui.startDragToPoint(event, {type: "text",children: ["This is some text"],});}if (isSupported(ui.startDragToCursor)) {ui.startDragToCursor(event, {type: "text",children: ["This is some text"],});}}return (<div className={styles.scrollContainer}><Rows spacing="1u"><TypographyCardariaLabel="Hello world"onClick={handleClick}onDragStart={handleDragStart}><Text>This is some text</Text></TypographyCard></Rows></div>);}
import React from "react";import { Rows, VideoCard } from "@canva/app-ui-kit";import { upload } from "@canva/asset";import { addElementAtCursor, addElementAtPoint, ui } from "@canva/design";import { useFeatureSupport } from "utils/use_feature_support"; // https://github.com/canva-sdks/canva-apps-sdk-starter-kit/blob/main/utils/use_feature_support.tsimport * as styles from "styles/components.css";export function App() {const isSupported = useFeatureSupport();async function handleClick() {if (isSupported(addElementAtPoint)) {const asset = await upload({mimeType: "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",type: "video",url: "https://www.canva.dev/example-assets/video-import/beach-video.mp4",width: 320,height: 180,aiDisclosure: "none",});addElementAtPoint({type: "video",ref: asset.ref,altText: {text: "Example video",decorative: false},});}if (isSupported(addElementAtCursor)) {const asset = await upload({mimeType: "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",type: "video",url: "https://www.canva.dev/example-assets/video-import/beach-video.mp4",width: 320,height: 180,aiDisclosure: "none",});addElementAtCursor({type: "video",ref: asset.ref,altText: {text: "Example video",decorative: false},});}}function handleDragStart(event: React.DragEvent<HTMLElement>) {if (isSupported(ui.startDragToPoint)) {ui.startDragToPoint(event, {type: "video",resolveVideoRef: () => {return upload({mimeType: "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",type: "video",url: "https://www.canva.dev/example-assets/video-import/beach-video.mp4",width: 320,height: 180,aiDisclosure: "none",});},previewSize: {width: 320,height: 180,},previewUrl:"https://www.canva.dev/example-assets/video-import/beach-thumbnail-image.jpg",altText: {text: "Example video",decorative: false},});}if (isSupported(ui.startDragToCursor)) {ui.startDragToCursor(event, {type: "video",resolveVideoRef: () => {return upload({mimeType: "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",type: "video",url: "https://www.canva.dev/example-assets/video-import/beach-video.mp4",width: 320,height: 180,aiDisclosure: "none",});},previewSize: {width: 320,height: 180,},previewUrl:"https://www.canva.dev/example-assets/video-import/beach-thumbnail-image.jpg",altText: {text: "Example video",decorative: false},});}}return (<div className={styles.scrollContainer}><Rows spacing="1u"><VideoCardariaLabel="Add video to design"thumbnailUrl="https://www.canva.dev/example-assets/video-import/beach-thumbnail-image.jpg"videoPreviewUrl="https://www.canva.dev/example-assets/video-import/beach-thumbnail-video.mp4"durationInSeconds={7}mimeType="video/mp4"onDragStart={handleDragStart}onClick={handleClick}/></Rows></div>);}