ProductBrand
Join us
Foundations
  • Introduction
  • Colors
  • Typography
  • Icons
  • Illustrations
  • Logos
  • Shadows
Components
  • Accordion
  • Alert Dialog
  • Avatar
  • Badge
  • Button
  • Calendar
  • Card
  • Chart
  • Checkbox
  • Collapsible
  • Combobox
  • Context Menu
  • Date Picker
  • Dialog
  • Drawer
  • Dropdown Menu
  • Fluid Avatar
  • Hover Scroll Text
  • Input
  • Kbd
  • Popover
  • Progress Bar
  • Radio Group
  • Scroll Area
  • Select
  • Selectors
  • Separator
  • Separator Dot
  • Shimmer Text
  • Skeleton
  • Slider
  • Switch
  • Tabs
  • Textarea
  • Toast
  • Tooltip
Compositions
  • Empty State
  • File Attachment
  • Floating Bar
  • Input Banner
  • Markdown Editor
  • Media Controls
  • Product Feature Banner
  • Panels
  • Sidebar
  • Reasoning Trace
  • Pagination
  • Data Table
  • Breadcrumb
  • Thread Outline
Inline
  • Overview
  • Stat Tile
  • Metric Row
  • Comparison Table
  • Chart Card
  • Trend Tile
  • Cite
  • Custom
  • Agent skill

Skeleton

Placeholder blocks and text lines shown while content loads.

Examples

Block

Loading preview

Text lines

Loading summary

Card placeholder

Loading card

Pulse only

Loading chart

Caller-owned semantics

Skeleton or Shimmer Text?

Two different jobs, easy to confuse by name:

  • Skeleton is a loading placeholder, a grey block or line where content will appear. Reach for it whenever you know the shape of what's coming.
  • ShimmerText is animated text, a gradient sweeping across words that already exist ("Thinking…", "Generating response…"). It needs real text to animate.

Never shimmer long-form content to signal loading, and never use an empty Skeleton where a short status label would tell the user more.

Usage

tsx
import { Skeleton, SkeletonGroup } from "@rogo-technologies/ui/skeleton";

<Skeleton className="h-24 w-full" />;

Shapes

variant="block" (the default) is a rounded box; give it a height. variant="text" is a line sized in em, so it matches the type it stands in for.

tsx
<Skeleton className="h-24 w-full" />
<Skeleton variant="text" width="60%" />

width takes any CSS length (a number is read as pixels). Tailwind width classes work just as well, so use whichever reads better where a width is one of several dimensions being set.

Groups

A loading state goes inside a SkeletonGroup, which owns the live region so the set announces once. Settings > Skills renders 101 placeholders at once, which is why the group announces and the boxes don't.

tsx
<SkeletonGroup className="space-y-2" label="Loading team members">
  <Skeleton variant="text" width="60%" />
  <Skeleton variant="text" width="100%" />
</SkeletonGroup>

Shimmer sweep

Every placeholder sweeps a highlight across itself on top of the pulse, so a loading state reads the same everywhere without each caller opting in. Pass shimmer={false} where the sweep is the wrong texture: a dense grid of small placeholders, or a box whose own animation already carries the motion.

tsx
<Skeleton className="aspect-video w-full" />
<Skeleton variant="text" width="60%" shimmer={false} />

Accessibility

The announcing unit is the loading state, not the box. A SkeletonGroup is a live region: role="status", aria-live="polite", and an sr-only label ("Loading" unless you pass label). Every Skeleton inside it is aria-hidden, so a grid of a hundred placeholders announces once.

  • A Skeleton on its own is decorative and silent. Pass announce (with a label) for a lone placeholder that stands in for a whole region with nothing else to announce it.
  • announce is ignored inside a group, so nesting can't produce a second announcement.
  • Passing your own role, aria-label, aria-labelledby, or aria-live also keeps the placeholder in the accessibility tree, since the component won't hide semantics a caller wrote. Prefer announce + label; they do the same thing without the wiring.
  • Both the pulse and the shimmer sweep stop under prefers-reduced-motion; the placeholder stays visible.
  • Reach for SkeletonGroup by default. A loading state that says nothing to a screen reader is the failure mode this component exists to fix.

API

Skeleton

PropTypeDefaultDescription
variant"block" | "text""block"Rounded box, or a line of text sized in em.
widthnumber | string-CSS width; a number is read as pixels.
shimmerbooleantrueSweeping highlight over the pulse; false leaves the pulse alone.
labelstring"Loading"Screen-reader text; reaches the a11y tree with announce.
announcebooleanfalseMakes a lone placeholder its own live region.
classNamestring-Additional classes: height, width, radius.

Any other <div> attribute passes through.

SkeletonGroup

PropTypeDefaultDescription
labelstring"Loading"Screen-reader text for the whole group.
classNamestring-Layout classes for the placeholder set.

Guidelines

Do

  • Match the shape and rough size of the content being loaded, so nothing jumps when it arrives
  • Wrap multi-placeholder loading states in SkeletonGroup
  • Prefer a skeleton over a bare "Loading…" line for content whose layout you already know

Don't

  • Don't animate a skeleton that may stay on screen for minutes. Show progress or an explanation instead
  • Don't build a placeholder out of animate-pulse divs; that's what this component is
  • Don't leave the sweep on a dense grid of small placeholders. Pass shimmer={false}; the pulse alone is calmer
PreviousShimmer Text
NextSlider
Made in NYC© 2026 Rogo Technologies Inc.

Design at Rogo

We’re redesigning an entire industry. Come design it with us.

See open roles

Design at Rogo

  • How we workThe mission of design at Rogo, and the open roles
  • About RogoThe company and the product