Text utilities write to the text properties of Roblox’s text instances: TextSize, TextColor3,
FontFace, TextXAlignment, TextYAlignment, TextWrapped, TextTruncate and LineHeight. Two
of them rewrite the Text string itself. Three host elements have those properties: textlabel,
textbutton, and textbox.
<textlabel className="text-2xl font-semibold text-slate-100 text-center align-middle" Text="Match found"/>import React from "@rbxts/react";
export function TextSamples() { return ( <frame className="flex flex-col gap-2 w-80 h-32 p-4 rounded-lg bg-slate-900"> <textlabel className="w-full h-7 text-left text-slate-100 text-2xl font-bold" Text="Match found" /> <textlabel className="w-full h-5 text-left text-slate-300 text-base font-medium" Text="Ranked · Duo queue" /> <textlabel className="w-full h-4 text-right text-slate-500 text-sm" Text="starting in 5s" /> </frame> );}import { __VelaBoundary } from "@rbxts/vela-runtime";import React from "@rbxts/react";export function TextSamples() { return <__VelaBoundary.Consume>{(<frame BackgroundColor3={Color3.fromRGB(15, 23, 43)} Size={UDim2.fromOffset(320, 128)} BorderSizePixel={0}><uilistlayout FillDirection={Enum.FillDirection.Vertical} Padding={new UDim(0, 8)} SortOrder={Enum.SortOrder.LayoutOrder}/><uipadding PaddingTop={new UDim(0, 16)} PaddingRight={new UDim(0, 16)} PaddingBottom={new UDim(0, 16)} PaddingLeft={new UDim(0, 16)}/><uicorner CornerRadius={new UDim(0, 8)}/> <textlabel Text="Match found" TextXAlignment={Enum.TextXAlignment.Left} TextColor3={Color3.fromRGB(241, 245, 249)} TextSize={24} Size={new UDim2(1, 0, 0, 28)} FontFace={new Font("rbxasset://fonts/families/SourceSansPro.json", Enum.FontWeight.Bold)} BorderSizePixel={0} BackgroundTransparency={1}/> <textlabel Text="Ranked · Duo queue" TextXAlignment={Enum.TextXAlignment.Left} TextColor3={Color3.fromRGB(202, 213, 226)} TextSize={16} Size={new UDim2(1, 0, 0, 20)} FontFace={new Font("rbxasset://fonts/families/SourceSansPro.json", Enum.FontWeight.Medium)} BorderSizePixel={0} BackgroundTransparency={1}/> <textlabel Text="starting in 5s" TextXAlignment={Enum.TextXAlignment.Right} TextColor3={Color3.fromRGB(98, 116, 142)} TextSize={14} Size={new UDim2(1, 0, 0, 16)} BorderSizePixel={0} BackgroundTransparency={1}/> </frame>)}</__VelaBoundary.Consume>;}The Lowered tab shows what Vela really emits for font-*: a FontFace carrying a Font value built from the theme’s family, the weight, and the style. The browser renderer behind these previews has no Font datatype, so the preview maps each weight onto the nearest legacy Font enum member instead. Relative weights read correctly. The exact face does not.
The host restriction is editor-only
Vela knows which utilities belong on which host element, and reports unsupported-host-utility when you get it wrong. That check lives in the LSP surface — diagnostics, hover, completions, and document colors.
The compiler never consults it. The transform pipeline does not check host tags at all.
<frame className="text-red-500" /> compiles cleanly and emits TextColor3 onto a Frame, with no
build-time warning.
The utility-per-host rules are enforced only by the editor and LSP. Text utilities belong on textlabel, textbutton and textbox, image-* on imagelabel and imagebutton, and placeholder-* on textbox. rbxtsc will happily emit TextColor3 on a Frame.
Roblox ignores an unknown property assignment, so nothing crashes — the class does nothing and you get no signal outside the editor. Use editor setup to get the warning.
The restriction does not apply to components
Inside a component’s className the rule does not exist. Vela has no idea which host element
<Title /> renders, so the editor completes and hovers the full set, text-*, image-* and
placeholder-* included. It never reports unsupported-host-utility.
<Title className="text-2xl font-semibold" Text="Match found" />Whether that text-2xl reaches a TextSize depends on your component forwarding what it does not
consume down to a text host. See
How it works.
How text-* is disambiguated
text- is the most overloaded prefix in the whole system. One rule, applied in order, decides what a given text-* class means:
- If the payload is a text-size key (
xsthrough9xl), it isTextSize. - If the payload is
left,center,right, orjustify, it isTextXAlignment. - If the payload is
wrapornowrap, it isTextWrap. - Everything else falls through to
TextColor.
The fourth step is a catch-all, so there is no such thing as an unrecognised text-* class.
text-foo is read as a colour, foo is not in theme.colors, and you get unknown-theme-key
rather than unsupported-utility-family.
So read unknown-theme-key on a text-* class as “you probably meant a size or an alignment”.
Size
text-{size} sets TextSize, in rem — the numbers below are what each key is worth at the base
viewport. See rem.
| Class | TextSize |
|---|---|
text-xs | 12 |
text-sm | 14 |
text-base | 16 |
text-lg | 18 |
text-xl | 20 |
text-2xl | 24 |
text-3xl | 30 |
text-4xl | 36 |
text-5xl | 48 |
text-6xl | 60 |
text-7xl | 72 |
text-8xl | 96 |
text-9xl | 128 |
The set is fixed and does not come from the theme. Anything outside the list falls through to the colour branch above.
Roblox stops honouring TextSize past 100 and does it silently, so rem stops there too: on a large viewport text-6xl and up land on that ceiling.
The ceiling belongs to the scaling, so it covers arbitrary values too: text-[240px] emits __VelaRem.scaleText(240, 0) and renders at 100. Pin the clamp and the scaling leaves the emit entirely.
Weight, family and style
font-* is one prefix over three axes. It resolves the fixed weight names first and reads anything
else as a font family key, so weight, family and style merge into a single FontFace.
FontFace={new Font("rbxasset://fonts/families/RobotoMono.json", Enum.FontWeight.Bold, Enum.FontStyle.Italic)}Weight
font-{weight} sets the weight axis of that Font value:
new Font("rbxasset://fonts/families/SourceSansPro.json", Enum.FontWeight.SemiBold)| Class | Enum.FontWeight |
|---|---|
font-thin | Thin |
font-extralight | ExtraLight |
font-light | Light |
font-normal | Regular |
font-medium | Medium |
font-semibold | SemiBold |
font-bold | Bold |
font-extrabold | ExtraBold |
font-black | Heavy |
Two names do not match their class: font-normal produces Regular and font-black produces
Heavy, following the Roblox enum member names.
italic rides in the same FontFace value as a third argument, so font-bold italic emits
new Font("…/SourceSansPro.json", Enum.FontWeight.Bold, Enum.FontStyle.Italic). not-italic resets
the style axis.
Family
The family is a theme axis: theme.fontFamily ships three keys, and font-{key} selects one:
| Class | Family |
|---|---|
font-sans | Source Sans Pro — the default when no family class is present |
font-serif | Merriweather |
font-mono | Roboto Mono |
Add your own the way you add any other theme key. The value is a Roblox font family asset path, not an expression:
export default defineConfig({ theme: { extend: { fontFamily: { display: "rbxassetid://12345678", body: "rbxasset://fonts/families/Nunito.json", }, }, },});<textlabel className="font-display font-bold" Text="Match found" />Because the family lookup is the fallback branch, a payload that is neither a weight nor a
configured family key reports unknown-theme-key.
A font you do not want in the theme can be set as a prop, leaving font-* off that element:
<textlabel FontFace={new Font("rbxasset://fonts/families/GothamSSm.json", Enum.FontWeight.Bold)} Text="Custom family"/>It does not compose with font-bold or italic, which emit a whole FontFace of their own.
Alignment
Horizontal alignment comes from text-left, text-center, and text-right, which set TextXAlignment.
Vertical alignment uses a different prefix: align-top, align-middle and align-bottom set
TextYAlignment to Top, Center and Bottom. Anything else after align- reports
unsupported-text-alignment.
text-justify parses but does not resolve, since TextXAlignment has no justified mode. It reports
unsupported-text-alignment.
Line height
leading-{key} sets LineHeight from six named keys — none (1), tight (1.25), snug (1.375),
normal (1.5), relaxed (1.625), loose (2). The numeric Tailwind forms (leading-5,
leading-[1.2]) are not accepted and report unsupported-line-height-value.
Wrapping and truncation
text-wrap sets TextWrapped = true, and text-nowrap sets it to false. The whitespace-normal
and whitespace-nowrap pair is an accepted alias for the same property. Any other whitespace-*
value is unsupported-whitespace-value.
truncate sets TextTruncate = Enum.TextTruncate.AtEnd. It takes no payload, and AtEnd is the
only truncation mode Vela emits.
<textlabel className="text-sm text-nowrap truncate" Text={playerName} />Case transforms rewrite the string
There is no text-transform property, so uppercase, lowercase, capitalize and normal-case
work on the Text string itself. When Text is a literal the rewrite happens at compile time:
<textlabel className="uppercase" Text="match found" /><textlabel Text="MATCH FOUND" />When Text is an expression, the transform has to run in-game. The element moves onto the
runtime path, and the helper transforms the value as it
changes.
Decorations use RichText
underline and line-through wrap the text in RichText markup, emitting Text="<u>hello</u>" with
RichText={true} and escaping the content as needed. no-underline removes them. If the element
sets RichText itself, Vela backs off with decoration-on-richtext. overline has no RichText
equivalent and reports no-roblox-equivalent.
Placeholders
placeholder-* sets PlaceholderColor3 and is meaningful only on textbox. As with every host
restriction on this page, the editor flags it and the compiler does not.
placeholder-transparent is an error (unsupported-color-key) — PlaceholderColor3 has no paired
transparency property. See Colours and surfaces for the
other families.
Typography families that cannot exist
A few Tailwind typography families have no Roblox property to target, and report
no-roblox-equivalent rather than reading as typos:
tracking-*— letter spacing. The Roblox text engine exposes nothing for it.indent-*,break-*,hyphens-*,list-*— no indentation, line-breaking, or list-marker control.decoration-*andoverline— RichText has underline and strikethrough only.
See also
- Colors and surfaces — the color half of
text-*, plustransparentand the shade rules. - Theming — where
text-{color}keys come from. - Editor setup — getting the host-restriction warnings the compiler does not give you.
- Diagnostics — every code and what triggers it.