# dioxus-type-animation
[](https://github.com/ewilken/dioxus-type-animation/actions?query=workflow%3ACI)
[](https://crates.io/crates/dioxus-type-animation)
[](https://docs.rs/dioxus-type-animation)
[](https://github.com/ewilken/dioxus-type-animation/blob/main/LICENSE)
A customizable Dioxus typewriter animation component inspired by [`react-type-animation`](https://github.com/maxeth/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:
```toml
[dependencies]
dioxus = "0.7"
dioxus-type-animation = "0.1"
```
## Usage
### Basic usage
```rust
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.
```rust
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.
```rust
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.
```rust
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.
```rust
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.
```rust
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:
```rust
.dioxus-type-animation__type::after {
}
@keyframes dioxus-type-animation__cursor {
50% {
opacity: 0;
}
}
```
Disable it with `cursor: false`:
```rust
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.
```rust
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.
```rust
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.
```rust
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
| `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.