# Text animation

> Animate a hero headline one word or one letter at a time on scroll. Try it live, then copy the code for React, Vue, Astro or plain JavaScript.

The headline enters one word or one letter at a time. Screen readers read the full title one time.

* React

  ```jsx
  import { Reveal } from "@awesome-reveal/react";
  import { slide } from "@awesome-reveal/react/keyframes";
  import { Fragment } from "react";

  export function Hero({ title }) {
    return (
      <h1>
        <span className="visually-hidden">{title}</span>
        <Reveal
          as="span"
          className="words"
          keyframes={slide()}
          stagger={80}
          aria-hidden="true"
        >
          {title.split(" ").map((word, index) => (
            <Fragment key={index}>
              {index > 0 && " "}
              <span>{word}</span>
            </Fragment>
          ))}
        </Reveal>
      </h1>
    );
  }
  ```

* Vue

  ```vue
  <script setup>
  import { Reveal } from "@awesome-reveal/vue";
  import { slide } from "@awesome-reveal/vue/keyframes";

  defineProps(["title"]);
  </script>

  <template>
    <h1>
      <span class="visually-hidden">{{ title }}</span>
      <Reveal
        as="span"
        class="words"
        :keyframes="slide()"
        :stagger="80"
        aria-hidden="true"
      >
        <template v-for="(word, index) in title.split(' ')" :key="index">
          {{ index > 0 ? " " : "" }}<span>{{ word }}</span>
        </template>
      </Reveal>
    </h1>
  </template>
  ```

* Astro

  ```astro
  ---
  import { Reveal } from "@awesome-reveal/astro";
  import { slide } from "@awesome-reveal/astro/keyframes";

  const { title } = Astro.props;
  ---

  <h1>
    <span class="visually-hidden">{title}</span>
    <Reveal
      as="span"
      class="words"
      keyframes={slide()}
      stagger={80}
      aria-hidden="true"
    >
      {title.split(" ").map((word, index) => (
        <>{index > 0 && " "}<span>{word}</span></>
      ))}
    </Reveal>
  </h1>
  ```

* JavaScript

  ```html
  <h1>
    <span class="visually-hidden">Build pages that feel alive</span>
    <span class="words" aria-hidden="true">Build pages that feel alive</span>
  </h1>
  ```

  ```js
  import { reveal } from "@awesome-reveal/core";
  import { slide } from "@awesome-reveal/core/keyframes";

  const words = document.querySelector("h1 .words");
  words.replaceChildren(
    ...words.textContent.split(" ").flatMap((word, index) => {
      const span = document.createElement("span");
      span.textContent = word;
      return index > 0 ? [" ", span] : [span];
    }),
  );

  reveal(words, {
    keyframes: slide(),
    stagger: 80,
  });
  ```

```css
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Transforms do not apply to inline elements. */
.words > span {
  display: inline-block;
}
```

## Keep the text accessible

Split text is a problem for screen readers: they can read each word or letter as a separate item. The code prevents this in two steps:

1. The full title is in an element that only screen readers see. `visually-hidden` is a class in `styles.css`. It hides the element visually, but keeps it for assistive technology.
2. The animated copy has `aria-hidden`.

Then screen readers read the title one time, and find-in-page finds the full title.

## Tips

* Choose `stagger` between 20 and 40 ms for letters, and between 40 and 80 ms for words. A long text with a large value is slow to finish.
* Use a short `duration` for letters, for example `400`.
* Use `fade()` or a small `slide({ distance: 12 })`. Large movements of many small elements look noisy.
* With `once: false`, the text hides and plays again at each entry. See [Viewport options](/concepts/options/#viewport).

## Next steps

[Stagger](/concepts/options/#stagger): Animate the children of an element one after the other.

[Keyframes](/concepts/keyframes/): The built-in animations, springs and custom keyframes.

[Accessibility](/concepts/accessibility/): Reduced motion, screen readers and pages without JavaScript.

[Getting started](/getting-started/): Install the package for your framework.
