Text Spans

TextSpan is the unit of styled text behind every immersive message. When you call ImmersiveMessage.fromMarkup, the parser builds spans internally. When your content is dynamic (computed values, per-player state, runtime colors) you can build that list yourself and skip the parser entirely.

A span wraps a string with styling attached:

BasicSpan.java
TextSpan span = new TextSpan("You found ")
.color(TextColor.fromRgb(0xFFD700))
.effect("rainbow freq=0.8");

You can also pass a TextColor constant, an RGB int, or a named color string; all three overloads exist:

span.color(TextColor.fromRgb(0xFF5555)); // TextColor object
span.color(0xFF5555); // int rgb
span.color("#ff5555"); // hex string
span.color("red"); // named color

ColorParser handles the string form, so anything the markup parser accepts works here too.

MethodArgumentEffect
color(TextColor)net.minecraft.network.chat.TextColorsets text color
color(int)RGB intsets text color from 0xRRGGBB
color(String)hex string or named colorsets text color via ColorParser
bold(boolean)booleanbold text
italic(boolean)booleanitalic text
underline(boolean)booleanunderline
strikethrough(boolean)booleanstrikethrough
obfuscated(boolean)booleanvanilla obfuscated style
font(ResourceLocation)ResourceLocationcustom font

All style methods return this, so you can chain freely.

fadeIn and fadeOut control per-span opacity in ticks. typewriter makes the span’s characters appear one at a time:

TextSpan line = new TextSpan("Encounter begins")
.color(0xAA0000)
.bold(true)
.fadeIn(10)
.fadeOut(20)
.typewriter(1.5f);

typewriter(float speed) accepts characters-per-tick. typewriterCentered(float speed) keeps the growing text centered.

fade(int inTicks, int outTicks) is shorthand for calling both at once.

obfuscate(ObfuscateMode mode, float speed) replaces characters progressively. ObfuscateMode values: NONE, LEFT, RIGHT, CENTER, EDGES, RANDOM.

There are two ways to attach an effect. addEffect takes an Effect instance you’ve already constructed:

Effect glow = EffectRegistry.create("neon", EmptyParams.INSTANCE);
new TextSpan("Critical hit!").addEffect(glow);

effect(String) is the shorter form. It routes through EffectRegistry.parseTag, which accepts the same tag syntax as markup: name first, then key=value pairs separated by spaces:

new TextSpan("Critical hit!")
.effect("neon")
.effect("shake amplitude=2.0");

Each call to addEffect or effect appends to the span’s effect list; calling them multiple times stacks effects.

If a tag string names an unknown effect, effect() swallows the error silently (a debug log is emitted). If you need to detect unknown effects, use EffectRegistry.parseTag directly and handle the IllegalArgumentException.

clearEffects() resets the list.

background(ImmersiveColor color) gives the span a solid backdrop. backgroundGradient(ImmersiveColor... colors) takes two or more stops:

new TextSpan("Warning")
.background(new ImmersiveColor(0xFF, 0x00, 0x00, 180));
new TextSpan("Rare")
.backgroundGradient(
new ImmersiveColor(0x44, 0x00, 0x88, 200),
new ImmersiveColor(0xAA, 0x00, 0xFF, 200)
);

ImmersiveColor takes either an ARGB int or separate (r, g, b, a) ints.

A span can render an item icon instead of text. The content string is ignored when itemId is set:

new TextSpan("").item("minecraft:diamond_sword")
new TextSpan("").item("minecraft:apple", 3) // stack of 3
new TextSpan("").item("minecraft:compass").itemOffset(2f, -4f)
new TextSpan("").item("minecraft:player_head")
.itemNbt("{SkullOwner:\"Steve\"}")

Entity rendering works the same way:

new TextSpan("").entity("minecraft:creeper")
new TextSpan("").entity("minecraft:sheep", 0.75f) // scaled down
.entityOffset(0f, 8f)
.entityRotation(30f, 10f)
.entitySpin(2.5f)
.entityLighting(12)
.entityAnimation("idle")

entityRotation(yaw, pitch) and entityRotation(yaw, pitch, roll) both exist. entityRoll(float) sets roll alone. entityLighting(int) clamps to [0, 15].

Use paired calls. The action name and the value are separate fields:

new TextSpan("Click me")
.clickAction("run_command")
.clickValue("/warp spawn");
new TextSpan("Hover me")
.hoverAction("show_text")
.hoverValue("Teleports you to spawn");

Valid click actions mirror the vanilla event names: run_command, suggest_command, open_url, copy_to_clipboard. Valid hover actions: show_text, show_item, show_entity.

inherit() returns a shallow copy. Style and effects are shared by reference except the effects list, which is copied. Setting text content on the copy isn’t available through the public API (setContent is package-private), so the practical pattern is to extract a small helper that applies your common style chain to a fresh span:

static TextSpan gold(String text) {
return new TextSpan(text).color(0xFFD700).bold(true);
}
TextSpan a = gold("Gold ");
TextSpan b = gold("ingot");

inherit() is still useful when you want a duplicate of an existing styled span and then plan to change only the effects on the copy.

Build a List<TextSpan> and pass it to ImmersiveMessage.fromSpans:

SpanMessage.java
import net.tysontheember.emberstextapi.immersivemessages.api.ImmersiveMessage;
import net.tysontheember.emberstextapi.immersivemessages.api.TextSpan;
int kills = 5;
int total = 20;
List<TextSpan> spans = List.of(
new TextSpan("Wave complete ").color(0xFFFFFF),
new TextSpan(kills + " kills").color(0xFF4444).bold(true),
new TextSpan(" / ").color(0x888888),
new TextSpan(total + " total").color(0xAAAAAA)
);
ImmersiveMessage msg = ImmersiveMessage.fromSpans(120, spans)
.fadeInTicks(8)
.fadeOutTicks(15)
.anchor(TextAnchor.MIDDLE);
EmbersTextAPI.sendMessage(player, msg);

The same result, two ways:

WithMarkup.java
ImmersiveMessage.fromMarkup(100, "<red><bold>Alert:</bold></red> <wave>zone is active</wave>");
WithSpans.java
ImmersiveMessage.fromSpans(100, List.of(
new TextSpan("Alert:").color(0xFF5555).bold(true),
new TextSpan(" zone is active").effect("wave")
));

Prefer spans over markup when:

  • The text content comes from runtime data (scores, counts, player names) and embedding it in a format string would be awkward.
  • Colors are computed at runtime (health gradients, team colors from config).
  • You need different effects on different segments and constructing the markup string dynamically would require careful escaping.
  • You’re generating messages in a loop with many per-player variants.

Prefer markup when the text is static or mostly static and you want to see the full styled string at a glance.