
The Text Selector Expression is arguably the most elusive Adobe After Effects Feature and yet it’s its most powerful Text Feature
This blog aims to provide insights and samples into the mystical world of Text Expression Selectors. Readers are assumed to know how to commit to basic application and adjustments using the Text Tool.
Text Expression Selectors are like mysterious orbs in the sky that everyone sees but only few speak about.
With the Range and Wiggly Selectors, we take note of two critical observations –
(1) what gets selected is quite easy to manage and
(2) how each selection is affected by Text Animator Properties is not as easy to manage.
And it is the latter where the Text Expression Selector shines.
Before we proceed, let’s define Text Objects
Text Object : A group of one or more text characters defined by the Based On Parameter. The Based On Drop Menu provides 4 Group Types – Characters, Characters Excluding Spaces, Words and Lines.
You may also create your own Text Objects using any one of these as the basis for grouping.
With Range Selectors, how each Text Object is affected and how it animates involves a complex formula comprising of the parameters in the Range Selector and Advanced Sections of the Text Animator Group.
With the Expression Selector, it’s actually a lot easier to manage how Text Objects are affected by the Text Animator Property. The tricky part is that you have to write your own selection and animation functions which are built-into Text Expression selectors that others provide for free or as a paid product.
What you will want to take away from this blog is that Text Expression Selectors allow for more control over how each Text Object is affected by Text Animator Properties.
Let’s take a look at a few Text Expression Selector examples because showing by example is always a good way for visual stuff.
What I’d like you to take away from watching this video is to appreciate how text characters are selected and animated with Expression Selectors and how the results here differ from results obtained with using Range and Wiggly Selectors.
Can’t see this video? Click here! If you are in Safari, hover over “Safari” in your menu, click “settings for creativecow.net” and toggle the “Auto-Play” to “Allow All Auto-Play”.
To add some brain work into the mix, pause at appropriate samples and ask yourself how you would re-create a sample using the Range and/or Wiggly Selectors.
Chances are you will not be able to replicate most, if any of these Text Animations if you used Range and/or Wiggly Selectors. And with this, the motivation is for you to start venturing into the world of Text Expression Selectors, if you have not done so.
Can’t see this video? Click here! If you are in Safari, hover over “Safari” in your menu, click “settings for creativecow.net” and toggle the “Auto-Play” to “Allow All Auto-Play”.
Text Selectors in After Effects
Most After Effects users are familiar with its Text Tool and its Range and Wiggly Selectors. However, only a tiny percentage have used the Text Expression Selector.
Advanced After Effects users will have used Expressions in their Range and Wiggly Selectors and even the Source Text Property. Now, let’s take a look at the Text Expression Selector.
Text Expression Selector
Default Text Expression Selector
When you first apply the Text Expression Selector to a Text Animator Group, this is the expression that is applied –
selectorValue * textIndex/textTotal
Unfortunately, for almost every user that sees this for the first time, it’s a cryptic line of code. So, let’s try to clear this up – what you see are essentially three reserved variables; they are functions built into the After Effects Expression Engine.
Before we proceed, let’s define these variables/functions –
selectorValue * textIndex/textTotal
selectorValue : This is defined as a percentage of the value of the Text Animator Property you apply to the Animator Group.
The selectorValue * ratio is calculated for each Text Object on every frame. I use two different ranges for the selectorValue : 0 and 100 or -100 and 100. They provide different results and you should experiment further appreciate their nuances.
The ideal way to experiment is to link/replace selectorValue with a Slider Control while leaving the other variables in the default expression as is.
There is yet another range that is useful and this accounts for the number of Text Objects available. As this is an introductory article, I will leave this use-case for later.
textIndex : This defines the index of a single Text Object regardless of the type of Text Object selected with the Based On Drop Menu. Each Text Object receives a distinct index.
textTotal : This is the total number of Text Objects based on the Based On Parameter.
Now, let’s proceed with some hands-on work …
Setting up the ground work
** Leave the Based On Parameter at its default selection, Characters.
** Disable the Range Selector by clicking on its Visibility Icon.
** Input the following as your Text Layer’s Input String –
1234567890
** Set the Text Layer’s Paragraph Justification to Left Aligned
** Apply the following Expression to the Text Layer’s Anchor Point
const myLayerRect = thisLayer.sourceRectAtTime();
[myLayerRect.left , myLayerRect.top + myLayerRect.height]
The last two procedures are applied only to ensure we have identical results – they have no bearing on results sitrctly occurring from the Text Selector Expression.
If we have a future blog on the Text Expression Selector, we will look at combining Range and Expression Selectors – for now, let’s focus on foundational topics.
With the default Text Expression Selector applied, add the Position Text Animator Property and set its xValue to 100.

If you applied the Position Text Animator Property, you will notice that the first Text Object is offset by 10 pixels and subsequent Text Objects are moved by 10 pixels * the index of the Text Object.
Let’s compare the Before & After…
Default Text Input String without a Text Animator Group.

Expression Selector applied with Text Animator Position Property set to [100,0 ].

Interesting result and a casual but useful observation …
So, the default Text Selector Expression applies the selectorValue of the Text Animator Property to Text Objects much like what the Range Selector does when its Shape Parameter is set to Ramp Up and its Offset value is set to 0.
Range Selector Expression with Text Animator Position Property set to [100,0] – Shape Type set to Ramp Up.

Let’s return focus to the Text Expression Selector …
Default Text Expression Selector
selectorValue * textIndex/textTotal
The default expression applied to the Text Expression Selector does not animate Text Objects. What the default expression does is distribute the selectorValue across the input text, weighted by each Text Object’s Index – smaller indices receive a smaller weighting (lower value) and Text Objects with larger indices receive a higher weighting (higher value).
To animate Text Objects, we animate at least one of the three variables/functions in the default expression.
Additionally, textTotal should not be 0 as this will result in an expression error – a divide by 0 error. Other than this restriction, you should be able to experiment with different static and keyframed values to better understand the mechanics of the Text Expression Selector.
A couple of Helper Notes
Link each of the three variable/functions to a Slider Control and experiment by changing the value of one Slider Control at a time.
Then, keyframe or set non-animating values for each variable/function.
Key Takeaway …
What I’d like you to keep an eye and your mind about is how you are able to control the value that is applied to Text Objects and the selection of Text Objects – they are independent when using the Text Expression Selector. This allows you to have greater control over what gets selected and and how they animate. Contrast with with using the Range Selector where it is a lot difficult to control how Text Objects animate.
If there is interest, I will dive deeper in a future Blog Post. I will also be launching a set of Text Expression Selectors. These will be distributed either as Animation Presets (FFX) or applied as Text Expression Selectors within an AEP.
Below are tutorials by three brilliant minds that should motivate you to tak a further look into at Text Expression Selectors.
At the end, I share two Text Expression Selector expression
** select Letters/Numbers of your Input String
** select one or more words/phrases of your Input String
The following tutorial is a good introduction and it’s got a few examples you can easily apply to your work.
Text animator’s Expression selector explained | Quick After Effects Tip
by ruthlessly quick AE tips
Can’t see this video? Click here! If you are in Safari, hover over “Safari” in your menu, click “settings for creativecow.net” and toggle the “Auto-Play” to “Allow All Auto-Play”.
Before diving into Luis’ tutorial, you should dowload and take a look at his Expression to get an idea of what it does. His technique is excellent but he combines the use of the Text Expression Selector in ways which WILL confuse you if you are starting out in this area and you are not familiar with his Expression.
After Effects Rigging – Expression Selector
by Luis Martínez
Can’t see this video? Click here! If you are in Safari, hover over “Safari” in your menu, click “settings for creativecow.net” and toggle the “Auto-Play” to “Allow All Auto-Play”.
Ilir takes matters into his own world. His Text Expression Selector Tutorials are quite extraordinary.
Advanced AE Expressions – Multiline Text Borders – Part 1 –
by Ilir Beqiri
Can’t see this video? Click here! If you are in Safari, hover over “Safari” in your menu, click “settings for creativecow.net” and toggle the “Auto-Play” to “Allow All Auto-Play”.
Select single letter/number Expression
- includes Case-Sensitive checkbox
This Expression is applied to the Amount Property of the Text Expression Selector.
This Expression allows you to select Individual Text Characters based on your input.
You can specify the character and its instance/occurrence in the Input Text String.
Each entry is “letter:occurrence”. If no occurrence is specified, all instances of that Letter/Number are selected. Use a comma to separate different search Letters/Numbers.
Sample Use-Case …
Search Array – “A:1”, “T”, “o” with Case-sensitive Checkbox enabled. Matches first instance of “A” and all instances of “o” and “T”.

// START OF EXPRESSION
// ** === SET BASED ON TO Characters ===
// Checkbox for Case-Sensitive matching
var CaseSensitive = effect(“CaseSensitive”)(“ADBE Checkbox Control-0001”); // |v=0|
// === Your search terms here ===
var searchArray = [
“A:1”, // only the 1st “a” (upper or lower – depends on case-sensitive checkbox)
“b”, // all “b”s
“o” // all “o”s
];
var src = text.sourceText.toString();
var flat = [];
for (var i = 0; i < src.length; i++) {
var c = src.charAt(i);
if (c !== “\r” && c !== “\n”) {
flat.push(c);
}
}
var n = flat.length;
// Simplified matching: if case-sensitive, match exactly.
// Otherwise compare both characters lowercased.
function matchChar(c, term) {
if (CaseSensitive == 1) {
return (c === term);
} else {
return (c.toLowerCase() === term.toLowerCase());
}
}
var matched = [];
for (var t = 0; t < searchArray.length; t++) {
var entry = searchArray[t].trim();
if (!entry) continue;
var parts = entry.split(“:”);
var letter = parts[0];
if (parts.length > 1 && parts[1].trim()) {
var targetOccur = parseInt(parts[1], 10);
var count = 0;
for (var i = 0; i < n; i++) {
if (matchChar(flat[i], letter)) {
count++;
if (count === targetOccur) {
matched.push(i);
break;
}
}
}
} else {
for (var i = 0; i < n; i++) {
if (matchChar(flat[i], letter)) {
matched.push(i);
}
}
}
}
var idx0 = textIndex – 1;
(matched.indexOf(idx0) !== -1) ? 100 : 0;
// END OF EXPRESSION
// Copyright 2025 – Roland Kahlenberg
/*
For personal and professional use. Not to be sold, resold, exchanged, shared on personal or publicly accessible sites
.*/
Select word/phrases Text Selector Expression
- includes Partial Word Match Checkbox
Sample Use-Case …
Search Array – “Effect”, “practical advice”.
PartialWordMatch – unchecked

Sample Use-Case …
Search Array – “Effect”, “practical advice”.
PartialWordMatch – checked

// START OF EXPRESSION
/* This Expression is applied to the Amount Property of the Text Expression Selector.
This Expression allows you to select word(s)/phrases.
** You can specify the character and its instance/occurrence in the Input Text String.
** Partial Word Match is supported via a Checkbox.
** By default, the Expression ignores punctuation marks that follow the last word of a matched word/phrase.
** SET BASED ON TO Characters Excluding Spaces
** Literal Word Selection
*/
var PartialWordMatch = effect(“PartialWordMatch”)(“ADBE Checkbox Control-0001”) == 1;
var ExcludePunctuation = 1;
var SearchArray = [
“Effect” ,
“Note:2”
];
var SourceText = text.sourceText.toString();
var Lines = SourceText.split(“\n”);
var Words = [];
for (var l = 0; l < Lines.length; l++) {
var line = Lines[l].trim();
if (!line) continue;
var lineWords = line.split(/\s+/).filter(function(w){ return w; });
Words = Words.concat(lineWords);
}
var MatchedWordIndices = [];
var MatchMetadata = [];
for (var i = 0; i < SearchArray.length; i++) {
var entry = SearchArray[i].trim();
if (!entry) continue;
var parts = entry.split(“:”);
var phrase = parts[0];
var targetWords = phrase.split(/\s+/).filter(function(w){ return w; });
var last = targetWords.length – 1;
var trailingP = /[.,!?;:]$/.test(targetWords[last]);
var cleanWords = targetWords.map(function(w, idx){
return (ExcludePunctuation && !trailingP && idx === last)
? w.replace(/[.,!?;:]$/, “”)
: w;
});
function scan(limitNth) {
var count = 0;
for (var w = 0; w <= Words.length – targetWords.length; w++) {
var ok = true;
for (var tw = 0; tw < targetWords.length; tw++) {
var word = Words[w + tw];
var wordClean = (ExcludePunctuation && !trailingP && tw === last)
? word.replace(/[.,!?;:]$/, “”)
: word;
if (PartialWordMatch) {
var off = wordClean.indexOf(cleanWords[tw]);
if (off < 0) { ok = false; break; }
} else {
if (wordClean !== cleanWords[tw]) { ok = false; break; }
}
}
if (!ok) continue;
count++;
if (limitNth == null || count === limitNth) {
for (var tw = 0; tw < targetWords.length; tw++) {
var idxFlat = w + tw;
var word = Words[idxFlat];
var wordClean = (ExcludePunctuation && !trailingP && tw === last)
? word.replace(/[.,!?;:]$/, “”)
: word;
var off = PartialWordMatch
? wordClean.indexOf(cleanWords[tw])
: 0;
var length = PartialWordMatch
? cleanWords[tw].length
: wordClean.length;
MatchedWordIndices.push(idxFlat);
MatchMetadata.push({
wordIndex: idxFlat,
matchOffset: off,
matchLength: length
});
}
if (limitNth != null) break;
}
}
}
if (parts.length > 1 && parts[1].trim()) {
var n = parseInt(parts[1], 10);
if (!isNaN(n) && n > 0) scan(n);
} else {
scan(null);
}
}
var Boundaries = [];
var charIndex = 1;
for (var l = 0; l < Lines.length; l++) {
var line = Lines[l];
if (!line.trim()) continue;
var lineWords = line.split(/\s+/).filter(function(w){ return w; });
for (var w = 0; w < lineWords.length; w++) {
var word = lineWords[w];
var start = charIndex;
var fullLength = word.length;
charIndex += fullLength;
var end = start + fullLength – 1;
var from = Boundaries.length
? Boundaries[Boundaries.length – 1].wordIndex + 1
: 0;
var flat = Words.indexOf(word, from);
Boundaries.push({
start: start,
end: end,
wordIndex: flat
});
}
if (l < Lines.length – 1 && Lines[l+1].trim()) {
charIndex++;
}
}
var isMatch = false;
for (var b = 0; b < Boundaries.length; b++) {
var B = Boundaries[b];
var idx = B.wordIndex;
var mi = MatchedWordIndices.indexOf(idx);
if (mi === -1) continue;
var meta = MatchMetadata[mi];
var matchStart = B.start + meta.matchOffset;
var matchEnd = matchStart + meta.matchLength – 1;
if (textIndex >= matchStart && textIndex <= matchEnd) {
isMatch = true;
break;
}
}
isMatch ? 100 : 0
// END OF EXPRESSION
// Copyright 2025 – Roland Kahlenberg
/*
For personal and professional use. Not to be sold, resold, exchanged, shared on personal or publicly accessible sites. */
Enjoying the news? Sign up for the Creative COW Newsletter!
Sign up for the Creative COW newsletter and get weekly updates on industry news, forum highlights, jobs, inspirational tutorials, tips, burning questions, and more! Receive bulletins from the largest, longest-running community dedicated to supporting professionals working in film, video, and audio.
Enter your email address, and your first and last name below!

Responses