Skip to content
Development
Skill

/skeleton-loaders

Builds skeleton loading states that are pixel-perfect matches of real content. Use this skill whenever adding loading states to components, building skeletons for async data, handling pending loader states in route transitions, or implementing the shimmer animation pattern. Also

From plugin
gaia
2619 skills18 agents12 commands

Claude Code

$ npx -y skills add gaia-harness/gaia --skill skeleton-loaders --agent claude-code

SKILL.md

skeleton-loaders.SKILL.md
name: skeleton-loaders
description: Builds skeleton loading states that are pixel-perfect matches of real content. Use this skill whenever adding loading states to components, building skeletons for async data, handling pending loader states in route transitions, or implementing the shimmer animation pattern. Also trigger when the user asks about preventing layout shift during data fetching.

Skeleton Loaders

Contents: Transparent Text Technique; 200ms Delay Pattern; Accessibility.

Transparent Text Technique

Use real HTML elements (`<p>`, `<span>`, `<h2>`, `<button>`) with the same font classes as the real component, plus shimmer + transparency classes. This makes skeletons inherit exact line-height, font-size, and weight, producing pixel-perfect dimensions without hardcoded `h-*`/`w-*` values.

Define the shimmer animation once

`animate-shimmer` is not a built-in Tailwind utility. If `app/styles/tailwind.css` has no `--animate-shimmer`, add the token and its keyframes inside the existing `@theme` block before using the class (otherwise `better-tailwindcss/no-unknown-classes` fails lint):

@theme {
  --animate-shimmer: shimmer 2s linear infinite;
  /* The gradient is 200% wide (bg-size-[200%_100%]); moving background-position from 100% to -100% shifts it one full tile, so the sweep loops seamlessly. */
  @keyframes shimmer {
    from {
      background-position: 100% 0;
    }
    to {
      background-position: -100% 0;
    }
  }
}

Shimmer class constant

Define a shared class string at the top of the skeleton component:

const shimmer =
  'animate-shimmer rounded-sm bg-linear-to-r from-muted via-accent to-muted bg-size-[200%_100%] text-transparent select-none';

Text elements

Copy the real component's element type and font classes, add `shimmer`. How you fill the text depends on whether the real text is static or dynamic:

  • **Static, translatable text** (labels, headings, button text): use the same `t()` value the real component uses. The text is transparent, but reusing `t()` makes the skeleton width match the revealed text exactly.
  • **Dynamic runtime values** (`{data.name}`, API content): you cannot know the real value, so use hardcoded placeholder text of similar character count to approximate its width.
import {cn} from 'cn';

// Real component
<h2 className="text-lg font-bold text-foreground">{t('profile.heading')}</h2>   // static
<p className="truncate text-sm font-semibold text-foreground">{data.name}</p>   // dynamic
<p className="text-xs text-muted-foreground">{data.value}</p>                     // dynamic

// Skeleton
<h2 className={cn('text-lg font-bold', shimmer)}>{t('profile.heading')}</h2>  // same t(), exact width
<p className={cn('truncate text-sm font-semibold', shimmer)}>Name</p>         // approximate
<p className={cn('text-xs', shimmer)}>Example value</p>                        // approximate

Non-text elements (images, icons, avatars)

Keep as empty divs with the shimmer class, no text needed:

<div className={cn('size-14 shrink-0', shimmer)} />

Interactive elements (buttons)

Use the real element type with `tabIndex={-1}` to prevent focus. Button labels are static text, so use the real `t()` value (exact width on reveal):

<button
  className={cn('w-full py-2 text-xs font-medium', shimmer)}
  tabIndex={-1}
  type="button"
>
  {t('common.submit')}
</button>

200ms Delay Pattern

Avoid skeleton flash on fast loads. Show stale/empty content for 200ms before revealing the skeleton:

const [showSkeleton, setShowSkeleton] = useState(false);

// Valid Effect: synchronizing component state with a timer (external system).
// The cleanup prevents a state update after unmount.
useEffect(() => {
  const timer = setTimeout(() => setShowSkeleton(true), 200);
  return () => clearTimeout(timer);
}, []);

if (!showSkeleton) return null; // or return stale content
return <MySkeleton />;

Accessibility

Skeleton text is transparent, but screen readers still announce it (placeholder text and any `t()` labels). Hide the skeleton from assistive tech and announce loading instead:

  • Put `aria-hidden` on the skeleton's root element so assistive tech skips the decorative placeholders.
  • Mark the region that will receive the content with `aria-busy="true"` while loading, or render a visually-hidden `role="status"` "Loading" message so screen-reader users know content is on the way.
Read more
@gaia-react
Ships withgaia

Claude is raw power. GAIA is order and focus. A serious harness for Claude Code. It keeps Claude-shipped code production-grade as your team scales. The React frontend is handled. You build the rest of your app on top.

2619 skills18 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
26
Star history of gaia-react/gaia on GitHub, a chart by star-history.com

Other skills on gaia.

file-tech-debt

file-tech-debt

Files a new tech-debt GitHub issue for an out-of-scope code-review finding. Builds the dedup key, skips an open or…

@gaia-react@gaia-reactInstall Skill
gaia-wiki

gaia-wiki

Runs GAIA wiki maintenance (sync, consolidate, lint with fixes) in one command and lands it as one commit and PR. Trigger on…

@gaia-react@gaia-reactInstall Skill
naming-conventions

naming-conventions

Applies descriptive, self-documenting naming in any language (TypeScript, bash, workflow YAML, Dockerfiles). Use this skill…

@gaia-react@gaia-reactInstall Skill
release-notes

release-notes

Maintainer-only. Rewrites a version's GAIA CHANGELOG entries as plain-language public release notes for the marketing site…

@gaia-react@gaia-reactInstall Skill
tdd

tdd

Runs test-driven development with a red-green-refactor loop, an opt-in tool for logic where test-first helps. Use when the user…

@gaia-react@gaia-reactInstall Skill
update-deps

update-deps

Remediates dependencies in place of Dependabot version and security updates. Discovers outdated packages and open security…

@gaia-react@gaia-reactInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills10d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc11d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react1d252k
@facebook@facebookInstall Skill

Questions about skeleton-loaders.

Which coding agents can use skeleton-loaders?
skeleton-loaders installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.