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

Install#

Prior to version 0.17.0, the skeleton was installed as its own package. This is no longer the case.

If you have @motify/skeleton in your package.json, be sure to delete it and upgrade moti.

See the PR.

Peer dependency#

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

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

If you're using Expo:#

expo install expo-linear-gradient

If you aren't using Expo:#

npm install expo-linear-gradient

Please make sure you complete any installation steps required for Expo's linear gradient component.

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>
)
}