Skip to content
SuperCoach

Components · Feedback

Skeleton

Beta

A placeholder while content loads.

Clients

Loading clients

When to use

  • While a page or region loads content whose shape you know: the client list, a program card, a check-in thread.
  • On first load, and when a filter or tab change replaces the whole region.

When not to use

  • For an action the coach started, such as saving a meal: show a spinner in the button and keep the content in place.
  • For a task with a known length, such as an upload: use Progress.
  • For loads that are usually instant (under a second): show nothing, so the page doesn't flash.
  • When loading failed or there is nothing to show: replace it with an error Alert or an empty state. A skeleton that never resolves looks like a slow page.

Anatomy

  1. 1. Shape: matches what it stands for, here a round avatar
  2. 2. Line: the height of the text it replaces, wider for titles
  3. 3. Shorter line: secondary text, so the layout reads like the real row

A skeleton is one div. Build the placeholder from several, sized with spacing steps and fractions (h-4 w-3/5), and wrap them in the region that is loading.

Variants

There is one style. Compose it into the shape of the content it replaces.

Loading program
PatternShapes
List row (client, invoice)A round avatar and two lines, three to five rows
Card (program, workout)A title line, a shorter description line, and pill shapes for badges
TableOne line per cell, header kept as real text

States

StateWhat changesToken
LoadingFill pulses between full and half opacitybg-accent, animate-pulse
Reduced motionThe pulse stops; the fill staysmotion-reduce:animate-none
LoadedThe skeleton is replaced by the content in the same place, and aria-busy is removednone

The component doesn't stop the pulse on its own. Add motion-reduce:animate-none to each skeleton, as in the examples, until it does.

Usage guidelines

  • Match the real layout, so nothing jumps when content arrives: same heights, same gaps, same number of columns.
  • Keep real text that is already known, such as page titles, column headers and buttons. Only the data is a skeleton.
  • Use three to five rows for a list, not a full page of them.
Do.The shape of a client row: avatar, name, program.
Don't.One large block. It says nothing about what's coming, and the layout jumps when it loads.

Content guidelines

A skeleton has no visible text. Its hidden label names what is loading: "Loading clients", "Loading program". Arabic: "جارٍ تحميل العملاء".

Accessibility

Skeletons are not focusable and have no keyboard behavior.

  • Hide the shapes from assistive technology with aria-hidden. They mean nothing read aloud.
  • Set aria-busy="true" on the region that is loading, and remove it when the content arrives.
  • Add a visually hidden label inside the region, such as "Loading clients", so a screen reader user who reaches it knows why it's empty. A screen reader reads "Clients, Loading clients".
  • If the coach is waiting on the load, announce the result once in a role="status" message outside the busy region, such as "18 clients".

RTL

Shapes are placed with the layout, so they follow it: the avatar sits at the start (right in RTL) and lines align to the start. Nothing in the skeleton itself mirrors.

Tokens used

Read from components/ui/skeleton.tsx at build time, so this list changes when the component does. Opacity modifiers such as /90 are shadcn hover and focus tints on top of the token.

Semantic color tokens used by this component
TokenUtilitiesSuperCoachCoach
--accentbg-accentbrand-50orange-50
  • Radiusrounded-md

Props

<Skeleton>

Also takes every prop of <div>

No props of its own.

Takes every div prop. Size it with className.

Replaces in coach-admin

From the read-only audit of coach-admin: 90 style values, 115 uses.

coach-admin has a shared skeleton loader and several page-specific ones (settlements, plan cards, calendar tasks, package forms). All of them become compositions of this component.

Classes and selectors

  • .settlements-skeleton__fill11
  • .skeleton-loader6
  • .plan-card-skeleton__fill6
  • .calendar-task-card__skeleton-title4
  • .calendar-task-card__skeleton-icon3
  • .calendar-task-card__skeleton-sub-text, .calendar-task-card_3
  • .settlements-skeleton__sr-only3
  • .plan-card-skeleton__name3
  • .package-form-skeleton__row--labels2
  • .tasks-review__skeleton-card2
  • .day-column-skeleton2
  • .package-form-skeleton__field-label2

Files

  • src/views/Settings/PricingTab/plan-card-skeleton.scss13
  • src/reusableComponents/SkeletonLoader/SkeletonLoader.scss4
  • src/views/Program/SmartNutritionPreview/components/ResultsSection/ResultsSectionSkeleton.scss4

The inventory lists the first 12 locations of each value, so file counts are a lower bound.

SCSS variables in these styles

  • $invoice-section-line→ nearest neutral-200 (no semantic token)
  • $spacing-mdnot a color: spacing, size or layout
  • $light-success-colornot a color: spacing, size or layout
  • $light-warning-colornot a color: spacing, size or layout
  • $spacing-lgnot a color: spacing, size or layout
  • $spacing-xlnot a color: spacing, size or layout

Progress, Card, Table, Motion.