Magic Native UI

Skeleton

Animated placeholder for loading content.

Installation

Terminal
npx magic-native-ui@latest add skeleton

Usage

The example running in the preview above. Imports use the default aliases from components.json.

skeleton-demo.tsx
import { View } from 'react-native'

import { Skeleton } from '@/components/ui/skeleton'

export function SkeletonDemo() {
  return (
    <View className="w-full flex-row items-center gap-3">
      <Skeleton className="h-12 w-12 rounded-full" />
      <View className="flex-1 gap-2">
        <Skeleton className="h-4 w-3/4" />
        <Skeleton className="h-4 w-1/2" />
      </View>
    </View>
  )
}

Component source

What the CLI copies into your project. Once it is there, the code is yours to edit.

registry/ui/skeleton.tsx
import * as React from 'react'
import { Animated, Easing, type ViewProps } from 'react-native'
import { withUniwind } from 'uniwind'

import { cn } from '@/registry/lib/utils'

const AnimatedView = withUniwind(Animated.View)

/**
 * `animate-pulse` has no native equivalent, so the pulse is driven by the
 * Animated API. The explicit `style` is merged after the class-derived styles,
 * so it only controls opacity.
 */
function Skeleton({ className, ...props }: ViewProps) {
  const opacity = React.useRef(new Animated.Value(0.5)).current

  React.useEffect(() => {
    const pulse = Animated.loop(
      Animated.sequence([
        Animated.timing(opacity, {
          toValue: 1,
          duration: 800,
          easing: Easing.inOut(Easing.ease),
          useNativeDriver: true,
        }),
        Animated.timing(opacity, {
          toValue: 0.5,
          duration: 800,
          easing: Easing.inOut(Easing.ease),
          useNativeDriver: true,
        }),
      ])
    )

    pulse.start()

    return () => pulse.stop()
  }, [opacity])

  return <AnimatedView style={{ opacity }} className={cn('rounded-md bg-muted', className)} {...props} />
}

export { Skeleton }