Text formatting

Lists

Try it in the demo →

A list item is a paragraph with a marker in front of it. The marker can be a bullet, a number, any character such as a symbol, a letter or an emoji, or a vector shape with a mark inside it, such as a white check in a colored circle. Lists are for menus, feature lists and steps, and people edit them with the keys they already know.

Make a list

// Konva: const rt = enableRichText(stage)
rt.manager.toggleBulletList(); // bullets, with the default "disc" marker
rt.manager.toggleBulletList("★"); // bullets, with a star

// Plain canvas: const rt = createCanvasRichText({ onChange: render })
rt.toggleBulletList();

toggleBulletList turns the paragraphs it reaches into list items, or back into plain paragraphs. If any of them is not a list item yet, all of them become list items, and the ones that were list items already keep their marker. If all of them are list items, all of them become plain paragraphs, and their marker, indent and marker offset are removed.

It reaches paragraphs the same way the paragraph commands do: every paragraph of a box that is only selected, every paragraph the selection touches while editing, or the paragraph the cursor is in. To change boxes you name instead, pass them as the optional last argument. With no marker to give, pass undefined first:

rt.manager.toggleBulletList(undefined, [specials]);

Each call is one undo step.

Change the marker

rt.manager.setBulletStyle("decimal"); // 1. 2. 3.
rt.manager.setBulletStyle("🍋"); // an emoji, drawn in color

setBulletStyle changes the marker of the list items it reaches and leaves plain paragraphs as they are. The marker is one of these:

Marker Draws
"disc" • (the default)
"circle" ◦
"square" ▪
"decimal" 1. 2. 3.
Any character That character: "–", "➤", "★", "✓", a letter, or an emoji

Numbers count the list items that follow one another: a plain paragraph, or an item with a different marker, between two numbered items starts the count again at 1.

A character marker is drawn in the font, size and color of its item's first letter, at the regular weight. Under a gradient or image fill, a character marker takes the fill as the letters do.

Limit: A marker is one character. A string longer than three UTF-16 code units, such as some emoji made of several joined emoji, draws a disc instead.

A shape with a mark inside

A vector marker is drawn from two shapes, each in its own color: a backing shape and a mark on top of it. Set it as marker in each list item's blockAttributes, then apply the document with setDocument, which is one undo step:

// A check mark in a 24x24 box (SVG path data)
const CHECK = "M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z";

const doc = structuredClone(box.getDocument()); // never change the live document in place
for (const block of doc.blocks) {
  if (block.type === "list-item") {
    block.blockAttributes.marker = { shape: "circle", shapeFill: "#5E2DC1", glyphPath: CHECK, glyphColor: "#FFFFFF" };
  }
}
rt.manager.setDocument(doc, [box]); // plain canvas: rt.setDocument(doc, [box])
Field What it does
shape The backing shape: "circle", "square" (a square with rounded corners), or "none" (the default) for the mark alone.
shapeFill The backing shape's color, any CSS color. Default: the item's text color.
glyphPath The mark, as SVG path data in a 24x24 box. The path is filled, not stroked. On a backing shape the mark takes 60% of the marker's size. Leave it out for a shape with no mark.
glyphColor The mark's color. Default: the item's text color.
scale The marker's size, as a multiple of the item's font size. Default 1.

A vector marker is centered in the space before the item's text, at the middle of the capital letters' height. It keeps its own colors under a gradient or image fill. setBulletStyle replaces a vector marker with the character you give it.

Indent

Two list settings in blockAttributes place the marker and the text, both as multiples of the item's font size:

Field What it does
listIndent Where the item's text starts, from the left edge of the box. Default 2. Every line of the item starts there, so wrapped lines line up with the first.
bulletOffset Where the marker's space starts, from the left edge of the box. Default 0.6. The marker is centered between this and listIndent.

There is no command for these two settings: set them in the document and apply it with setDocument, as for a vector marker. Because they are multiples of the font size, the indent grows and shrinks with the text.

Typing in a list

While a list item is being edited:

  • Enter starts a new list item with the same marker and settings.
  • Enter on an empty list item turns that item into a plain paragraph, which ends the list.
  • Enter at the start of an item leaves an empty item above it.
  • Shift+Enter breaks the line inside the item. The new line starts at the item's text indent and gets no marker.
  • Tab types a tab, which moves the text after it to the next tab stop. It does not indent the item: see Tab stops.

Limit: Lists have one level. There is no nesting; to indent a whole list further, raise its listIndent and bulletOffset.

Show the current list

FormattingState reports isList (the paragraph at the cursor is a list item) and listStyle (its marker), so a toolbar can light the right marker button.

In the API reference