Text typing animation without JavaScript (only CSS)

This is a text typing effect that is achieved using only CSS and HTML.


This is a text typing effect that happens entirely through HTML and CSS.


Click to edit (doesn't update preview)

  <span class="typing-effect" style="animation-delay: 0s; animation-duration: 1.5s; width: 29ch; animation-timing-function: steps(29);">This is a text typing effect</span>
  <span class="typing-effect" style="animation-delay: 1.65s; animation-duration: 1.85s; width: 30ch; animation-timing-function: steps(30);">that happens entirely through</span>
  <span class="typing-effect" style="animation-delay: 3.65s; animation-duration: 0.9s; width: 13ch; animation-timing-function: steps(13);">HTML and CSS.</span>

<!-- Button below is only used for animation reset -->
<button onClick="reset()" class="typing-effect" style="animation-delay: 5s; animation-duration: 1.3s; width: 19ch; animation-timing-function: steps(18);">Click me to reset!</button>


Click to edit (doesn't update preview)

.typing-effect {
  display: block;
  animation: letters, blink .5s step-end alternate;
  animation-fill-mode: forwards;
  white-space: nowrap;
  overflow: hidden;
  border-right: 3px solid;
  opacity: 0;
  font-family: monospace;

@keyframes letters {
  0% {
    width: 0;
    opacity: 1;
  99% {
    border-color: #888;
  100% {
    opacity: 1;
    border-color: transparent;


Click to edit (doesn't update preview)

// JavaScript is not needed at all!
// This is just a function for the "reset" button.
function reset() {
  const typing = document.getElementsByClassName('typing-effect');
  for (var i = 0; i < typing.length; ++i) {
    typing[i].style.display = 'none';
  setTimeout(() => {
    for (var i = 0; i < typing.length; ++i) {
      typing[i].style.display = 'block';
  }, 55);


How it works

The .typing-effect class has an animation where it goes from zero width to full width on multiple text lines. Part of the animation is a border-right CSS attribute, which creates the typing pointer effect.

To make sure each line has the correct width, the width attribute is inlined to each span element using a ch (ch = character width unit). To know what ch amount you need, you just count how many letters the line has, and use that amount. With some fonts, you might need to use a larger or smaller amount.

Next to the inlined width attribute is the animation-timing-function attribute that tells the CSS in how many steps the animation should happen. Without it, the width would animate smoothly instead of being "typed out". As we want only one letter to come out on each step, we use the amount of letters on the line as the step amount. A line with 20 letters will have a step amount of 20.

Each line has an animation-duration attribute, where you just choose how fast that line should get typed. The longer the line, the longer the duration should be. Next to it is the animation-delay, which makes sure that the lines don't all start at the same time, and instead you only make each line start animating after the one above it has finished.


This effect is fully accessible to screen readers. If this effect is a major part of your design, you might want to check out prefers-reduced-motion media option in CSS. That way, you can disable the effect for those users who don't wish to see moving elements on the screen.

The lines are wrapped inside a paragraph tag, because in this context, each line is not a separate paragraph, and instead is part of the same paragraph.