Lightweight, performant React animation primitives using Web Animations API (WAAPI)
npm install @mks2508/waapi-animation-primitivesprefers-reduced-motion automatically
Intl.ListFormat support for localized separators
bash
npm
npm install @mks2508/waapi-animation-primitives
bun
bun add @mks2508/waapi-animation-primitives
yarn
yarn add @mks2508/waapi-animation-primitives
`
---
Components
$3
Animated number transitions with format preservation.
`tsx
import { SlidingNumber } from '@mks2508/waapi-animation-primitives';
value={1234.56}
duration={300}
format={{ decimals: 2, separator: ',' }}
/>
`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| value | number | required | The number to display |
| duration | number | 200 | Animation duration in ms |
| format | FormatOptions | undefined | Decimal separator options |
| fontSize | string | "inherit" | Font size CSS value |
| fontWeight | string | "inherit" | Font weight CSS value |
| color | string | "inherit" | Text color CSS value |
---
$3
Character or word-by-word text animations.
`tsx
import { SlidingText } from '@mks2508/waapi-animation-primitives';
text="Hello World"
mode="character"
direction="vertical"
staggerDelay={15}
blur={4}
/>
`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| text | string | required | Text to display |
| mode | 'character' \| 'word' \| 'none' | 'character' | Animation granularity |
| direction | 'vertical' \| 'horizontal' | 'vertical' | Animation direction |
| duration | number | 200 | Animation duration in ms |
| staggerDelay | number | 15 | Delay between tokens (ms) |
| blur | number | 0 | Blur effect amount (px) |
| widthAnimation | boolean | false | Animate width changes |
| initial | boolean \| 'initial' | true | Play animation on mount |
---
$3
Animated token list with coordinated enter/exit transitions.
`tsx
import { AnimatedTokens, Token } from '@mks2508/waapi-animation-primitives';
const tokens: Token[] = [
{ id: '1', text: 'React' },
{ id: '2', text: 'TypeScript' },
{ id: '3', text: 'WAAPI' }
];
tokens={tokens}
maxVisible={5}
separator=", "
/>
`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| tokens | Token[] | required | Array of { id, text } |
| maxVisible | number | undefined | Max visible before "+N more" |
| separator | string | ", " | Separator between tokens |
| textAnimationMode | 'character' \| 'word' | 'character' | Text animation mode |
| textDirection | 'vertical' \| 'horizontal' | 'vertical' | Text animation direction |
| enableWidthAnimation | boolean | false | Animate width changes |
| placeholder | string | undefined | Empty state text |
---
$3
> Alias for AnimatedTokensV2 - Modern version with locale-aware separators and Reorder primitive integration.
`tsx
import { TextFlow } from '@mks2508/waapi-animation-primitives';
// en-US: "React, TypeScript, and WAAPI"
// es-ES: "React, TypeScript y WAAPI"
tokens={tokens}
locale="es"
listType="conjunction"
maxVisible={5}
/>
`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| tokens | Token[] | required | Array of { id, text } |
| locale | string | browser | Locale for separators |
| listType | 'conjunction' \| 'disjunction' \| 'unit' | 'conjunction' | List format type |
| listStyle | 'long' \| 'short' \| 'narrow' | 'long' | List format style |
| separator | string | undefined | Manual separator override |
| maxVisible | number | undefined | Max visible before "+N more" |
| textAnimationMode | 'character' \| 'word' | 'character' | Text animation mode |
| textDirection | 'vertical' \| 'horizontal' | 'vertical' | Text animation direction |
| textStaggerDelay | number | 15 | Stagger delay (ms) |
| enableWidthAnimation | boolean | false | Animate width changes |
| placeholder | string | "No tokens" | Empty state text |
---
Primitives
$3
Agnostic FLIP animation primitive for animated list reordering.
`tsx
import { Reorder } from '@mks2508/waapi-animation-primitives';
function TodoList() {
const [todos, setTodos] = useState([
{ id: '1', text: 'Buy milk' },
{ id: '2', text: 'Write code' }
]);
const handleRemove = (id: string) => {
setTodos(prev => prev.filter(t => t.id !== id));
};
return (
layout="horizontal"
stagger={15}
onItemExit={handleRemove}
>
{todos.map(todo => (
{todo.text}
))}
);
}
`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| children | ReactNode | required | Child elements to animate |
| layout | 'auto' \| 'horizontal' \| 'vertical' \| 'grid' | 'auto' | Layout arrangement |
| autoAnimate | boolean | true | Enable enter animations |
| stagger | number \| { enter, exit } | undefined | Stagger delay (ms) |
| duration | number \| { enter, exit } | { enter: 300, exit: 200 } | Animation duration (ms) |
| flipBehavior | FLIPBehavior | 'smooth' | FLIP animation strategy |
| exitPositionStrategy | ExitPositionStrategy | 'keep-pace' | Exit positioning strategy |
| onItemExit | (id: string) => void | undefined | Exit animation start callback |
| onItemEnter | (id: string) => void | undefined | Enter animation complete callback |
---
$3
Low-level hook for orchestration of FLIP animations.
`tsx
import { useReorder } from '@mks2508/waapi-animation-primitives';
function ListManager() {
const reorder = useReorder({
enterDuration: 400,
exitDuration: 200,
flipDuration: 300,
onComplete: (id) => console.log('Done:', id)
});
// Register element
reorder.registerElement('item-1', el)}>Item
// Trigger animations
await reorder.startItemExit('item-1');
}
`
---
$3
Hook wrapper for Intl.ListFormat with locale-aware separators.
`tsx
import { useListFormat } from '@mks2508/waapi-animation-primitives';
const parts = useListFormat(
['React', 'TypeScript', 'WAAPI'],
{ locale: 'es', type: 'conjunction' }
);
// Returns: [{ type: 'element', index: 0, value: 'React' }, ...]
`
---
Core
$3
Low-level WAAPI orchestration for custom animations.
`tsx
import { AnimationOrchestrator } from '@mks2508/waapi-animation-primitives';
const orchestrator = new AnimationOrchestrator({
flipDuration: 300,
flipEasing: 'cubic-bezier(0.2, 0, 0.2, 1)',
flipBehavior: 'smooth'
});
orchestrator.registerElement('id-1', element);
await orchestrator.startFlip();
`
---
CSS System
All animation values are controlled via CSS variables.
$3
`css
:root {
/ Timing /
--waapi-duration-enter: 200ms;
--waapi-duration-exit: 180ms;
--waapi-duration-flip: 300ms;
--waapi-stagger-enter: 15ms;
/ Transforms /
--waapi-offset-y-enter: 8px;
--waapi-offset-y-exit: -8px;
--waapi-scale-exit: 0.95;
/ Effects /
--waapi-blur-enter: 4px;
--waapi-blur-exit: 2px;
/ Easings /
--waapi-ease-enter: cubic-bezier(0.33, 1, 0.68, 1);
--waapi-ease-exit: cubic-bezier(0.32, 0, 0.67, 0);
--waapi-ease-flip: cubic-bezier(0.2, 0, 0.2, 1);
}
`
$3
`tsx
`
$3
`tsx
import { CSS_VAR_NAMES } from '@mks2508/waapi-animation-primitives';
const customStyle = {
[CSS_VAR_NAMES.durationEnter]: '500ms',
[CSS_VAR_NAMES.blurEnter]: '8px',
};
`
---
CSS Classes
| Class | Component | Description |
|-------|-----------|-------------|
| .waapi-sliding-text-container | SlidingText | Main container |
| .waapi-sliding-text-token | SlidingText | Individual character/word |
| .waapi-sliding-number-container | SlidingNumber | Main container |
| .waapi-animated-tokens-container | AnimatedTokens | Main container |
| .waapi-token-wrapper | AnimatedTokens | Token wrapper |
| .waapi-token-separator | AnimatedTokens | Separator |
| .waapi-token-overflow | AnimatedTokens | "+N more" counter |
---
Debug System
Development-only tools. Automatically tree-shaken in production.
`tsx
import { DebugProvider, useDebug } from '@mks2508/waapi-animation-primitives';
// Access debug events
const { events, logEvent, exportToCSV } = useDebug();
`
---
Accessibility
- Reduced Motion: Respects prefers-reduced-motion` media query