Forest Logo
search
package_2

textsizer

By @biotoxin495

Roblox

Mirrored from Wally

TextSizer — Automatic sizing for Roblox UI text

TextSizer, a small, dependency-free Roblox utility module that sizes TextLabel, TextButton, and TextBox instances to fit their rendered text.

Text-driven UI often needs to grow or shrink as content changes. TextSizer handles this for buttons, tabs, badges, tags, counters, inventory labels, localized text, and other content-driven UI elements.

Quick example

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local TextSizer = require(
	ReplicatedStorage:WaitForChild("TextSizer")
)

local handle = TextSizer.Attach(script.Parent, {
	Axis = Enum.AutomaticSize.X,
	Padding = Vector2.new(16, 6),
	MinSize = Vector2.new(80, 32),
	MaxSize = Vector2.new(320, 64),
})

TextSizer performs an initial resize and continues updating the object whenever its text or relevant text-rendering properties change.

When the object is no longer needed, destroy the returned handle:

handle:Destroy()

🚀 Features

  • Measures text without modifying the object
  • Resizes on the X, Y, or both axes
  • Reactively resizes changing text through TextSizer.Attach()
  • Supports TextLabel, TextButton, and TextBox
  • Supports minimum and maximum size constraints
  • Supports independent horizontal and vertical padding
  • Can include an existing UIPadding child
  • Uses rendered TextBounds when possible
  • Falls back to TextService:GetTextBoundsAsync() for width-constrained wrapped text
  • Accounts for FontFace, rich text, localization, and line-height changes
  • Batches rapid updates with deferred scheduling
  • Warns and leaves objects unchanged when TextScaled or AutomaticSize is active
  • Fully typed for Luau strict mode
  • No external dependencies

🛠️ Installation

Place the TextSizer ModuleScript somewhere accessible to your client code, such as ReplicatedStorage.

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local TextSizer = require(
	ReplicatedStorage:WaitForChild("TextSizer")
)

TextSizer is intended primarily for client-rendered Roblox UI.

📖 Basic Usage

Call Resize when a one-time measurement and resize is needed:

TextSizer.Resize(button, {
	Axis = Enum.AutomaticSize.X,
	Padding = Vector2.new(12, 0),
	MinSize = Vector2.new(100, 0),
	MaxSize = Vector2.new(280, math.huge),
})

For content that changes over time, call Attach once. The handle immediately resizes the object and continues watching it for relevant changes.

local handle = TextSizer.Attach(descriptionLabel, {
	Axis = Enum.AutomaticSize.Y,
	Padding = Vector2.new(0, 8),
	MinSize = Vector2.new(0, 24),
	MaxSize = Vector2.new(math.huge, 300),
})

The handle also destroys itself when its text object is destroyed. Call Destroy when the binding is no longer needed:

handle:Destroy()

Configuration

Measure, Resize, and Attach accept an optional Options table.

TextSizer.Attach(label, {
	Axis = Enum.AutomaticSize.XY,
	Padding = Vector2.new(12, 8),
	MinSize = Vector2.new(80, 32),
	MaxSize = Vector2.new(360, 240),
	UseUIPadding = true,
})

Axis

Axis: Enum.AutomaticSize?

Determines which axes Resize and Attach manage.

Axis = Enum.AutomaticSize.X

Manages horizontal sizing and preserves the existing vertical UDim value.

Axis = Enum.AutomaticSize.Y

Manages vertical sizing and preserves the existing horizontal UDim value. This is useful for wrapped text whose width is already determined by its layout.

Axis = Enum.AutomaticSize.XY

Manages both axes.

The default value is Enum.AutomaticSize.X. Enum.AutomaticSize.None is not supported.

Padding

Padding: Vector2?

Adds pixel padding around the measured text. The X value is added to both the left and right sides, while the Y value is added to both the top and bottom sides.

The default value is Vector2.zero.

Padding = Vector2.new(16, 8)

MinSize

MinSize: Vector2?

Sets the minimum final pixel size on each axis.

The default value is Vector2.zero.

MinSize = Vector2.new(100, 40)

MaxSize

MaxSize: Vector2?

Sets the maximum final pixel size on each axis.

The default value is infinite on both axes.

When TextWrapped is enabled and the X axis is being sized, a finite MaxSize.X is also used as the width for text measurement.

MaxSize = Vector2.new(320, 240)

UseUIPadding

UseUIPadding: boolean?

Includes the resolved values of the first direct UIPadding child in the measured size.

The default value is false.

TextSizer.Attach(button, {
	Axis = Enum.AutomaticSize.X,
	UseUIPadding = true,
})

OnResize

OnResize: ((newSize: Vector2, oldSize: Vector2) -> ())?

Runs after a resize changes the applied pixel size. Callback errors are caught and reported as warnings.

TextSizer.Attach(label, {
	OnResize = function(newSize, oldSize)
		print("Resized from", oldSize, "to", newSize)
	end,
})

Supported Text Objects

TextSizer accepts the following Roblox UI classes:

  • TextLabel
  • TextButton
  • TextBox

Passing another Instance to Measure, Resize, or Attach raises an error.

Measurement behavior

TextSizer uses the object's rendered TextBounds whenever that result is suitable. This preserves behavior associated with the actual rendered object, including localization and line-height changes.

When wrapped text must be measured against a width that the object does not currently have, TextSizer falls back to TextService:GetTextBoundsAsync() with GetTextBoundsParams. The fallback uses the object's Text, FontFace, TextSize, and RichText properties.

All final dimensions are rounded upward to reduce one-pixel clipping at text boundaries.

The selected Axis affects wrapped-text measurement, but Measure still returns both measured dimensions.

Axis Examples

Fit a button horizontally

local handle = TextSizer.Attach(button, {
	Axis = Enum.AutomaticSize.X,
	Padding = Vector2.new(18, 0),
	MinSize = Vector2.new(120, 44),
	MaxSize = Vector2.new(400, 44),
})

The existing Y component of button.Size is preserved.

Grow a wrapped description vertically

label.TextWrapped = true
label.Size = UDim2.fromOffset(320, 20)

local handle = TextSizer.Attach(label, {
	Axis = Enum.AutomaticSize.Y,
	Padding = Vector2.new(0, 6),
	MinSize = Vector2.new(0, 20),
	MaxSize = Vector2.new(math.huge, 260),
})

The label retains its current width and grows or shrinks vertically.

Fit a wrapped tooltip within a maximum width

tooltip.TextWrapped = true

local handle = TextSizer.Attach(tooltip, {
	Axis = Enum.AutomaticSize.XY,
	Padding = Vector2.new(12, 8),
	MinSize = Vector2.new(80, 32),
	MaxSize = Vector2.new(320, 220),
})

A finite MaxSize.X supplies the width used to measure wrapped text.

Manual Updates

The module automatically responds to supported UI changes when using Attach, but a recalculation can also be requested manually:

handle:Refresh()

Refresh immediately measures and resizes the attached object.

For a one-time measurement without changing the object, use Measure:

local measuredSize = TextSizer.Measure(label, {
	Padding = Vector2.new(8, 4),
	MaxSize = Vector2.new(300, math.huge),
})

print(measuredSize)

Handle Methods

handle:Refresh() -> Vector2

Immediately measures and resizes the attached object.

handle:SetOptions(options?)

Replaces the current options, reconnects any axis-dependent observers, and schedules a refresh.

handle:SetOptions({
	Axis = Enum.AutomaticSize.XY,
	Padding = Vector2.new(12, 8),
	MaxSize = Vector2.new(360, 240),
})

handle:Destroy()

Disconnects every observer owned by the handle. Calling it more than once is safe.

🛡️ TextScaled and AutomaticSize

TextSizer intentionally does not resize objects while TextScaled is enabled. TextScaled derives the rendered font size from the container size, while TextSizer derives the container size from the text, creating a circular sizing relationship.

TextSizer also does not resize objects whose Roblox AutomaticSize property is active. Running both systems on the same object can cause conflicting size writes.

In either case, TextSizer emits a warning and leaves the object unchanged. The warning is suppressed until the incompatibility changes, preventing repeated warning spam from attached handles.

Recommended setup:

textObject.TextScaled = false
textObject.AutomaticSize = Enum.AutomaticSize.None

UIPadding

Padding is usually the simplest choice. Set UseUIPadding = true when a text object already has a UIPadding child and the measured size should include it.

local padding = Instance.new("UIPadding")
padding.PaddingLeft = UDim.new(0, 12)
padding.PaddingRight = UDim.new(0, 12)
padding.Parent = button

TextSizer.Attach(button, {
	Axis = Enum.AutomaticSize.X,
	UseUIPadding = true,
})

Scale-based UIPadding values are resolved against the object's current AbsoluteSize. Pixel offsets are generally more predictable for content-driven sizing.

Lifecycle and performance

Attached updates are batched with task.defer(). If several observed properties change in one task cycle, TextSizer performs at most one scheduled refresh for that handle.

Each call to Attach owns its own connections. Store and destroy the returned handle when replacing UI, changing screens, or otherwise ending the binding's lifetime.

Avoid attaching multiple handles to the same object unless they are deliberately coordinated.

⚙️ API Reference

TextSizer.Measure

TextSizer.Measure(
	textObject: Instance,
	options: Options?
): Vector2

Measures the content and returns the desired pixel size after applying padding and constraints. It does not modify the instance.

TextSizer.Resize

TextSizer.Resize(
	textObject: Instance,
	options: Options?
): Vector2

Measures the object and applies the result to the selected axes of its Size property. Selected axes are written as pixel offsets; axes not selected by Axis retain their existing UDim values.

TextSizer.Attach

TextSizer.Attach(
	textObject: Instance,
	options: Options?
): Handle

Creates a reactive binding and performs an initial resize.

The binding observes changes to:

  • Text
  • TextSize
  • FontFace
  • RichText
  • TextWrapped
  • LineHeight
  • TextBounds
  • TextScaled
  • AutomaticSize

For Y-only sizing, it also watches AbsoluteSize so changes to the available width can update wrapped text height.

Complete Example

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local TextSizer = require(
	ReplicatedStorage:WaitForChild("TextSizer")
)

local label = script.Parent:WaitForChild("Description")
label.TextWrapped = true
label.Size = UDim2.fromOffset(320, 20)

local handle = TextSizer.Attach(label, {
	Axis = Enum.AutomaticSize.Y,
	Padding = Vector2.new(0, 8),
	MinSize = Vector2.new(0, 24),
	MaxSize = Vector2.new(math.huge, 300),
})

task.delay(2, function()
	handle:SetOptions({
		Axis = Enum.AutomaticSize.XY,
		MaxSize = Vector2.new(360, 300),
	})
end)

📝 Notes

  • TextSizer is designed primarily for client-rendered Roblox UI.
  • For the most predictable initial measurement, parent the object into its intended GUI hierarchy before calling Resize or Attach.
  • TextBounds and AbsoluteSize are most meaningful after the object participates in client layout and rendering.
  • TextSizer.Measure returns both dimensions even when Axis selects only one axis.
  • Selected axes are applied as pixel offsets; unselected axes retain their existing UDim values.
  • Attached updates are deferred and coalesced per handle.
  • Avoid attaching multiple handles to the same object unless they are deliberately coordinated.

Roblox API references

License

No license has been selected in this package. Add the license you want to publish under before releasing the repository.


made with ❤️ by biotoxin495

Package Details

Install command (Click to copy)


Version

1.0.0

License

MIT

check_circle

Safe for commercial use

Automated license review — not legal advice.