# Ghostly — Complete Documentation
> Zero-config skeleton loaders for React. CSS-first engine that transforms your existing components into skeleton loading states. No CLI, no build step, no code changes. ~3KB gzipped.
---
## Installation
Install both packages:
```bash
npm install @ghostly-ui/core @ghostly-ui/react
```
### Step 1: Import the CSS
```css
/* globals.css */
@import '@ghostly-ui/core/css';
```
### Step 2: Wrap your components
```tsx
import { Ghostly } from '@ghostly-ui/react'
function UserPage() {
const { data, isLoading } = useFetch('/api/user')
return (
)
}
```
Requirements: React >= 18, any bundler with CSS import support.
### Optional: Global configuration
```tsx
import { GhostlyProvider } from '@ghostly-ui/react'
import '@ghostly-ui/core/css'
export default function RootLayout({ children }) {
return (
{children}
)
}
```
---
## API Reference
### `` Component
Wraps any content to show skeleton loaders while loading. Supports ref forwarding.
```tsx
import { Ghostly } from '@ghostly-ui/react'
```
Props:
- `loading` (boolean, required): When true, shows skeleton effect
- `animation` ('shimmer' | 'pulse' | 'wave' | 'none', default: 'shimmer'): Animation style
- `radius` ('none' | 'xs' | 'sm' | 'md' | 'lg' | 'full', default: 'sm'): Border radius
- `speed` ('slow' | 'normal' | 'fast', default: 'normal'): Animation speed
- `as` ('div' | 'section' | 'article' | 'main' | 'aside' | 'span', default: 'div'): Wrapper tag
- `className` (string): CSS classes for wrapper
- `style` (CSSProperties): Inline styles, merged with CSS variables
- `children` (ReactNode, required): Content to display or skeletonize
- `ref` (Ref): Forwarded to wrapper element
Behavior when `loading={true}`:
- Sets `data-ghostly="{animation}"` on wrapper
- Sets `aria-busy="true"` for accessibility
- Sets CSS variables `--ghostly-radius` and `--ghostly-speed`
- All descendant text/media elements become skeleton blocks via CSS
Example:
```tsx
```
### `` Component
Skeleton loader for lists and grids. Clones a template element N times during loading. Supports ref forwarding.
```tsx
import { GhostlyList } from '@ghostly-ui/react'
```
Props:
- `loading` (boolean, required): When true, shows skeleton items
- `count` (number, required): Number of skeleton items to render
- `item` (ReactElement, default: first child): Template element to clone
- `animation`, `radius`, `speed`: Same as Ghostly
- `className` (string): CSS classes for list container
- `children` (ReactNode, required): Real content when not loading
Example:
```tsx
}
className="grid grid-cols-3 gap-4"
>
{products?.map(p => )}
```
### `` Component
Sets default config for all Ghostly descendants. Priority: Instance props > Provider > Defaults.
Props:
- `animation` (GhostlyAnimation, default: 'shimmer')
- `radius` (GhostlyRadius, default: 'sm')
- `speed` (GhostlySpeed, default: 'normal')
- `children` (ReactNode, required)
### `useGhostly()` Hook
Returns the current Ghostly context from the nearest ancestor.
```tsx
const { loading, animation, radius, speed } = useGhostly()
```
Returns: `{ loading: boolean, animation: GhostlyAnimation, radius: GhostlyRadius, speed: GhostlySpeed }`
---
## CSS Reference
### Custom Properties
Override these globally to customize:
```css
:root {
--ghostly-color: hsl(220 13% 87%); /* Skeleton block color */
--ghostly-shine: hsl(220 13% 94%); /* Shimmer highlight */
--ghostly-radius: 4px; /* Border radius */
--ghostly-speed: 1.5s; /* Animation duration */
}
.dark {
--ghostly-color: hsl(220 13% 18%);
--ghostly-shine: hsl(220 13% 25%);
}
```
### Animations
Shimmer: gradient sweep left-to-right (default, most professional)
Pulse: opacity fades 100% → 40% → 100%
Wave: cascading opacity with staggered delays per child (80ms intervals, up to 12 children)
None: static blocks, no animation
### Dark Mode Detection
Automatic via three methods (priority order):
1. `.dark` CSS class (Tailwind convention)
2. `data-theme="dark"` attribute (shadcn/ui convention)
3. `prefers-color-scheme: dark` media query (system preference)
### Reduced Motion
All animations disabled when `prefers-reduced-motion: reduce` is active. Skeletons render as static blocks.
### HTML Attributes
`data-ghostly="shimmer|pulse|wave"`: Set by Ghostly component. Activates skeleton CSS on all descendants.
`data-ghostly-ignore`: Add manually to exclude an element and its children from skeleton effect.
`aria-busy="true"`: Set automatically when loading.
`aria-live="polite"`: Set automatically for screen reader announcements.
### Element Coverage
Text elements covered: h1-h6, p, span, a, li, td, th, dt, dd, label, legend, figcaption, caption, summary, blockquote, cite, q, em, strong, small, mark, code, pre, button, input, textarea, select, option, fieldset, dialog, details, address.
Media elements covered: img, svg, video, canvas, picture, iframe.
### Minimum Dimensions (prevents empty element collapse)
h1: 1.75em height, 40% width
h2: 1.5em height, 50% width
h3: 1.3em height, 55% width
p: 3em height, 80% width
pre: 5em height, 100% width
input/textarea: 2.5rem height, 6rem width
button: 2.25rem height, 5rem width
img (no size): 12rem height, 100% width
svg: 1.5rem height/width
### Specificity
Ghostly uses `:where()` selectors (zero specificity). Override with a single class selector:
```css
[data-ghostly] p { min-height: 2em; }
```
---
## TypeScript Types
```tsx
type GhostlyAnimation = 'shimmer' | 'pulse' | 'wave' | 'none'
type GhostlyRadius = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'full'
type GhostlySpeed = 'slow' | 'normal' | 'fast'
interface GhostlyConfig { animation?: GhostlyAnimation; radius?: GhostlyRadius; speed?: GhostlySpeed }
```
Radius values: none=0px, xs=2px, sm=4px, md=8px, lg=12px, full=9999px
Speed values: slow=2s, normal=1.5s, fast=0.8s
---
## Patterns
### Suspense fallback
```tsx
}>
```
### Next.js loading.tsx
```tsx
export default function Loading() {
return (
} className="grid grid-cols-3 gap-4">
<>>
)
}
```
### TanStack Query
```tsx
const { data, isLoading } = useQuery({ queryKey: ['user'], queryFn: fetchUser })
```
### SWR
```tsx
const { data, isLoading } = useSWR('/api/posts', fetcher)
}>{data?.map(...)}
```
### Exclude elements
```tsx
{data?.title}
```
### Custom colors per section
```tsx
```
### Multiple independent sections
```tsx
} animation="wave">
{orders.data?.map(...)}
```
---
## How It Works
Your component IS the skeleton. Ghostly adds `data-ghostly` attribute to a wrapper. CSS rules then:
1. Make text transparent + show background color (skeleton block)
2. Hide images via object-position offset + show background color
3. Apply animation (shimmer gradient, pulse opacity, wave stagger)
4. Block pointer events and user selection
5. Set min-height to prevent empty element collapse
When loading ends, the attribute is removed. No layout shift because the skeleton uses the exact same layout.
Zero runtime JavaScript for rendering. CSS animations are GPU-accelerated and off the main thread.
## Bundle Size
@ghostly-ui/core (CSS): ~2KB gzipped
@ghostly-ui/react: ~1KB gzipped
Total: ~3KB gzipped
## Browser Support
Chrome 88+, Firefox 78+, Safari 14+, Edge 88+, iOS Safari 14+. Limiting factor: `:where()` selector.
## Component Requirements
Components should handle undefined data with optional chaining (`data?.title`). This is standard React practice.
---
## Links
- GitHub: https://github.com/AdanSerrano/ghostly
- npm @ghostly-ui/core: https://www.npmjs.com/package/@ghostly-ui/core
- npm @ghostly-ui/react: https://www.npmjs.com/package/@ghostly-ui/react