# Skeleton

> A placeholder component to show a loading state while content is being fetched.

**BNA UI** — a React Native / Expo component library.
These components render through `react-native`, not the DOM: there are no HTML
elements, no Tailwind classes and no Radix primitives. Source is copied into your
project and imported through `@/components/ui/*`, `@/components/charts/*`,
`@/hooks/*` and `@/theme/*`. Colours come from the `useColor` hook rather than
hardcoded hex; sizing tokens (`HEIGHT`, `FONT_SIZE`, `BORDER_RADIUS`, `CORNERS`)
come from `@/theme/globals`.

- Docs: https://ui.ahmedbna.com/docs/components/skeleton
- Markdown: https://ui.ahmedbna.com/docs/components/skeleton.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/skeleton.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/skeleton.json
- Install: `npx bna-ui add skeleton`
- npm dependencies: `react-native-reanimated`
- Registry dependencies: `mode-provider`, `useColorScheme`, `colors`, `useColor`, `globals`
- Preview recording: https://demo.ahmedbna.com/0273-skeleton-demo.MP4

---

**Example:** A basic skeleton loader with pulsing animation

```tsx
// components/demo/skeleton/skeleton-demo.tsx
import { Skeleton } from '@/components/ui/skeleton';
import React from 'react';

export function SkeletonDemo() {
  return <Skeleton width={200} height={20} />;
}
```

## Installation

### CLI

```bash
npx bna-ui add skeleton
```

### Manual

**1.** Copy and paste the following code into your project.

```tsx
// components/ui/skeleton.tsx
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS, CORNERS } from '@/theme/globals';
import React, { useEffect } from 'react';
import { ViewStyle } from 'react-native';
import Animated, {
  Easing,
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  withRepeat,
} from 'react-native-reanimated';

interface SkeletonProps {
  width?: number | string;
  height?: number;
  style?: ViewStyle;
  variant?: 'default' | 'rounded';
}

export const Skeleton = React.memo(function Skeleton({
  width = '100%',
  height = 100,
  style,
  variant = 'default',
}: SkeletonProps) {
  const mutedColor = useColor('muted');
  // Start the opacity at its lowest point
  const opacity = useSharedValue(0.5);

  const animatedStyle = useAnimatedStyle(() => {
    return {
      opacity: opacity.value,
    };
  });

  useEffect(() => {
    // We only define the animation going from 0.5 -> 1.
    // The `withRepeat` function will handle reversing it automatically.
    opacity.value = withRepeat(
      // Animate to an opacity of 1
      withTiming(1, {
        duration: 1000,
        easing: Easing.inOut(Easing.quad),
      }),
      -1, // Loop infinitely
      true // Set to true to automatically reverse the animation (yoyo effect)
    );
  }, []); // Use an empty dependency array as the shared value object is stable

  return (
    <Animated.View
      accessibilityElementsHidden
      accessibilityLabel='Loading content'
      style={[
        {
          width: width as any,
          height,
          backgroundColor: mutedColor,
          borderRadius: variant === 'default' ? CORNERS : BORDER_RADIUS,
        },
        animatedStyle,
        style,
      ]}
    />
  );
});
```

**2.** Update the import paths to match your project setup.

## Usage

```tsx
import { Skeleton } from '@/components/ui/skeleton';
```

```tsx
<Skeleton width={200} height={20} />
```

## Examples

#### Default

**Example:** A basic skeleton loader with pulsing animation

```tsx
// components/demo/skeleton/skeleton-demo.tsx
import { Skeleton } from '@/components/ui/skeleton';
import React from 'react';

export function SkeletonDemo() {
  return <Skeleton width={200} height={20} />;
}
```

#### Different Sizes

**Example:** Skeletons in various sizes and dimensions

```tsx
// components/demo/skeleton/skeleton-sizes.tsx
import { Skeleton } from '@/components/ui/skeleton';
import { View } from '@/components/ui/view';
import React from 'react';

export function SkeletonSizes() {
  return (
    <View style={{ gap: 12 }}>
      <Skeleton width={100} height={16} />
      <Skeleton width={200} height={20} />
      <Skeleton width={300} height={24} />
      <Skeleton width='100%' height={32} />
    </View>
  );
}
```

#### Card Layout

**Example:** Skeleton placeholders arranged in a card layout

```tsx
// components/demo/skeleton/skeleton-card.tsx
import { Skeleton } from '@/components/ui/skeleton';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS } from '@/theme/globals';
import React from 'react';

export function SkeletonCard() {
  const card = useColor('card');

  return (
    <View
      style={{
        padding: 16,
        borderRadius: BORDER_RADIUS,
        backgroundColor: card,
        gap: 12,
      }}
    >
      {/* Header */}
      <View style={{ flexDirection: 'row', gap: 12, alignItems: 'center' }}>
        <Skeleton width={40} height={40} style={{ borderRadius: 20 }} />
        <View style={{ flex: 1, gap: 4 }}>
          <Skeleton width='60%' height={16} />
          <Skeleton width='40%' height={12} />
        </View>
      </View>

      {/* Content */}
      <Skeleton width='100%' height={200} variant='rounded' />

      {/* Footer */}
      <View style={{ gap: 8 }}>
        <Skeleton width='100%' height={16} />
        <Skeleton width='80%' height={16} />
        <Skeleton width='60%' height={16} />
      </View>
    </View>
  );
}
```

#### Profile Layout

**Example:** Skeleton layout mimicking a user profile

```tsx
// components/demo/skeleton/skeleton-profile.tsx
import { Skeleton } from '@/components/ui/skeleton';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS } from '@/theme/globals';
import React from 'react';

export function SkeletonProfile() {
  const card = useColor('card');

  return (
    <View
      style={{
        alignItems: 'center',
        gap: 16,
        padding: 16,
        borderRadius: BORDER_RADIUS,
        backgroundColor: card,
      }}
    >
      {/* Profile Picture */}
      <Skeleton width={80} height={80} style={{ borderRadius: 40 }} />

      {/* Name and Title */}
      <View style={{ alignItems: 'center', gap: 8 }}>
        <Skeleton width={150} height={20} />
        <Skeleton width={100} height={16} />
      </View>

      {/* Stats */}
      <View style={{ flexDirection: 'row', gap: 24 }}>
        <View style={{ alignItems: 'center', gap: 4 }}>
          <Skeleton width={30} height={18} />
          <Skeleton width={50} height={14} />
        </View>
        <View style={{ alignItems: 'center', gap: 4 }}>
          <Skeleton width={30} height={18} />
          <Skeleton width={50} height={14} />
        </View>
        <View style={{ alignItems: 'center', gap: 4 }}>
          <Skeleton width={30} height={18} />
          <Skeleton width={50} height={14} />
        </View>
      </View>

      {/* Bio */}
      <View style={{ gap: 8, width: '100%' }}>
        <Skeleton width='100%' height={16} />
        <Skeleton width='90%' height={16} />
        <Skeleton width='70%' height={16} />
      </View>
    </View>
  );
}
```

#### List Items

**Example:** Multiple skeleton items arranged in a list

```tsx
// components/demo/skeleton/skeleton-list.tsx
import { Skeleton } from '@/components/ui/skeleton';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS } from '@/theme/globals';
import React from 'react';

export function SkeletonList() {
  const card = useColor('card');

  return (
    <View
      style={{
        gap: 16,
        padding: 16,
        borderRadius: BORDER_RADIUS,
        backgroundColor: card,
      }}
    >
      {Array.from({ length: 5 }, (_, i) => (
        <View
          key={i}
          style={{ flexDirection: 'row', gap: 12, alignItems: 'center' }}
        >
          <Skeleton width={50} height={50} style={{ borderRadius: 25 }} />
          <View style={{ flex: 1, gap: 6 }}>
            <Skeleton width='70%' height={16} />
            <Skeleton width='50%' height={14} />
            <Skeleton width='30%' height={12} />
          </View>
        </View>
      ))}
    </View>
  );
}
```

#### Custom Shapes

**Example:** Skeletons with custom shapes and styling

```tsx
// components/demo/skeleton/skeleton-shapes.tsx
import { Skeleton } from '@/components/ui/skeleton';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS } from '@/theme/globals';
import React from 'react';

export function SkeletonShapes() {
  const card = useColor('card');

  return (
    <View
      style={{
        flexDirection: 'row',
        flexWrap: 'wrap',
        gap: 16,
        padding: 16,
        borderRadius: BORDER_RADIUS,
        backgroundColor: card,
      }}
    >
      {/* Circle */}
      <Skeleton width={60} height={60} style={{ borderRadius: 30 }} />

      {/* Square */}
      <Skeleton width={60} height={60} style={{ borderRadius: 4 }} />

      {/* Rounded Rectangle */}
      <Skeleton width={120} height={60} style={{ borderRadius: 12 }} />

      {/* Pill */}
      <Skeleton width={100} height={30} style={{ borderRadius: 15 }} />

      {/* Custom styled */}
      <Skeleton
        width={80}
        height={80}
        style={{
          borderRadius: 20,
          transform: [{ rotate: '45deg' }],
        }}
      />
    </View>
  );
}
```

## API Reference

### Skeleton

A loading placeholder component with animated pulsing effect.

| Prop      | Type                     | Default     | Description                                                                                                              |
| --------- | ------------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------ |
| `width`   | `number \| string`       | `100%`      | The width of the skeleton.                                                                                               |
| `height`  | `number`                 | `100`       | The height of the skeleton in pixels.                                                                                    |
| `style`   | `ViewStyle`              | -           | Additional styles to apply to the skeleton.                                                                              |
| `variant` | `'default' \| 'rounded'` | `'default'` | Corner radius preset: `"default"` uses `CORNERS` (fully rounded), `"rounded"` uses `BORDER_RADIUS` (moderately rounded). |

## Accessibility

The Skeleton component is built with accessibility in mind:

- Uses appropriate color contrast for loading states
- Provides visual feedback during content loading
- Maintains layout stability while content loads
- Compatible with screen readers through proper semantic structure
