On September 25th, 2024, we released v2 of the Apps SDK. To learn what’s new and how to upgrade, see Migration FAQ and Migration guide.

Creating text

How to add text to a user's design.

Text is an essential ingredient in a designer's toolkit, and apps can create and otherwise interact with text in a variety of ways. This page explains everything you need to know about working with text.

Text formats

While using the Apps SDK, text can be represented in either of the following formats:

  • Plaintext
  • Richtext

Apps can choose which format to work with, but the choice affects what features are available. Generally speaking, working with plaintext is simpler, while working with richtext is more flexible.

Plaintext

  • When creating plaintext, formatting can only be applied to the whole text.
  • When reading plaintext, apps can't access the existing formatting information.
  • When updating plaintext, apps can't maintain the styling of the text.

Richtext

  • When creating richtext, different parts of the text can have different formatting.
  • When reading richtext, apps can access the existing formatting information.
  • When updating richtext, apps can maintain the styling of the text.

Text elements

Apps can create text elements that contain text content. Users can then control the position, dimensions, and rotation of these containers.

To create a richtext element, an app must first create a richtext range. It's this range that defines the structure and formatting of the richtext. To learn more, see Richtext ranges.

import { addElementAtPoint, createRichtextRange } from "@canva/design";
const range = createRichtextRange();
range.appendText("Hello world");
await addElementAtPoint({
type: "richtext",
range,
});
TSX
import { addElementAtPoint } from "@canva/design";
await addElementAtPoint({
type: "text",
children: ["Hello world"],
});
TSX

Text content

When text exists in a design, it's known as text content. This content can exist in a variety of containers, including but not limited to:

  • Table cells
  • Text elements

It's important to understand that "content" is not synonymous with "element". This distinction matters because some of the features in the Apps SDK operate on content and not on elements (and vice-versa).

Text formatting

Apps can apply a variety of formatting options to text, including fonts.

In the case of richtext, there are two types of formatting:

  • Paragraph formatting
  • Inline formatting

Paragraph formatting options can only be applied to entire paragraphs, while inline formatting options can be applied to any range of text, including paragraphs. To learn more, see Formatting richtext.

In the case of plaintext, formatting can only be applied to the text as a whole. To view all of the available options, see the API reference.

Paragraphs

Apps can use the \n character to denote the end of a paragraph, creating a line break in the resulting text:

import { createRichtextRange } from "@canva/design";
const range = createRichtextRange();
range.appendText("This is the first paragraph.\nThis is the second paragraph.");
TSX
import { addElementAtPoint } from "@canva/design";
await addElementAtPoint({
type: "text",
children: ["This is the first paragraph.\nThis is the second paragraph."],
});
TSX

Text selection

Apps can listen for the selection of text content in a user's design and then read or update the selected content. For example, an app could analyze and fix the grammar of the selected text content.

When richtext is selected, the text content is available as a richtext range.

import React from "react";
import { Button } from "@canva/app-ui-kit";
import { selection, SelectionEvent } from "@canva/design";
import * as styles from "styles/components.css";
export function App() {
const [currentSelection, setCurrentSelection] = React.useState<
SelectionEvent<"richtext"> | undefined
>();
const isElementSelected = (currentSelection?.count ?? 0) > 0;
React.useEffect(() => {
return selection.registerOnChange({
scope: "richtext",
onChange: setCurrentSelection,
});
}, []);
async function handleClick() {
if (!isElementSelected || !currentSelection) {
return;
}
const draft = await currentSelection.read();
for (const content of draft.contents) {
// Get the text with formatting information
const regions = content.readTextRegions();
for (const region of regions) {
// The plaintext content of a region
console.log(region.text);
// The formatting information of a region
console.log(region.formatting);
}
}
}
return (
<div className={styles.scrollContainer}>
<Button
variant="primary"
disabled={!isElementSelected}
onClick={handleClick}
stretch
>
Read selected richtext content
</Button>
</div>
);
}
TSX

When plaintext is selected, the text content is available as a plain string, without any formatting information.

import React from "react";
import { Button } from "@canva/app-ui-kit";
import { useSelection } from "utils/use_selection_hook"; // https://github.com/canva-sdks/canva-apps-sdk-starter-kit/blob/main/utils/use_selection_hook.ts
import * as styles from "styles/components.css";
export function App() {
const currentSelection = useSelection("plaintext");
const isElementSelected = currentSelection.count > 0;
async function handleClick() {
if (!isElementSelected) {
return;
}
const draft = await currentSelection.read();
for (const content of draft.contents) {
console.log(content.text);
}
}
return (
<div className={styles.scrollContainer}>
<Button
variant="primary"
disabled={!isElementSelected}
onClick={handleClick}
>
Read selected plaintext content
</Button>
</div>
);
}
TSX

To learn more about selection, see:

Richtext ranges

A richtext range is an object that represents a portion of formatted text — anything from a single word to multiple paragraphs — and exposes methods for safely interacting with that range.

Before an app creates a richtext element, it must create a range:

import { createRichtextRange } from "@canva/design";
const range = createRichtextRange();
TSX

By default, this range does not contain any text.

When an app reads existing richtext content, such as when using the Selection API, it receives a range from Canva:

const draft = await currentSelection.read();
for (const content of draft.contents) {
console.log(content); // This is the richtext range object
}
TSX

The same methods are available to all ranges, whether or not they were created by the app.

There is no concept of a "plaintext range" because plaintext is a simple string that can be modified with standard string manipulation techniques.

Creating richtext

Apps can create richtext by appending it to a range:

import { createRichtextRange } from "@canva/design";
const range = createRichtextRange();
range.appendText("Hello world.");
TSX

This text can be formatted with inline formatting options:

import { createRichtextRange } from "@canva/design";
const range = createRichtextRange();
range.appendText("This is bold.", { fontWeight: "bold" });
TSX

If a formatting option isn't specified, its value is inherited from the text that precedes it:

import { createRichtextRange } from "@canva/design";
const range = createRichtextRange();
range.appendText("This is bold. ", { fontWeight: "bold" });
range.appendText("This is also bold.");
TSX

In other words, when text is appended, formatting options stack on top of one another:

import { createRichtextRange } from "@canva/design";
const range = createRichtextRange();
range.appendText("This is bold. ", { fontWeight: "bold" });
range.appendText("This is bold and pink.", { color: "#ff0099" });
range.appendText("This is bold, pink, and italic.", { fontStyle: "italic" });
TSX

Formatting richtext

Apps can format text in a variety of ways.

A limited range of options are available when formatting inline text.

import { addElementAtPoint, createRichtextRange } from "@canva/design";
const range = createRichtextRange();
range.appendText("This is the first paragraph.\nThis is the second paragraph.");
range.formatText({ start: 0, length: 4 }, { fontWeight: "bold" });
TSX

All formatting options are available when formatting paragraphs.

import { addElementAtPoint, createRichtextRange } from "@canva/design";
const range = createRichtextRange();
range.appendText("This is the first paragraph.\nThis is the second paragraph.");
range.formatParagraph({ start: 0, length: 1 }, { textAlign: "center" });
TSX

Be aware that, when formatting paragraphs, any paragraph that overlaps with the specified bounds will be formatted — even if the paragraph only overlaps with a single character.

Reading richtext as plaintext

Apps can access the plaintext representation of richtext content:

import { createRichtextRange } from "@canva/design";
// Create a richtext range
const range = createRichtextRange();
range.appendText("Hello world");
// Get the plaintext representation of the range
const plaintext = range.readPlaintext();
console.log(plaintext); // => "Hello world"
TSX

A common use-case for this behavior is to check if a substring exists before processing the text further:

import { createRichtextRange } from "@canva/design";
// Create a richtext range
const range = createRichtextRange();
range.appendText("Hello world");
// Get the plaintext representation of the range
const plaintext = range.readPlaintext();
// Check if the plaintext contains a string
if (plaintext.includes("Hello")) {
// TODO: Do something here
}
TSX

Reading richtext as text regions

Apps can convert a richtext range into an array of text regions. This array allows the app to inspect how formatting is applied to text throughout the range.

import { createRichtextRange } from "@canva/design";
// Create a richtext range
const range = createRichtextRange();
range.appendText("Hello world");
// Get an array of text regions
const regions = range.readTextRegions();
console.log(regions); // => [{ text: "Hello world" }]
TSX

Each region represents a distinctly formatted string of characters. For example, each of the following would be represented as a separate region:

  • A string without any explicit formatting.
  • A string with bold formatting.
  • A string with bold and italic formatting.

This is demonstrated in the following code snippet:

import { createRichtextRange } from "@canva/design";
// Create a richtext range
const range = createRichtextRange();
range.appendText("First text region. ");
range.appendText("Also the first text region. ");
range.appendText("Second text region. ", { fontWeight: "bold" });
range.appendText("Third text region.", { fontStyle: "italic" });
// Get an array of text regions
const regions = range.readTextRegions();
console.log(regions.length); // => 3
TSX

Gotchas

  • Apps can't set the line-height of text.
  • Apps can't create text with a vertical writing mode.
  • Text elements can't have a predefined height.
  • Font sizes are defined with pixels (px) in the API but displayed as points (pt) in the UI.

API reference