Typewriter Effect: New property MaxVisibleGraphemes (Live)

We have wanted to support developers with a better way to add a “typewriter” style effect to your text. The most common method used today, is to update the string by adding 1 additional character repeatedly, which can cause some issues. Besides enabling a cool effect, there are additional benefits with using the new solution:

  • More performant and intuitive method to add a typewriter effect to an entire string
  • Remove visual issues with text wrapping, as the text string no longer changes in size/length
  • Eliminate issues with localization as you no longer need to break up the string into individual characters

The solution is a new property called MaxVisibleGraphemes.
This new property will be added to text objects in about 1 week.

NOTE: in the unlikely event that you happen to have an existing child with the same name under a text object, you would need to use a different name.

What does it do

The property is an integer that is initialized to -1 by default. Once it is set to a non-negative value, it will render the same number of graphemes that is specified by the MaxVisibleGraphemes property. This can be applied to text objects with or without rich text enabled.

What is a grapheme?

A grapheme is the smallest functional unit of a writing system. You can read more about definitions of graphemes here.

For better compatibility with localized forms in other languages and emojis, we recommend you that count graphemes in the following way:

local len = 0
for _ in utf8.graphemes(text) do
    len += 1

This will get you a correct graphemes count of 3 when text is


while the string.len() will return 6 in this case.

Code example

local function removeTags(str)
	-- replace line break tags (otherwise grapheme loop will miss those linebreak characters)
	str = str:gsub("<br%s*/>", "\n")
	return (str:gsub("<[^<>]->", ""))

local displayText = removeTags(script.Parent.LocalizedText)

local index = 0
for first, last in utf8.graphemes(displayText) do 
	local grapheme = displayText:sub(first, last) 
	index += 1
	-- Uncomment this statement to get a reveal effect that ignores spaces.
	-- if grapheme ~= " " then
		script.Parent.MaxVisibleGraphemes = index
	-- end

This topic was automatically opened after 14 minutes.

This is really great, but question - would it be possible to allow for us to let individual text characters fade in from above? I.e make it look as if they are “falling down.” Our game uses a custom typewriter, and while this is very promising and intuitive, I would prefer using my own custom typewriter until then.

EDIT: This is what I mean - this is our current game’s typewriter. Take notice to how each character falls down to where it should be!


Amazing! And now I can add a typewriter effect to my dialogue without it glitching for foreign languages i.e. Russian and such when using this property.

A question I want to ask though:

  • Does this solve issues where the topside rectangle with an X character would display due to conflictions with other certain characters i.e. apostrophes or certain devices unable to display such character due to it not being recognized by the device OS?

Very cool update ! What I particularly like about the new feature is that text wrapping is automatic and there will be no issues with changing the line size .Thank you so much for the new feature ! It will help me in the future .


Oh finally! I can still remember how I struggled to implement my one. It had so many issues, and i never thought I would see the day that it’s finally added officially. Thank you!


I’m glad more efforts are being put towards reducing the learning curve for beginners with the recent QOL features.

Just wanted to point out that some tutorial may be outdated by the time this feature releases:
Animating Text (developer.roblox.com)

If the new API additions — including this one: UI Automatic Size (developer.roblox.com) — could be taken into account it’d be swell.


What about utf8.len? utf8.len("AB😊") results in 3.

In the code example it (probably unintentionally) also returns the second result of string.gsub. Wrapping the second call to gsub in parenthesis will cause it to only return the first result.

local function removeTags(str)
	return (string.gsub((string.gsub(str,"<br%s*/>","\n")),"<[^<>]->",""))

This couldn’t have come at a better time for me. I needed to support UTF-8 characters so this really saves me from the complications of that.

If you’re looking to update the article on animating text, could I ask that a delta time approach is taken. Using wait() can be severely inaccurate, as I have previously encountered.


Sick! Now everyone who does typewriter the terrible non-accessible way (set text per grapheme and everything shifts back and forth all the time, making it impossible to read while printed) has no excuse anymore. :stuck_out_tongue:


This definitely would help save time, as well as bugs that occur when having type writer effects. Its a bit confusing for me to process how it works, but its better than scripting a type writer that can bug out all the time.


Nice, I’m not exactly sure what else I can use this for but I’ll definitely look for an area I can implement it in.


This seems pretty good! I’m planning on making a dialogue system for my game, and this will definitely be helpful, even more so because it handles the rich text formatting and all that, so players won’t see stuff like <b>text</ when the text is showing bolded text.


Would it be reasonable to make it possible to achieve this affect backwards to hide graphemes at the beginning of the label? Perhaps this feature could instead be implemented as a rich text tag that hides graphemes, so that any grapheme can be hidden regardless of whether it’s at the end. My use would be to offset the start position of a TextLabel so that multiple TextLabels can line up easily, although rich text could likely solve this for me.

I should probably just switch to rich text for chat messages, but I’m already manually calculating TextWrapped for my game’s chat due to this shortcoming:

Screen space on small mobile devices is valuable, and it’s disappointing to see messages use unneeded lines.


This is great, this takes out so many annoying processes that discouraged me from making stuff like this! Thank you for helping out and adding such an amazing feature!

1 Like

Nice! Love to see these kinds of things get implemented into Studio.
It’s gonna help a lot. The other methods of achieving this effect are somewhat unreliable


For once a type writing effect doesn’t have to be so broken with localization, the text shifting slightly, or the text changing size a bit. This update was most definitely needed.


At this point you’re literally writing my game for me :smile:
I can finally incorporate typewriting effect with custom colored words and no flickering.


1 Like

To be honest this is a bit of a non necessity considering its something that you can readily and easily do by just writing some simple code to do it. Instead of adding utilities like a typewriter effect i feel people would get much more utility out of something like being able to import your own fonts. But i digress, now if i want to i dont have to write my own version of this feature, so there are positives.

This also proves useful to ignore things that string will ignore. I hope that we can see graphemes being used for effectively and more use cases to come.


Wow man, this is so cool :smiley:!
Will there be some API available for the beta?