Skip to main content

Animations

Add eye-catching animations to your icons using the AnimatedIcon wrapper component.

Basic Usageโ€‹

Wrap any icon with AnimatedIcon to add animations:

import { AnimatedIcon } from 'rn-iconify/animated';
import { Mdi } from 'rn-iconify';

// Spinning loader
<AnimatedIcon animate="spin">
<Mdi name="loading" size={24} />
</AnimatedIcon>

// Pulsing heart
<AnimatedIcon animate="pulse">
<Mdi name="heart" size={24} color="red" />
</AnimatedIcon>

// Bouncing arrow
<AnimatedIcon animate="bounce">
<Mdi name="arrow-down" size={24} />
</AnimatedIcon>

Animation Presetsโ€‹

PresetEffectUse Case
spin360ยฐ rotationLoading indicators
pulseOpacity fade 1 โ†’ 0.4Attention, heartbeat
bounceVertical bounceDownload, scroll hint
shakeHorizontal shakeError, notification
pingScale expand (1 โ†’ 1.5)Status, notification
wiggleRotate ยฑ15ยฐGreeting, attention

All Presets Exampleโ€‹

import { AnimatedIcon } from 'rn-iconify/animated';
import { Mdi } from 'rn-iconify';

<AnimatedIcon animate="spin">
<Mdi name="loading" size={24} />
</AnimatedIcon>

<AnimatedIcon animate="pulse">
<Mdi name="heart" size={24} />
</AnimatedIcon>

<AnimatedIcon animate="bounce">
<Mdi name="arrow-down" size={24} />
</AnimatedIcon>

<AnimatedIcon animate="shake">
<Mdi name="bell" size={24} />
</AnimatedIcon>

<AnimatedIcon animate="ping">
<Mdi name="circle" size={24} />
</AnimatedIcon>

<AnimatedIcon animate="wiggle">
<Mdi name="hand-wave" size={24} />
</AnimatedIcon>

Loading Spinnerโ€‹

Create a loading indicator:

import { AnimatedIcon } from 'rn-iconify/animated';
import { Mdi } from 'rn-iconify';
import { View, Text } from 'react-native';

function LoadingState() {
return (
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
<AnimatedIcon animate="spin">
<Mdi name="loading" size={24} color="#6366f1" />
</AnimatedIcon>
<Text>Loading...</Text>
</View>
);
}

Notification Bellโ€‹

Animated notification indicator:

import { AnimatedIcon } from 'rn-iconify/animated';
import { Mdi } from 'rn-iconify';

function NotificationBell({ hasNew }: { hasNew: boolean }) {
if (hasNew) {
return (
<AnimatedIcon animate="shake">
<Mdi name="bell" size={24} color="red" />
</AnimatedIcon>
);
}

return <Mdi name="bell" size={24} color="gray" />;
}

Like Buttonโ€‹

Animated heart icon:

import { useState, useRef } from 'react';
import { TouchableOpacity } from 'react-native';
import { AnimatedIcon } from 'rn-iconify/animated';
import { Mdi } from 'rn-iconify';
import type { AnimationControls } from 'rn-iconify/animated';

function LikeButton() {
const [liked, setLiked] = useState(false);
const animRef = useRef<AnimationControls>(null);

const handlePress = () => {
setLiked(!liked);
animRef.current?.start();
};

return (
<TouchableOpacity onPress={handlePress}>
<AnimatedIcon ref={animRef} animate="pulse" autoPlay={false}>
<Mdi
name={liked ? 'heart' : 'heart-outline'}
size={32}
color={liked ? 'red' : 'gray'}
/>
</AnimatedIcon>
</TouchableOpacity>
);
}

Download Indicatorโ€‹

Loading spinner during download:

import { AnimatedIcon } from 'rn-iconify/animated';
import { Mdi } from 'rn-iconify';

function DownloadButton({ isDownloading }: { isDownloading: boolean }) {
if (isDownloading) {
return (
<AnimatedIcon animate="spin">
<Mdi name="loading" size={24} />
</AnimatedIcon>
);
}

return <Mdi name="download" size={24} />;
}

Greeting Animationโ€‹

Waving hand icon:

import { AnimatedIcon } from 'rn-iconify/animated';
import { Mdi } from 'rn-iconify';
import { View, Text } from 'react-native';

function Greeting({ name }: { name: string }) {
return (
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
<AnimatedIcon animate="wiggle">
<Mdi name="hand-wave" size={24} />
</AnimatedIcon>
<Text>Hello, {name}!</Text>
</View>
);
}

Status Pingโ€‹

Online status indicator:

import { AnimatedIcon } from 'rn-iconify/animated';
import { Mdi } from 'rn-iconify';
import { View } from 'react-native';

function OnlineStatus({ isOnline }: { isOnline: boolean }) {
if (isOnline) {
return (
<AnimatedIcon animate="ping">
<Mdi name="circle" size={12} color="green" />
</AnimatedIcon>
);
}

return <Mdi name="circle" size={12} color="gray" />;
}

Custom Animation Configurationโ€‹

Fine-tune animations with custom configuration:

import { AnimatedIcon } from 'rn-iconify/animated';
import { Mdi } from 'rn-iconify';

// Slow rotation
<AnimatedIcon
animate={{
type: 'rotate',
duration: 3000,
easing: 'linear',
loop: true,
}}
>
<Mdi name="sync" size={24} />
</AnimatedIcon>

// Scale animation
<AnimatedIcon
animate={{
type: 'scale',
duration: 800,
from: 1,
to: 1.3,
easing: 'ease-in-out',
}}
>
<Mdi name="star" size={24} />
</AnimatedIcon>

Animation Control with Refโ€‹

Use ref for manual animation control:

import { useRef } from 'react';
import { Button, View } from 'react-native';
import { AnimatedIcon } from 'rn-iconify/animated';
import { Mdi } from 'rn-iconify';
import type { AnimationControls } from 'rn-iconify/animated';

function ControlledAnimation() {
const animRef = useRef<AnimationControls>(null);

return (
<View>
<AnimatedIcon ref={animRef} animate="bounce" autoPlay={false}>
<Mdi name="heart" size={32} color="red" />
</AnimatedIcon>

<Button title="Start" onPress={() => animRef.current?.start()} />
<Button title="Stop" onPress={() => animRef.current?.stop()} />
<Button title="Reset" onPress={() => animRef.current?.reset()} />
</View>
);
}

AnimationControls APIโ€‹

MethodDescription
start()Start the animation
stop()Stop the animation
pause()Pause the animation
resume()Resume paused animation
reset()Reset to initial state
isAnimatingWhether animation is running
stateCurrent animation state
Pause/Resume Limitations

React Native's Animated API does not support true mid-animation pause/resume.

  • pause() stops the animation at its current position
  • resume() restarts the animation from the beginning, not from where it was paused

If you need true pause/resume, consider using react-native-reanimated with custom animation logic.

With React Navigationโ€‹

Animated tab icons:

import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { AnimatedIcon } from 'rn-iconify/animated';
import { Mdi } from 'rn-iconify';

const Tab = createBottomTabNavigator();

<Tab.Navigator>
<Tab.Screen
name="Notifications"
component={NotificationsScreen}
options={{
tabBarIcon: ({ focused, color, size }) => {
const icon = <Mdi name="bell" size={size} color={color} />;

if (focused) {
return (
<AnimatedIcon animate="pulse">
{icon}
</AnimatedIcon>
);
}

return icon;
},
}}
/>
</Tab.Navigator>

useIconAnimation Hookโ€‹

For more control, use the hook directly:

import { useIconAnimation } from 'rn-iconify/animated';
import { Animated, TouchableOpacity, View, Text, Button } from 'react-native';
import { Mdi } from 'rn-iconify';

function CustomAnimatedIcon() {
const {
animatedStyle,
start,
stop,
pause,
resume,
reset,
isAnimating,
hasAnimation,
state,
} = useIconAnimation({
animation: 'spin',
autoPlay: false,
});

return (
<View>
<TouchableOpacity onPress={isAnimating ? stop : start}>
<Animated.View style={animatedStyle}>
<Mdi name="refresh" size={24} />
</Animated.View>
</TouchableOpacity>
<Text>State: {state}</Text>
<Text>Has animation: {hasAnimation ? 'Yes' : 'No'}</Text>
<Button title="Pause" onPress={pause} disabled={state !== 'running'} />
<Button title="Resume" onPress={resume} disabled={state !== 'paused'} />
<Button title="Reset" onPress={reset} />
</View>
);
}

useIconAnimation Return Valueโ€‹

PropertyTypeDescription
animatedStyleobjectStyle object to apply to Animated.View
start() => voidStart the animation
stop() => voidStop the animation
pause() => voidPause the animation
resume() => voidResume paused animation
reset() => voidReset to initial state
isAnimatingbooleanWhether animation is currently running
hasAnimationbooleanWhether animation is configured
state'idle' | 'running' | 'paused' | 'completed'Current animation state

AnimatedIcon Propsโ€‹

PropTypeDefaultDescription
childrenReactNoderequiredIcon element to animate
animateAnimationPreset | AnimationConfig-Animation to apply
animationDurationnumbervariesDuration override (ms)
animationLoopbooleanvariesLoop override
animationEasingAnimationEasingvariesEasing override
animationDelaynumber0Delay before start (ms)
autoPlaybooleantrueAuto-start animation
onAnimationComplete() => void-Callback on completion
widthnumber-Width of the animation container
heightnumber-Height of the animation container
testIDstring-Test ID for testing

Animation Presets Referenceโ€‹

PresetTypeDurationLoopsEasingDirectionFrom โ†’ To
spinrotate1000msYeslinearnormal0ยฐ โ†’ 360ยฐ
pulseopacity1500msYesease-in-outalternate1 โ†’ 0.4
bouncescale600msYesbouncealternate1 โ†’ 1.2
shaketranslate500msNoease-outnormal0 โ†’ 10px (x)
pingscale1000msYesease-outnormal1 โ†’ 1.5
wigglerotate300msNoease-in-outalternate-15ยฐ โ†’ 15ยฐ

Reduced Motion Support (v3.0)โ€‹

When an AccessibilityProvider is present and the user has enabled "Reduce Motion" in system settings, all icon animations are automatically disabled. No manual code is needed.

import { AccessibilityProvider, Mdi } from 'rn-iconify';

// Animations auto-disable when system Reduce Motion is on
<AccessibilityProvider>
<Mdi name="loading" animate="spin" /> {/* Respects Reduce Motion */}
</AccessibilityProvider>

Performanceโ€‹

Animations use React Native's Animated API for smooth 60fps performance:

  • Animations run on the native thread
  • Minimal JavaScript overhead
  • Battery-efficient

Tipsโ€‹

1. Use Sparinglyโ€‹

Too many animations can be distracting:

// Good - one animated icon
<View>
<AnimatedIcon animate="spin">
<Mdi name="loading" size={24} />
</AnimatedIcon>
<Mdi name="check" size={24} />
<Mdi name="close" size={24} />
</View>

// Avoid - too many animations
<View>
<AnimatedIcon animate="spin">
<Mdi name="loading" size={24} />
</AnimatedIcon>
<AnimatedIcon animate="pulse">
<Mdi name="heart" size={24} />
</AnimatedIcon>
<AnimatedIcon animate="shake">
<Mdi name="bell" size={24} />
</AnimatedIcon>
</View>

2. Meaningful Animationsโ€‹

Use animations that convey meaning:

// Good - loading uses spin
<AnimatedIcon animate="spin">
<Mdi name="loading" size={24} />
</AnimatedIcon>

// Good - error uses shake
<AnimatedIcon animate="shake">
<Mdi name="alert" size={24} />
</AnimatedIcon>

// Confusing - loading with bounce
<AnimatedIcon animate="bounce">
<Mdi name="loading" size={24} />
</AnimatedIcon>

3. Respect Motion Preferencesโ€‹

Check user's reduced motion preference:

import { AccessibilityInfo } from 'react-native';
import { useEffect, useState } from 'react';
import { AnimatedIcon } from 'rn-iconify/animated';
import { Mdi } from 'rn-iconify';

function AccessibleIcon() {
const [reduceMotion, setReduceMotion] = useState(false);

useEffect(() => {
AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion);
}, []);

if (reduceMotion) {
return <Mdi name="loading" size={24} />;
}

return (
<AnimatedIcon animate="spin">
<Mdi name="loading" size={24} />
</AnimatedIcon>
);
}

Next Stepsโ€‹