Skeleton

Moti's skeleton component is great for showing animated loading states. It works on all platforms, including web.

import React from 'react'
import { Skeleton } from 'moti/skeleton'
const Loader = ({ children }) => <Skeleton>{children}</Skeleton>
export default Loader

Example#

Install#

Expo Users#

You'll also want to install expo-linear-gradient.

Please note that you must have Reanimated 3 installed. See installation steps for more info.

expo install expo-linear-gradient

Non-Expo users#

If you aren't using Expo, you can either use the expo-linear-gradient package, or react-native-linear-gradient.

Option 1: expo-linear-gradient#

npm install expo-linear-gradient

Keep in mind you'll need to also install expo-modules-core. Please make sure you complete any installation steps required for Expo's linear gradient component.

Option 2: react-native-linear-gradient#

Click here to use react-native-linear-gradient

First, install the module resolver plugin:

npm install babel-plugin-module-resolver

Next, add this to your app's babel.config.js's plugins array.

// babel.config.js
module.exports = function (api) {
api.cache(true)
return {
plugins: [
[
'module-resolver',
{
root: ['./'],
alias: {
'moti/skeleton': 'moti/skeleton/react-native-linear-gradient',
},
},
],
],
}
}

This assumes you've installed react-native-linear-gradient and followed any of its installation requirements.

Your skeletons will now use react-native-linear-gradient. You can continue importing from moti/skeleton.

Video#

Usage#

Show/hide#

Skeleton will hide when data exists by default.

<Skeleton>{!!data ? <Data /> : null}</Skeleton>

You can always show the skeleton:

<Skeleton show={loading}>
<Data />
</Skeleton>

Or hide it:

<Skeleton show={false}>{!!data ? <Data /> : null}</Skeleton>

Border radius#

Use radius to show a circle, square, or custom border radius. Defaults to 8.

Circle#

<Skeleton height={48} width={48} radius="round">
{!!data ? <Data /> : null}
</Skeleton>

Square#

<Skeleton height={48} width={48} radius="square">
{!!data ? <Data /> : null}
</Skeleton>

Custom radius#

<Skeleton radius={16}>{!!data ? <Data /> : null}</Skeleton>

Color modes#

light or dark

<Skeleton colorMode="light" />

Custom animation delay#

<Skeleton delay={250} />

Custom animation transition#

<Skeleton
transition={{
translateX: {
// defaults to a 3000ms timing function
type: 'spring',
},
}}
/>

Full example#

Here's the code from the video above:

import React, { useReducer } from 'react'
import { StyleSheet, Pressable } from 'react-native'
import { MotiView } from 'moti'
import { Skeleton } from 'moti/skeleton'
const Spacer = ({ height = 16 }) => <MotiView style={{ height }} />
export default function HelloWorld() {
const [dark, toggle] = useReducer((s) => !s, true)
const colorMode = dark ? 'dark' : 'light'
return (
<Pressable onPress={toggle} style={styles.container}>
<MotiView
transition={{
type: 'timing',
}}
style={[styles.container, styles.padded]}
animate={{ backgroundColor: dark ? '#000000' : '#ffffff' }}
>
<Skeleton colorMode={colorMode} radius="round" height={75} width={75} />
<Spacer />
<Skeleton colorMode={colorMode} width={250} />
<Spacer height={8} />
<Skeleton colorMode={colorMode} width={'100%'} />
<Spacer height={8} />
<Skeleton colorMode={colorMode} width={'100%'} />
</MotiView>
</Pressable>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
padded: {
padding: 16,
},
})

<Skeleton.Group />#

If you have many skeleton components, you can now wrap them with a single <Skeleton.Group show={loading} /> component. This will help you achieve this type of effect.

import { Image, Text } from 'react-native'
import { Skeleton } from 'moti/skeleton'
export function ListItem({ loading, item }) {
return (
<Skeleton.Group show={loading}>
<Skeleton>
<Image src={{ uri: image.avatar }} />
</Skeleton>
<Skeleton>
<Text>{item.title || ' '}</Text>
</Skeleton>
</Skeleton.Group>
)
}