<text>
<text> renders a block of text. It is a full styled node, like a
<node>, with layout, background, border, pointer events, state
styles and layer styles, whose content is its string children. A <text>
nested inside another <text> is an inline span that restyles part of the
block.
Usage
<text style={{ fontSize: 18, color: "#c0caf5" }}>
Hello, <text style={{ fontSize: 18, color: "#7aa2f7" }}>world</text>!
</text>Content
- Children are strings, numbers and nested
<text>spans:<text>Score: {score}</text>works as in the DOM. - A
"\n"in a string starts a new line, whatever thelineBreakmode. - JSX whitespace rules apply: a line break between text and a tag is
dropped, so write
{" "}where a span needs a space beside it. - There are no inline elements. Put images and boxes next to the text in a
<node>, not inside the<text>. - A bare string outside any
<text>still renders, but as unstyled text: white, 20px, in Bevy's built-in font rather than your default font. Always wrap text in<text>.
Spans
A nested <text> is a span: a styled run inside its block. It takes
style, name, sharedTag and key.
- Spans don't inherit. A span's unset properties take the defaults
(white, 20px, the default font), not the parent's values. Restate
fontSizeandfontFamilyon every span, for example by spreading a shared style object. - Bare strings do inherit: a string run takes the style of the
<text>directly around it, block or span. - On a span,
color,fontSize,fontWeight,fontFamily,lineHeight,letterSpacingandopacity(which fades the span's glyphs) apply.textAlign,lineBreakandtextShadowbelong to the block, and box styles (size, padding, background, border) have no effect: a span has no box. filter,backdropFilter,morphFilter,transform3dandcacheon a span are ignored with aspanLayerStylewarning. Put them on the block.- Spans take no event handlers or state styles (a
propIgnoredwarning). A click on a span goes to its block.
const body: BevyStyle = { fontSize: 16, fontFamily: "Inter" };
<text style={{ ...body, color: "#c0caf5" }}>
Status: <text style={{ ...body, color: "#9ece6a" }}>online</text>
</text>;Text styles
| Property | Value | Default | Applies to |
|---|---|---|---|
color |
CSS color, or an { animated } color binding |
white | block, span |
fontSize |
px number, or "px", "vw", "vh", "vmin", "vmax", "rem" string |
20px | block, span |
fontWeight |
keyword, or a numeric string "1" to "1000" |
"normal" (400) |
block, span |
fontFamily |
a registered family name | the default font | block, span |
lineHeight |
multiple of the font size, px string, or { px } |
1.2 |
block, span |
letterSpacing |
px number, "px"/"rem"/"em" string, or { rem } |
0 |
block, span |
textAlign |
"left", "center", "right", "justify", "start", "end" |
"left" |
block |
lineBreak |
"wordBoundary", "anyCharacter", "wordOrCharacter", "noWrap" |
"wordBoundary" |
block |
textShadow |
{ color?, offsetX?, offsetY? } |
none | block |
<editableText> uses the same color and font properties.
Color
color takes any CSS color; an invalid one renders
magenta with a warning. opacity on the element multiplies it. There is no
transition channel for color: to animate it, bind it to a shared value
with interpolateColor (see Interpolation):
const t = useSharedValue(0);
<text
style={{
color: { animated: interpolateColor(t, [0, 1], ["#c0caf5", "#f7768e"]) },
}}
>
Alert
</text>;Size and weight
fontSize: a bare number is logical pixels."rem"is relative to Bevy'sRemSizeresource (20px by default); viewport units follow the window size. CSSemis not supported. An unparsable string becomes0(the text disappears) and reports afontSizewarning.fontWeight:"thin"(100),"light"(300),"normal"(400),"medium"(500),"semibold"(600),"bold"(700),"black"(900), or any weight as a string, such as"800". A JSON number is rejected, which the TypeScript type prevents. An unknown keyword falls back to"normal"with afontWeightwarning.- Weights render only as far as the font provides them. A variable font covers every weight; a static font file has its own weight only, and Bevy does not synthesize bold.
Line height and letter spacing
lineHeight: a number is a multiple of the font size. As a string,"px"is absolute, and a unitless,"em"or"rem"value is a multiple of the font size.{ px: 24 }is absolute. An invalid string falls back to1.2with alineHeightwarning.letterSpacing: a number, a numeric string or a"px"string is logical pixels."rem","em"and{ rem }are multiples of Bevy'sRemSize(20px by default), not of the font size."normal"is0. An invalid string falls back to0with aletterSpacingwarning.
Alignment and wrapping
textAlignaligns the lines within the block's width. A content-sized block is exactly as wide as its longest line, so give it awidth(or let its parent stretch it) to center or right-align a single line."start"and"end"follow the text direction.lineBreakchooses where text wraps when it is wider than the block:"wordBoundary"breaks between words,"anyCharacter"anywhere,"wordOrCharacter"between words and inside words that don't fit a line alone, and"noWrap"never wraps (a"\n"still breaks).- Text wraps against the block's own width or, without one, the width its parent allows.
Shadow
textShadow draws one hard-edged copy of the text behind it: offsetX and
offsetY in logical pixels (default 4 each) and a color (default black
at 75% opacity). There is no blur. The element's opacity fades the shadow
with the text.
<text style={{ textShadow: { color: "#000000cc", offsetX: 2, offsetY: 2 } }}>
Game over
</text>Fonts
Fonts are registered on the Rust side and selected by name from React:
TSX
<text style={{ fontFamily: "Mono", fontSize: 14 }}>let x = 1;</text>Rust
app.add_plugins(
ReactPlugins.set(
ReactUiPlugin::default()
.default_font("fonts/Inter-Variable.ttf")
.font("Mono", "fonts/JetBrainsMono.ttf"),
),
);- Paths are asset paths, relative to the app's asset folder, loaded through
the
AssetServerat startup. default_fontapplies to every<text>, span and<editableText>without afontFamily. Without it, text uses Bevy's built-in font.fontFamilynames are exact and case-sensitive. An unknown name falls back to the default font with afontFamilywarning. Registering a name twice keeps the last path.- Each family is one font file. Register a variable font to get every
weight, or register each static weight under its own name
(
"Inter Bold"). - React cannot load fonts at runtime: every family must be registered up front.
Interaction and layers
A <text> block is a full element: it takes pointer handlers, state styles
(a hoverStyle color for links), scroll props, and layer styles such as
filter and transform3d. See <node> for the shared props and
Layers for what promotes a block.
Limits
- Spans don't inherit their parent's style, and they have no box, events or layer styles.
- Unsetting both
textAlignandlineBreak, including when a state style that set them ends, keeps the last alignment and wrapping. SettextAlign: "left"andlineBreak: "wordBoundary"explicitly instead. - Wrapped text inside a node that combines a percentage
widthwithmaxWidthcan be measured at the unclamped width, so the node reserves too little height and overlaps what follows. Size such a container in pixels. - One shadow per block, without blur.
See <text> in the element reference, and
color,
fontSize,
fontWeight,
fontFamily,
lineHeight,
letterSpacing,
textAlign,
lineBreak and
textShadow in the style
reference.
bevy-react