Typewriter Effect with Blinking Cursor

A versatile typewriter text animation that reveals characters progressively with a customizable blinking cursor, creating a realistic typing effect for any text layer.

Author:
Denis Stefanides
Category:
Text

Expression Code

txt = value; // uses the text directly from the layer
speed = 10; // characters per second
cursor = "|"; // customize this: "|", "_", "█", etc.
blink = true; // set to false if you want a non-blinking cursor that disappears at the end
keepBlinking = true; // if true, cursor keeps blinking after typing is done

charsShown = Math.floor((time - inPoint) * speed); // how many characters to show
typed = txt.substr(0, charsShown); // typed portion of the text

// blink toggles every 0.5 seconds
blinkingState = Math.floor(time * 2) % 2 == 0;

// if blinking is on, use the blinkingState
// if blinking is off, show cursor only while text is still being typed
cursorVisible = blink
  ? blinkingState
  : (charsShown < txt.length); // static cursor only during typing

// final visibility check
showCursor = keepBlinking
  ? cursorVisible // always follow the blink setting
  : (charsShown < txt.length ? cursorVisible : false); // stop showing when done typing

typed + (showCursor ? cursor : ""); // combine text and cursor
jsx

Where to Apply

Apply this expression to the Source Text property of any text layer. It works on any property with a stopwatch, including text and effects.


How It Works

Let’s walk through the expression line by line from top to bottom and explain exactly what happens and when:


txt = value;

This line grabs the current text from the layer. It uses whatever you typed manually into the text layer itself. No need to define your text inside the expression. The expression reads it automatically.


speed = 10;

This controls how fast the characters appear, in characters per second. A value of 10 means 10 characters will be typed every second. You can raise or lower this to speed up or slow down the animation.


cursor = "|";

Here you choose what character will be used as the cursor. It can be anything you want, like "|", "_", or "█". This is what will appear at the end of the text during typing.


blink = true;

This controls whether the cursor should blink or not. If it’s set to true, the cursor will toggle visibility on and off every half-second. If it’s false, the cursor stays solid, but as we’ll see later, only during typing.


keepBlinking = true;

This tells the expression what to do after the text finishes typing.

  • If this is true, the cursor continues blinking (or stays solid) even after the text is fully typed.

  • If it’s false, the cursor disappears once typing is done.


charsShown = Math.floor((time - inPoint) * speed);

This line is where the timing kicks in.
It calculates how many characters should be visible at the current moment by:

  • Taking the time since the layer became visible (time - inPoint)

  • Multiplying that by your speed value

  • Then rounding down using Math.floor()

So for example, at 1.6 seconds with a speed of 10, you'd see 16 characters.


typed = txt.substr(0, charsShown);

Now that we know how many characters should show, we cut the original text using substr.
This only shows characters from position 0 up to the current charsShown count.
So your text builds up over time, letter by letter.


blinkingState = Math.floor(time * 2) % 2 == 0;

This line sets up the blinking rhythm.
It checks the time, multiplies it by 2 (so it checks twice per second), and toggles between true and false.

  • Every 0.5 seconds, the result flips

  • If true, the cursor is visible

  • If false, it’s hidden

This only affects things if blinking is turned on.


cursorVisible = blink ? blinkingState : (charsShown < txt.length);

This is where we decide whether the cursor is visible at all, based on your blink setting.

  • If blink is true, then we follow the blinkingState and the cursor blinks on and off.

  • If blink is false, the cursor doesn't blink, but it will only be shown while typing is still in progress.
    Once the full text is revealed, it disappears.

This line is important because it fixes the old bug where the cursor would stay visible forever if blinking was off.


showCursor = keepBlinking ? cursorVisible : (charsShown < txt.length ? cursorVisible : false);

This is the final decision point. We check:

  • If keepBlinking is true, just follow the normal cursorVisible logic. That means the cursor continues blinking or staying visible even after the animation ends.

  • If keepBlinking is false, then once all the characters are revealed, the cursor will fully disappear, no matter what the blink setting is.

This is how you get full control over whether the cursor keeps going after the animation or not.


typed + (showCursor ? cursor : "");

And finally, this line builds the actual string that will appear on screen.

  • It adds the currently revealed portion of the text (typed)

  • Then it checks if the cursor should be shown

  • If yes, it adds the cursor character at the end

  • If not, it just adds an empty string

So across time, you get something like:

"H|"
"He|"
"Hel|"
"Hell|"
"Hello|"
"Hello"  (cursor disappears, depending on settings)

Frequently asked questions

How do I use the expression on this page?

Just copy the full expression code from the top of this page. Then Alt-click (or Option-click on Mac) the stopwatch on the property you want to animate. Paste the code into the editor and that's it. If you’re not sure which property to use, check the "Where to Apply" section above.

The expression isn’t working. What should I check?

First, make sure your project is using the JavaScript engine (go to File > Project Settings > Expressions). Also double-check for missing characters, and see if the code requires parenting a layer. If so, there will be a comment in the code explaining what needs to be connected.

I’m not sure what the expression does. Where can I learn more?

Take a look at the "How It Works" section on this page. It explains each part of the expression in plain language so you can understand how everything works together.

Can I customize how the expression behaves?

Yes, absolutely. Most expressions include easy-to-edit variables near the top and comments that guide you on what to change. You can also link sliders or checkboxes using Expression Controls if you want more control in the timeline.

Can I apply this to other properties too?

Yes, you can use it on any property with a stopwatch. That includes Position, Scale, Opacity, and also effect settings like Blur or Tint.

Will it work in comps with different frame rates or sizes?

In most cases, yes. These expressions are designed to adapt to your comp’s resolution and frame rate. If anything specific needs adjusting, it’ll be noted in the code or in the "How It Works" section.

Related Expressions

Explore more expressions in this category