Pardon our dust while we move our docs here. The Godot (GDScript) docs are on this site, and the Yarn language, Unity, Godot (C#) and Unreal docs are still at docs.yarnspinner.dev.

Markup is tags in square brackets that you put in your lines, like [b]this[/b]. The Line Presenter uses them to format the text, change the words, or run your own code as the line types out.

Markup comes in two kinds:

  • Markup that changes the text. It’s applied before the line is shown. Formatting like [b], and tags that pick words like [plural], work this way.
  • Markup that does something while the line types out. It marks a point in the line, and runs when the typewriter reaches it. [pause/] works this way.

The Line Presenter only formats markup when its Use Markup setting is on, which is the default.

The examples from this page in a scene.

Formatting text

The Line Presenter formats these for you, with no extra code, as long as Use Markup is on. The Options Presenter shows options without formatting. If you build your own Presenter, it needs to turn markup into formatting itself.

MarkupWhat it does
[b]text[/b]Bold.
[i]text[/i]Italic.
[u]text[/u]Underline.
[s]text[/s]Strikethrough.
[code]text[/code]Monospaced text.
[style=bold]text[/style]The same as [b]. [style] also takes italic, underline, strikethrough and code, and any styles you add yourself.
[color=red]text[/color]Coloured text. Use a colour name, like red or orange, or a hex code, like #ff8800.
[link="logbook"]text[/link]Text the player can click. See Links.
Formatting markup the Line Presenter handles.
Rosa: The lamp is [b]never[/b] supposed to go out.
A line with bold markup.
The dialogue box showing: The lamp is never supposed to go out, with never in bold
Bold, with [b].
Rosa: Check the [color=orange]oil level[/color] first.
A line with orange text.
The dialogue box showing: Check the oil level first, with oil level in orange
Orange, with [color=orange].

If a line has markup the Line Presenter doesn’t recognise, like [wave]text[/wave], it shows the text without the tags. The markup is still part of the line, so your own code can use it.

To show square brackets in a line, put a backslash before each one: \[like this\]. To show a whole run of text without treating any of it as markup, wrap it in [nomarkup]:

Rosa: The label says [nomarkup][DO NOT TOUCH][/nomarkup].
Square brackets shown as written.
The dialogue box showing: The label says [DO NOT TOUCH], with the square brackets shown
The text inside [nomarkup] is shown as written.

[link] turns part of a line into something the player can click. You might use it for a word the player can click to read more about, such as an entry in a journal or glossary.

Rosa: It's all written down in [link="logbook"]the logbook[/link].
A link to the logbook.
The dialogue box showing: It's all written down in the logbook, with the logbook underlined
The link is underlined.

The text between the tags, “the logbook”, is what the player sees. The value in quotes, logbook, is how your code tells which link was clicked. A [link] without a value is shown as plain text.

Clicking a link doesn’t do anything by itself. It doesn’t continue the dialogue either, even with the Line Presenter’s Click Anywhere to Continue on. To make a link do something, connect to the text label’s meta_clicked signal. It passes the link’s value as a String:

@onready var line_presenter: YarnLinePresenter = $CanvasLayer/YarnLinePresenter

func _ready():
	line_presenter.text_label.meta_clicked.connect(_on_link_clicked)

func _on_link_clicked(meta):
	if meta == "logbook":
		open_logbook()
Lines 3–4 Connect to the text label’s meta_clicked signal, which is emitted when the player clicks a link.
Lines 6–8 meta is the link’s value, like logbook from [link="logbook"]. Check it to decide what to do.
Doing something when the player clicks a link.

To react when the mouse moves over a link, for example to show a tooltip, connect to meta_hover_started and meta_hover_ended in the same way.

Godot underlines links. To turn that off, select the text label and turn off its Meta Underlined property. To colour a link, put [color] inside it:

Rosa: It's all written down in [link="logbook"][color=orange]the logbook[/color][/link].
A link coloured orange.

Some limits on links:

  • Links only work with a mouse or touchscreen. Players using a keyboard or gamepad can’t select them, so don’t put anything the player needs behind a link.
  • The player can only click a link once its text has typed out.
  • Options show their text without markup, so a [link] in an option can’t be clicked.
  • Use a name for the link’s value, not a web address. // starts a comment in Yarn, so everything after it in the line is ignored.

Changing the words

These tags are replaced with different words, depending on a value. They’re self-closing: they end in /] and have no closing tag.

[select] picks one of several options by name:

<<declare $mood = "happy">>
Rosa: I'm [select value={$mood} happy="glad" sad="sorry"/] you came.
Choosing a word based on $mood.
The dialogue box showing: I'm glad you came
With $mood set to happy.

[plural] picks the right word for a number. % is replaced with the number:

<<declare $coins = 3>>
Rosa: You have [plural value={$coins} one="% coin" other="% coins"/].
Choosing coin or coins based on $coins.
The dialogue box showing: You have 3 coins
With $coins set to 3.

[ordinal] does the same for positions, like 1st, 2nd and 3rd:

<<declare $visits = 2>>
Rosa: It's your [ordinal value={$visits} one="%st" two="%nd" few="%rd" other="%th"/] visit.
Adding the right ending to a number.
The dialogue box showing: It's your 2nd visit
With $visits set to 2.

[plural] and [ordinal] follow the rules of the line’s language, so you only need to give the forms that language uses. English uses one and other for plurals, and one, two, few and other for ordinals.

Pausing partway through a line

[pause/] stops the typewriter for a moment. See Typewriter and Effects.

The dialogue box showing: The lamp went out, and then, with the rest of the line not yet shown
The line stopped at [pause/], partway through The lamp went out, and then [pause/]I heard footsteps.

A self-closing tag removes the space that comes after it. Put the space before the tag instead: then [pause/]I heard, not then[pause/] I heard.

Your own formatting tags

To add your own tags, like [happy], that apply formatting you choose, make a palette. A palette is a list of tag names, each with the formatting it applies.

Put this in a script on any node in your scene:

@export var line_presenter: YarnLinePresenter

func _ready():
	var palette := YarnMarkupPalette.new()
	palette.add_basic_marker("happy", true, Color.GOLD, true)
	var big := "[font_size=64]"
	palette.add_custom_marker("loud", big, "[/font_size]")

	var processor := YarnPaletteMarkerProcessor.new(palette)
	processor.register_with_line_provider(line_presenter)
Line 1 The Line Presenter the tags are for. Select the node with this script, and set Line Presenter in the Inspector.
Line 3 Set up the palette when the scene starts, before any dialogue runs.
Line 4 Make a new, empty palette.
Line 5 Add a basic tag called happy. The arguments are the tag name, whether to change the colour, the colour, and whether to make the text bold. Three optional arguments follow, in this order: italic, underlined and struck through.
Lines 6–7 Add a custom tag called loud, with the Godot BBCode to put before and after the text. Use a custom tag for anything a basic tag can’t do, like changing the size.
Line 9 Make a processor that applies the palette to lines.
Line 10 Register the processor with the Line Presenter, for every tag in the palette.
A palette with two tags, happy and loud.
Rosa: I'm [happy]so pleased[/happy] you're here!
The happy tag in a line.
The dialogue box showing: I'm so pleased you're here, with so pleased in bold gold
The happy tag from the palette.
Rosa: [loud]HELLO?[/loud]
The loud tag in a line.
The dialogue box showing HELLO? in larger text
The loud tag from the palette.

Your own styles

To add your own names for [style], register a YarnStyleMarkerProcessor with them:

func _ready():
	var styles := YarnStyleMarkerProcessor.new()
	styles.styles["title"] = {
		"start": "[b][color=#4080ff]",
		"end": "[/color][/b]",
	}
	line_presenter.register_marker_processor("style", styles)
Line 1 If the script already sets up a palette, put these lines in that same _ready function. A script can only have one _ready.
Line 2 Make a style processor. It includes the built-in styles, like bold and italic.
Line 3 Add a style called title. Write the name in lowercase: [style=title] and [style=Title] both find it.
Lines 4–5 start is the BBCode to put before the text, and end is the BBCode to put after it. Close the tags in the opposite order to how you opened them.
Line 7 Register the processor with the Line Presenter, for the style tag. It takes over [style] from the Line Presenter, and handles the built-in styles as well as yours.
Adding a style called title.
Rosa: Have you read [style=title]The Keeper's Log[/style]?
The title style in a line.
The dialogue box showing: Have you read The Keeper's Log, with the title in bold blue
The title style.

The built-in styles, like [style=italic], still work.

Writing your own text-changing markup

If a palette can’t do what you need, write a marker processor. It gets the text inside the tag, and can change it however you like. This one turns [whisper]text[/whisper] into grey italics:

class_name WhisperMarkerProcessor
extends YarnAttributeMarkerProcessor

const START := "[color=gray][i]"
const END := "[/i][/color]"


func process_replacement_marker(
		_marker: YarnMarkupAttribute,
		child_builder: Array,
		_child_attributes: Array,
		_locale_code: String
) -> ReplacementMarkerResult:
	var start := YarnMarkupParser.brackets_to_tags(START)
	var end := YarnMarkupParser.brackets_to_tags(END)
	child_builder[0] = start + child_builder[0] + end
	var added := START.length() + END.length()
	return ReplacementMarkerResult.new([], added)
Lines 1–2 Extend YarnAttributeMarkerProcessor.
Lines 4–5 The BBCode to put around the text.
Lines 8–13 process_replacement_marker is called for each [whisper] in a line, before the line is shown. child_builder[0] is the text inside the tag. The other arguments aren’t needed here, so their names start with _.
Lines 14–15 BBCode you add has to go through brackets_to_tags. Otherwise its brackets are shown on screen as text.
Line 16 Change the text inside the tag by setting child_builder[0].
Lines 17–18 Return how many characters you added that aren’t shown on screen. For BBCode, that’s all of it.
A marker processor for a whisper tag.

Register it on the Line Presenter, with the tag name it handles:

func _ready():
	var whisper := WhisperMarkerProcessor.new()
	line_presenter.register_marker_processor("whisper", whisper)
Registering the whisper tag.
Rosa: [whisper]Don't tell the others.[/whisper]
The whisper tag in a line.
The dialogue box showing: Don't tell the others, in grey italics
The whisper tag.

You can register the same processor for more than one tag. The first argument, the tag itself, has its name in name and any values you gave it, such as [whisper volume=2].

Running code partway through a line

Markup can trigger your own code when the typewriter reaches a certain point in a line, like changing a character’s expression, playing a sound, or shaking the screen. To do this, write an event handler and add it to the Line Presenter’s Event Handlers list.

This is the handler from Typewriter and Effects, which shakes the dialogue box wherever a line has [shake_box/]:

extends YarnActionMarkupHandlerNode

@export var panel: Control

var _positions: Array[int] = []


func on_prepare_for_line(
		line: Variant,
		_text_control: Control = null
) -> void:
	_positions.clear()
	for attribute in (line as YarnMarkupParseResult).attributes:
		if attribute.name == "shake_box":
			_positions.append(attribute.position)


func on_character_will_appear(
		character_index: int,
		_line: Variant,
		_token: Variant = null
) -> Signal:
	if character_index in _positions:
		return YarnEffects.shake(panel, 12.0, 0.4)
	return Signal()
Line 1 Event handlers extend YarnActionMarkupHandlerNode, so they’re nodes in your scene and can use other nodes.
Line 3 The node to shake, set in the Inspector.
Line 5 Where in the line each [shake_box/] is.
Lines 8–15 on_prepare_for_line is called when a line arrives, before any of it is shown. Look through the line’s markup, and remember the position of each [shake_box/].
Lines 18–22 on_character_will_appear is called before each character appears.
Lines 23–24 If there’s a [shake_box/] here, shake the panel. Returning a signal makes the typewriter wait until it’s emitted.
Line 25 Otherwise, return an empty Signal() so the typewriter carries on without waiting.
An event handler that shakes the dialogue box.

To use it:

  1. Add a Node to your scene, and attach the script to it.
  2. Set its Panel to the Line Presenter’s PanelContainer.
  3. Select the Line Presenter, and add the node to its Event Handlers list.

An event handler can define any of these functions. Write only the ones you need:

FunctionWhen it’s called
on_prepare_for_line(line, text_control)A line has arrived, before any of it is shown.
on_line_display_begin(line, text_control)Before the first character appears.
on_character_will_appear(character_index, line, token)Before each character appears. Return a signal to make the typewriter wait for it.
on_line_display_complete()The whole line is showing.
on_line_will_dismiss()The line is about to leave the screen.
The functions an event handler can use.

If the player presses Continue Action while your handler is waiting, token.is_hurry_up_requested becomes true. For anything that takes a while, check it and finish early.

Samples

Two samples show more of this. Open them from the samples browser in the Yarn Spinner tab:

  • Replacement Markup uses palettes, styles and marker processors.
  • Inline Events uses event handlers to move a character and change their expression partway through a line.
Next step Voice-Over Playing recorded audio for each line.