Skip to content

Proposal: CSS Text Transitions & Animations #13689

Open

Description

@kbabbitt

Explainer

This is a first round of thinking aimed at addressing the need for animation effects at sub-element units. These effects are showing up in use cases such as chat interfaces and textual progress animations.

Example showing chat interface text flow-in

Example ellipsis animation suggesting a message is incoming

Example grayscale shimmer of "Loading..." text

The challenge Web authors face today is that the unit of currency for animations on the Web is the element. Achieving these effects thus requires authors to split their text into separate elements, such as <span>s. This generates extra overhead in DOM, style calculation, and layout. It also fragments the markup away from its natural semantic structure, which makes it harder for accessibility tools to work with and may necessitate ARIA workarounds.


The core idea is to extend CSS Transitions & Animations with a few new properties:

transition-text-interval: <time [0s,∞]>#
transition-text-unit: [ none | character | word | line ]#
transition-text: [ <transition-text-interval> || <transition-text-unit> ]#

animation-text-interval: <time [0s,∞]>#
animation-text-unit: [ none | character | word | line ]#
animation-text: [ <animation-text-interval> || <animation-text-unit> ]#

These properties exhibit the same list behavior as other transition-* and animation-* properties.

My general mental model for how these would work is:

  1. The end state of any text transition or animation should be the same as if the changed property were applied to the element as a whole.
  2. Barring (1), the effect should proceed as if individual animations were applied to inline elements subdividing the content.

There will be cases where those two principles come into conflict. For example, a scale transform animation will behave differently when applied to three neighboring words individually versus three words treated as a single unit. We would need to account for such cases.

It also means that not all properties would be eligible for a text transition. For example, width and height have no effect when applied to an inline element, so if a transition with text transition properties were specified on width on a <div>, the text transition properties would be ignored, and it would be treated as a normal transition.

There are, of course, lots of interesting questions to consider:

  • How to handle descendant elements, and whether different categories of descendants should be treated differently (inline vs. block; replaced vs. not)
  • How to handle changes to text content during and after a text animation
  • How much detail the specification needs to provide in cases where animating individual text units would yield a different end state than animating the text as a whole

There are some ideas for handling these in the explainer.


Questions for discussion in the Working Group:

  • Is this something we should pursue in CSS?
  • Does the general shape of the API make sense?
  • How should the new properties integrate with the existing transition and animation shorthands?
  • What other important questions or use cases should be considered?

Activity

  1. self-assigned this
    on Mar 19, 2026
  2. astearns commented on Mar 19, 2026

    @astearns
    Member

    This is a big feature, and overkill for the animated dots.

    Some initial questions:

    1. How would we represent intermediate steps here?
    2. How does the parent animation interact with children that have set the animating property to a different value?
    3. How would this interact with text shaping, or other changes in layout for adjacent characters that momentarily have separate values applied?
    4. Have you consulted with the SplitText folks about solving this problem in-browser?
  3. bramus commented on Mar 20, 2026

    @bramus
    Contributor

    A while ago I made an inventory of which features GSAP offers that are hard or impossible to do with CSS. “Split text into units” was one of the gaps I identified.

    Here are all their demos that could benefit from this being added into CSS:

  4. kbabbitt commented on Mar 23, 2026

    @kbabbitt
    CollaboratorAuthor

    Definitely recognize and agree that this would be a big feature. At this point I'm mostly looking to stress-test the idea and understand whether it's feasible and interesting to pursue. Good questions @astearns; I'll offer some initial thoughts:

    1. How would we represent intermediate steps here?

    To clarify, are you asking about computed value? If so, I think the most consistent and predictable thing would be that computed value would follow the value of the first animated unit, and the remainder of the text animation would be (from a values standpoint) a presentational effect. That way, the values surfaced to inheritance and on the OM look the same regardless of text length, and they are consistent with what would be exposed if the effect were running without a text transition. I would also be open to having an API that surfaces intermediate values on subsequent text units, but I think that's something other than the element's computed value.

    1. How does the parent animation interact with children that have set the animating property to a different value?

    For a lot of properties, I think the answer is probably similar to what happens with whole-element transitions and animations. If the child element would inherit a value from the parent, it participates in the effect.

    opacity and similar are more interesting cases, because they're not inherited but implicitly affect the entire subtree. These I think would need to be special cased such that words in descendant elements do participate in the effect.

    1. How would this interact with text shaping, or other changes in layout for adjacent characters that momentarily have separate values applied?

    I'll admit text shaping is not an area I have a ton of expertise in. But I think that breaking text into units for these effects happens downstream of text shaping and line breaking, regardless of the unit the author has selected. For example, I would not break ligatures in English and similar languages to animate each letter individually.

    1. Have you consulted with the SplitText folks about solving this problem in-browser?

    I haven't yet; this proposal is still pretty warm out of the oven.

  5. ydaniv commented on Mar 24, 2026

    @ydaniv
    Member

    Thanks for proposing, @kbabbitt ! This is indeed worth pursuing, but IMHO you seem to have bound together several, more basic, animation concepts/building blocks - that are still missing from the platform - into a single proposal.

    In order to create these effects we need to:

    1. Probably reuse the Intl.Segmenter API (which also allows for sentence BTW) for breaking text contents.
    2. Be able to break inline text boxes into separate boxes and blockify them, so that we can apply any property on them, namely transform. We also need to have a reference box for each part so that we can properly calculate effects that rely on those boxes, like resolving percentages etc.
    3. Create a general mechanism for controlling a Sequence/Group of effects, currently WIP. There's an open issue on progressing with these APIs.
    4. Create a general mechanism for staggering effects/values. There are a couple of issues, also WIP, on progressing with that, but still at very early stage.
    5. Be able to select the boxes generated in step 2 for reuse inside step 3.

    Then you'd, probably, have a proper platform for generally applying any effect you want, instead of trying to solve specific use-cases.
    For example, you did not account for progressing these effects using progress-based timelines (i.e. view()/scroll()).
    Another key feature that's missing is controlling the staggering effect. Your OP currently accounts just for linear staggering from start to end, and just the offsets (i.e. delay).

    To sum it up, I'd suggest we explore the options of creating general-purpose tooling for creating these effects, and the rest will follow.


    /fyi @birtles @flackr for thoughts

  6. moved this from Agenda+ FTF to Tuesday morning in CSSWG spring 2026 meeting agendaon Mar 25, 2026
  7. closed this as completedby moving to Tuesday morning in CSSWG spring 2026 meeting agendaon Mar 25, 2026
  8. moved this from Tuesday morning to Tues morning in CSSWG spring 2026 meeting agendaon Mar 25, 2026
  9. kbabbitt commented on Mar 27, 2026

    @kbabbitt
    CollaboratorAuthor

    Thanks @ydaniv, I'm definitely open to breaking this down into building blocks where possible.

    Re: Intl.Segmenter - it's notable that this supports splitting on graphemes, which would help with the point Alan raised with regard to text shaping. On the other hand, the specific comment I heard when discussing this with an internal partner was that it was unfortunate they had to split content up and stitch it back together after the animation effect concludes - this is one of the reasons I'm hoping we can offer a way to achieve these effects declaratively.

    Re: break inline text boxes into separate boxes and blockify them - can you expand on this? I'd be a little concerned about the performance implications if, for example, we were to make chunks of text addressable by selectors as if they were elements.

    Declarative syntaxes for group effects and staggering timings definitely sound like pieces we would want. Starting with those makes a lot of sense to me - that would give us declarative pieces we could apply to the existing approach of breaking text units into individual elements, and maybe in parallel we can iterate more on how to do the text segmentation.

  10. ydaniv commented on Mar 28, 2026

    @ydaniv
    Member

    On the other hand, the specific comment I heard when discussing this with an internal partner was that it was unfortunate they had to split content up and stitch it back together after the animation effect concludes - this is one of the reasons I'm hoping we can offer a way to achieve these effects declaratively.

    Of course, currently text animations are very unfriendly towards SEO and A11Y. Another option I know of is to keep the original text visually hidden, and make a copy of the text for the effect and wrap it with an element with an aria-hidden="true". But it's bad anyway.

    Re: break inline text boxes into separate boxes and blockify them - can you expand on this? I'd be a little concerned about the performance implications if, for example, we were to make chunks of text addressable by selectors as if they were elements.

    If you want to animate the transform property, you have to not only wrap parts with <span>s, but also set display: inline-block on each one of them, since transform does not work on inline boxes.
    I agree with your concerns about performance and allow arbitrary text parts selection. That is indeed one of the main obstacles we need to pass.
    I suppose we'd need some opt-in mechanism to allow part selection and bockifying. This could perhaps behave similar to will-change? Like will-change: text-effect?

    Declarative syntaxes for group effects and staggering timings definitely sound like pieces we would want. Starting with those makes a lot of sense to me - that would give us declarative pieces we could apply to the existing approach of breaking text units into individual elements, and maybe in parallel we can iterate more on how to do the text segmentation.

    wOOt!

  11. smfr commented on Mar 31, 2026

    @smfr
    Contributor

    One thing about CSS animations and transitions that I think needs to be preserved for text animation is that all intermediate states can be represented as static styles (either just with plain properties most of the time, or some kind of mix() function otherwise). That implies that we'd have to resolve the problem of how to apply a transform to a text fragment (word, letter etc), and transforms currently only apply to atomic boxes.

  12. css-meeting-bot commented on Mar 31, 2026

    @css-meeting-bot
    Member

    The CSS Working Group just discussed Proposal: CSS Text Transitions & Animations.

    The full IRC log of that discussion <Rossen> Zakim, open queue
    <Zakim> ok, Rossen, the speaker queue is open
    <TabAtkins> kbabbitt: basic problem i'm looking to solve is being able to do aniamtions/transitions at sub-element level
    <TabAtkins> kbabbitt: becom epopular with some streaming text interfaces
    <TabAtkins> kbabbitt: or loading animations with a shimmer effect
    <TabAtkins> kbabbitt: authors have done this by dividing all the content into spans
    <TabAtkins> kbabbitt: not great for perf, lots of excess style calc
    <TabAtkins> kbabbitt: not good for a11y
    <TabAtkins> kbabbitt: would be simpler for authors to have a few simple properties that achieve these effects
    <TabAtkins> kbabbitt: alan pointed out this is a lot of work, yehonatan pointed out proposals for some of these things
    <flackr> q+
    <TabAtkins> kbabbitt: specifically grouping effects on element-by-element basis and sequencing these things
    <TabAtkins> kbabbitt: syncing and choreographing animations
    <TabAtkins> kbabbitt: can start with those
    <TabAtkins> kbabbitt: need a few pieces still
    <TabAtkins> kbabbitt: ref boxes for positional animations.
    <TabAtkins> kbabbitt: transforms, if you blockify they're animating relative to the postiion of their block not their original position
    <TabAtkins> kbabbitt: another, implicit assingment of group effects
    <TabAtkins> kbabbitt: proposal was assinging ordinals to these elements
    <ydaniv> q+
    <TabAtkins> kbabbitt: would b enice to say "all of my children, assign everyone this animation"
    <TabAtkins> kbabbitt: and the other effect is some way to segment text
    <TabAtkins> kbabbitt: i don't want to get into a world where we're addressing text uinits with selectors. back where we started with style expense
    <florian> q+
    <TabAtkins> kbabbitt: so think we need some way of talking about text more generally that just applies transforms or animations
    <TabAtkins> kbabbitt: addressing some issues, nested elements
    <TabAtkins> kbabbitt: want animations to apply thru descendent elements to all text bits
    <TabAtkins> kbabbitt: mutations of the text while animation is in progress...
    <lea> q?
    <lea> q+
    <TabAtkins> kbabbitt: appending content is easy enough, replacing is trickier
    <TabAtkins> kbabbitt: i'd probably just treat as a brand neew anim
    <TabAtkins> kbabbitt: qs about generated content, and animation/transition events
    <TabAtkins> kbabbitt: i think for compat sake we keep the events firing
    <TabAtkins> kbabbitt: they'll fire at the end of all the text units
    <TabAtkins> kbabbitt: so want to hear about concerns, if we're in the right direction, etc
    <Rossen> ack flackr
    <TabAtkins> flackr: we already have pseudo-elements for some pieces of text, ::first-letter/::first-line, etc
    <TabAtkins> flackr: have you thought thru those? havne't considered expense
    <TabAtkins> kbabbitt: expense is my main concern
    <TabAtkins> kbabbitt: avoid ahving to do a full cascade, like ::nth-word(). even in non-animation potentially v expensive
    <astearns> q+
    <Rossen> ack ydaniv
    <TabAtkins> ydaniv: i gave a few comments in the issue.
    <TabAtkins> ydaniv: like kevin said, regarding sync/staggering/sequencing, i think it's something we want to solve more generally for animations
    <TabAtkins> ydaniv: nothing currently regarding transitions, but we probably want to solve it there as well
    <TabAtkins> ydaniv: regarding targeting text parts. all these libraries today split the text (hopefully with Intl.segmenter)
    <TabAtkins> ydaniv: and then they wrap each part in a span
    <TabAtkins> ydaniv: then make it inline-block
    <TabAtkins> ydaniv: need to blockify everything so you get transforms
    <TabAtkins> ydaniv: if we want to solve these effects we need to somehow blockify the components. then can do something like what rob suggested, have a way of targetting these parts
    <TabAtkins> ydaniv: i know webkit jsut released a new inline-block engine
    <fantasai> no, it was new implementation of block-splitting-inline-boxes code ...
    <TabAtkins> ydaniv: i suggested something in the issue something like 'will-change:text-effects'
    <fantasai> much less interesting :)
    <TabAtkins> ydaniv: something that authors will use sparingly and UAs can maintain. if it's overused, maybe something the UA can control
    <Rossen> ack florian
    <TabAtkins> florian: havne't thought deeply, but i like the concept
    <TabAtkins> florian: from i18n pov, there's considerable variation about letter vs word, especially word
    <TabAtkins> florian: but word in english, chinese, japanese, thai all different. probably want to set it up so authros don't have to think too hard.
    <TabAtkins> florian: a bunch of langs have space-seaprate words, not all. thai space-separates setnences, don't wnat to get sentences when you say "word"
    <TabAtkins> florian: "word-level" is tricky but useful. do we want to spend time deffining this well, or is it too complex?
    <TabAtkins> kbabbitt: one other reason i think about it this way, layout engine alreayd needs to know graphemes, ligatures, word breaks, line breaks
    <TabAtkins> kbabbitt: so i'm hoping by using a higher-level concept we can just reuse that logic
    <Rossen> ack lea
    <ntim> p
    <ntim> p+
    <TabAtkins> lea: to florian, there is Intl.segmenter, UA already has these mechanics and exposes it to authors. presumably would do the same
    <TabAtkins> lea: for transofrm effects, was mentioned it need to be blockified. a lot of effects weren't using transofrms, today could use highlight API, doens't need blockification
    <castastrophe> florian: what would you think about alternative naming like “chunk” or “set”?
    <TabAtkins> lea: am wondering if we can reduce the number of primitives, complexity is a common complaint
    <fantasai> s/florian:/florian,/
    <TabAtkins> lea: there is precedent today where we have pseudo-elements restricted to an allowlist of props
    <TabAtkins> lea: wonder if it would alleviate perf concenrs to heavily limit the props allowed
    <TabAtkins> lea: would be more extnesible in the future, while new primitives require us to edit the feature when we want to do mor epowerful
    <TabAtkins> q+
    <Rossen> ack astearns
    <TabAtkins> astearns: i don't like thi sproposal, animating text is wrong and you shouldn't do it
    <romain> +1
    <TabAtkins> astearns: i do worry we could get into a sitaution where we're chasing a popular effect, and by the time it's specified/implemented the effect will be out of vogue
    <TabAtkins> astearns: i did mention in the issue we should talk to the people with those libraries, might be low-hanging fruit that could make their scripting solution better
    <ydaniv> +1 to astearns but I think text effects in general have been around for quite a while (:
    <Rossen> ack fantasai
    <Zakim> fantasai, you wanted to point at fill-stroke
    <fantasai> https://drafts.csswg.org/fill-stroke/
    <TabAtkins> fantasai: there is a fill-stroke spec that hans't seen much attention but would allow for some of the animation effects
    <TabAtkins> fantasai: allows image fills, etc. might address some use-cases
    <Rossen> ack TabAtkins
    <ntim> brain dumping an idea: `text-effect: name 0.3s ease;`, `@starting-style { p::text-effect(name) { transform: scale(0); } }`
    <fantasai> TabAtkins: Multiple people talked about :nth-word. I agree with kbabbitt, even if there weren't perf issues, I think the usability of the feature is better by having a dedicated system
    <fantasai> TabAtkins: because of cases like appending vs adding, etc. We can't address in selectors.
    <fantasai> TabAtkins: but a UA could handle how it wants to do all the animations in these cases
    <fantasai> TabAtkins: So if we want to do this feature, it is best done with a special-case word animator, not via generic animations + pseudos
    <Rossen> q?
    <TabAtkins> (not disagreeing with what Tim just suggested, fwiw)
    <TabAtkins> arronei: not disagreeing with Alan that the effect might be overused currently. but think we should continue with it to see what problems we run into
    <TabAtkins> arronei: as we do, should make very careful to consider intl text. probably where we'll hit the most problems, not necessarily from impl concerns but from a user
    <Kurt> prefers-reduced-motion should respect this, which should address a lot of concerns
    <TabAtkins> +1 to Kurt
    <TabAtkins> astearns: one comment on pref for solution that doen't use ::nth-word()/etc. could be fine but risks getting half the use-cases, but other half can't be extended to fit. so some amount of plumbing needs to be exposed, i think
    <TabAtkins> Rossen: Hearing interest and concerns
    <TabAtkins> Rossen: were you looking for a resolution, or just introing the concept?
    <TabAtkins> kbabbitt: just presenting for now
    <TabAtkins> <br dur=17min>
  13. kbabbitt commented on Apr 14, 2026

    @kbabbitt
    CollaboratorAuthor

    One thing about CSS animations and transitions that I think needs to be preserved for text animation is that all intermediate states can be represented as static styles (either just with plain properties most of the time, or some kind of mix() function otherwise). That implies that we'd have to resolve the problem of how to apply a transform to a text fragment (word, letter etc), and transforms currently only apply to atomic boxes.

    @smfr what would the use case(s) be for representing intermediate states of text animations using static styles? Not disagreeing that it's a nice property of what's currently supported, but I think articulating why it's useful would help inform how we carry that forward into text animations.

  14. smfr commented on Apr 14, 2026

    @smfr
    Contributor

    what would the use case(s) be for representing intermediate states of text animations using static styles

    An author should be able to call getComputedStyle and set the result as static style to "freeze" an animation. Also, from an implementation perspective, animations work by blending style values and then rendering with the resulting style, so those intermediate style values need to be representable in the engine.

  15. kbabbitt commented on Jul 27, 2026

    @kbabbitt
    CollaboratorAuthor

    When I presented this proposal at the April F2F, a couple of people brought up ::nth-word() as an alternate construct for these scenarios. I was resistant at the time due to concerns about the performance of styling each word. But since then, I've prototyped ::nth-word() and found it to be comparable in performance to the Text Animations prototype I had built previously. Which makes sense: The cost to style each word via ::nth-word() should not be any worse than the cost to style each word via a wrapping <span>, and in Blink at least, pseudo-elements get selector-matched at the same time as their base elements so that we can discover what pseudos we need to instantiate, and ::nth-word() benefits from that optimization. So that concern has gone away.

    ::nth-letter() and ::nth-word() were already being discussed in #3208, so I will continue there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions