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.
Building a span
Section titled “Building a span”A span wraps a string with styling attached:
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 objectspan.color(0xFF5555); // int rgbspan.color("#ff5555"); // hex stringspan.color("red"); // named colorColorParser handles the string form, so anything the markup parser accepts works here too.
Style methods
Section titled “Style methods”| Method | Argument | Effect |
|---|---|---|
color(TextColor) | net.minecraft.network.chat.TextColor | sets text color |
color(int) | RGB int | sets text color from 0xRRGGBB |
color(String) | hex string or named color | sets text color via ColorParser |
bold(boolean) | boolean | bold text |
italic(boolean) | boolean | italic text |
underline(boolean) | boolean | underline |
strikethrough(boolean) | boolean | strikethrough |
obfuscated(boolean) | boolean | vanilla obfuscated style |
font(ResourceLocation) | ResourceLocation | custom font |
All style methods return this, so you can chain freely.
Fade and animation
Section titled “Fade and animation”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.
Effects on a span
Section titled “Effects on a span”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
Section titled “Background”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.
Inline content
Section titled “Inline content”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 3new 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].
Click and hover
Section titled “Click and hover”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.
Copying a span
Section titled “Copying a span”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.
Combining spans into a message
Section titled “Combining spans into a message”Build a List<TextSpan> and pass it to ImmersiveMessage.fromSpans:
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);Spans versus markup
Section titled “Spans versus markup”The same result, two ways:
ImmersiveMessage.fromMarkup(100, "<red><bold>Alert:</bold></red> <wave>zone is active</wave>");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.