dioxus-type-animation 0.1.0

A Dioxus typewriter animation component inspired by react-type-animation.
Documentation

dioxus-type-animation

CI crates.io docs.rs license: MIT

A customizable Dioxus typewriter animation component inspired by react-type-animation.

This crate provides a TypeAnimation component with a Rust-friendly API for animated typing/deleting effects, delays, callbacks, repeat behavior, custom wrappers, cursor styling, and custom string splitting.

Installation

Add the crate to your Dioxus project:

[dependencies]
dioxus = "0.7"
dioxus-type-animation = "0.1"

Usage

Basic usage

use dioxus::prelude::*;
use dioxus_type_animation::{Repeat, SequenceElement, Speed, TypeAnimation, Wrapper};

#[component]
fn App() -> Element {
    rsx! {
        TypeAnimation {
            sequence: vec![
                SequenceElement::from("We produce food for Mice"),
                SequenceElement::from(1000_u64),
                SequenceElement::from("We produce food for Hamsters"),
                SequenceElement::from(1000_u64),
                SequenceElement::from("We produce food for Guinea Pigs"),
                SequenceElement::from(1000_u64),
                SequenceElement::from("We produce food for Chinchillas"),
                SequenceElement::from(1000_u64),
            ],
            wrapper: Wrapper::Span,
            speed: Speed::Preset(50),
            style: Some("font-size: 2em; display: inline-block;".to_string()),
            repeat: Repeat::Infinite,
        }
    }
}

Sequence items

The sequence prop accepts three kinds of items:

  • SequenceElement::Text(String) / SequenceElement::from("text") — transition to this text.
  • SequenceElement::Delay(u64) / SequenceElement::from(1000_u64) — wait for this many milliseconds.
  • SequenceElement::Callback(Rc<dyn Fn()>) / SequenceElement::from(|| { ... }) — run a callback.
use dioxus::prelude::*;
use dioxus_type_animation::{SequenceElement, TypeAnimation};

#[component]
fn App() -> Element {
    rsx! {
        TypeAnimation {
            sequence: vec![
                SequenceElement::from("Typing..."),
                SequenceElement::from(750_u64),
                SequenceElement::from(|| println!("Done typing")),
                SequenceElement::from("Finished."),
            ],
        }
    }
}

Repeat behavior

By default, a sequence runs once. Repeat::Count(n) means the animation runs once plus n repeats, matching react-type-animation behavior. Use Repeat::Infinite to loop forever.

use dioxus::prelude::*;
use dioxus_type_animation::{Repeat, SequenceElement, TypeAnimation};

#[component]
fn App() -> Element {
    rsx! {
        TypeAnimation {
            sequence: vec![SequenceElement::from("Loop me"), SequenceElement::from(1000_u64)],
            repeat: Repeat::Count(3),
        }
    }
}

Speed options

Speed::Preset(value) mirrors the React library's speed={value} prop. Numeric speed values are normalized to abs(value - 100) milliseconds per keystroke, then randomized by ±50% for a natural typing rhythm.

Use Speed::KeyStrokeDelayInMs(value) for an exact base keystroke delay in milliseconds, equivalent to React's { type: "keyStrokeDelayInMs", value } option.

use dioxus::prelude::*;
use dioxus_type_animation::{SequenceElement, Speed, TypeAnimation};

#[component]
fn App() -> Element {
    rsx! {
        TypeAnimation {
            sequence: vec![
                SequenceElement::from("Type quickly"),
                SequenceElement::from(500_u64),
                SequenceElement::from("Delete slowly"),
            ],
            speed: Speed::Preset(80),
            deletion_speed: Some(Speed::KeyStrokeDelayInMs(120)),
        }
    }
}

Omit deletion animation

Set omit_deletion_animation to true to jump directly to the deletion end-state and only animate writing the new text.

use dioxus::prelude::*;
use dioxus_type_animation::{SequenceElement, TypeAnimation};

#[component]
fn App() -> Element {
    rsx! {
        TypeAnimation {
            sequence: vec![
                SequenceElement::from("Dioxus is fun"),
                SequenceElement::from(1000_u64),
                SequenceElement::from("Dioxus is fast"),
            ],
            omit_deletion_animation: true,
        }
    }
}

Wrapper elements

The default wrapper is Wrapper::Span. You can choose from p, div, span, strong, a, h1-h6, and b via the Wrapper enum.

use dioxus::prelude::*;
use dioxus_type_animation::{SequenceElement, TypeAnimation, Wrapper};

#[component]
fn App() -> Element {
    rsx! {
        TypeAnimation {
            wrapper: Wrapper::H1,
            sequence: vec![SequenceElement::from("Animated heading")],
        }
    }
}

Cursor styling

The blinking cursor is enabled by default. The component automatically injects CSS equivalent to:

.dioxus-type-animation__type::after {
  content: '|';
  animation: dioxus-type-animation__cursor 1.1s infinite step-start;
}

@keyframes dioxus-type-animation__cursor {
  50% {
    opacity: 0;
  }
}

Disable it with cursor: false:

use dioxus::prelude::*;
use dioxus_type_animation::{SequenceElement, TypeAnimation};

#[component]
fn App() -> Element {
    rsx! {
        TypeAnimation {
            sequence: vec![SequenceElement::from("No cursor")],
            cursor: false,
        }
    }
}

Pre-render the first string

Set pre_render_first_string to true to render the first string immediately before animation starts.

use dioxus::prelude::*;
use dioxus_type_animation::{SequenceElement, TypeAnimation};

#[component]
fn App() -> Element {
    rsx! {
        TypeAnimation {
            sequence: vec![
                SequenceElement::from("Already visible"),
                SequenceElement::from(1000_u64),
                SequenceElement::from("Then animated"),
            ],
            pre_render_first_string: true,
        }
    }
}

Accessibility attributes

You can pass aria_label, aria_hidden, and role. When aria_label is set, the animated visual text is rendered inside an inner aria-hidden="true" span so assistive technology reads the stable label instead of every intermediate typing state.

use dioxus::prelude::*;
use dioxus_type_animation::{SequenceElement, TypeAnimation};

#[component]
fn App() -> Element {
    rsx! {
        TypeAnimation {
            sequence: vec![SequenceElement::from("Fast-changing animated text")],
            aria_label: Some("Animated product tagline".to_string()),
            role: Some("text".to_string()),
        }
    }
}

Custom splitter

The default splitter uses text.chars(), similar to JavaScript's [...text]. If you need grapheme-aware splitting for complex emoji or combined characters, provide your own splitter.

use dioxus::prelude::*;
use dioxus_type_animation::{SequenceElement, StringSplitter, TypeAnimation};
use std::rc::Rc;

#[component]
fn App() -> Element {
    let splitter: StringSplitter = Rc::new(|text: &str| {
        // Replace this with unicode-segmentation or another grapheme splitter
        // if your application needs full grapheme-cluster support.
        text.chars().map(|char| char.to_string()).collect()
    });

    rsx! {
        TypeAnimation {
            sequence: vec![SequenceElement::from("👨‍👩‍👧‍👦 family")],
            splitter: Some(splitter),
        }
    }
}

API reference

Prop Type Default Description
sequence Vec<SequenceElement> required Animation sequence of text, delays, and callbacks.
repeat Repeat Repeat::Count(0) Number of repeats or infinite loop.
wrapper Wrapper Wrapper::Span HTML element used as wrapper.
speed Speed Speed::Preset(40) Typing speed.
deletion_speed Option<Speed> None Deletion speed. Falls back to speed.
omit_deletion_animation bool false Skip animated deletion steps.
cursor bool true Show default blinking cursor.
pre_render_first_string bool false Render first string before animation starts.
splitter Option<StringSplitter> None Custom text splitting function.
class Option<String> None Class applied to wrapper.
style Option<String> None Inline style applied to wrapper.
aria_label Option<String> None Wrapper aria-label.
aria_hidden Option<String> None Wrapper aria-hidden.
role Option<String> None Wrapper role.

Notes

Like the React implementation, TypeAnimation is intentionally immutable: prop changes are treated as equal and do not restart the animation. If you need to restart with different props, mount a new component instance, for example by changing its Dioxus key.

Callbacks are Rust closures (Rc<dyn Fn()>) instead of React callbacks receiving an HTMLElement | null. The Dioxus implementation drives text with signals rather than directly mutating the DOM.