MobTranslate styleguide

Skeleton

Reserve the shape of forthcoming content while keeping loading, failure and recovery understandable.

Placeholder versus task progress

Use a skeleton for a predictable content shape during loading. Keep content containers stable so a finished view does not jump unexpectedly. Avoid a skeleton when there is no known shape or when its detail would imply content that may never exist.

A skeleton is a decorative div. It is not a progressbar, does not fetch anything and has no automatic announcement, timeout or failure state. The surrounding task owns those responsibilities.

Anatomy and variants

Variant / roleRendered specification
textWidth 100%, height 1rem, --radius-sm
circular--radius-full; supply explicit equal width and height
rectangularRadius 0; explicit height needed for visible area
rounded--radius-md; explicit height needed
Surface / motion--color-muted; 1.5s opacity pulse
Reduced motionStatic shape, no pulse
LayoutCaller provides dimensions and the final content region

Working loading, content and failure

Skeletons reserve a content shape. They do not prove a request is progressing.

Local content loading; choose an outcome.

tsx
'use client';
import { useRef, useState } from 'react';
import { Button, Skeleton } from '@mobtranslate/ui';

export function SkeletonCommunicationExample() {
  const [phase, setPhase] = useState<'loading' | 'ready' | 'error'>('loading');
  const reload = useRef<HTMLElement>(null);
  function finish(next: 'ready' | 'error') { setPhase(next); reload.current?.focus(); }
  return <div data-skeleton-lab className="space-y-4">
    <p>Skeletons reserve a content shape. They do not prove a request is progressing.</p>
    <div aria-busy={phase === 'loading'} aria-label="Local content example" role="region" className="min-h-36 space-y-3">
      {phase === 'loading' ? <div aria-hidden="true" className="space-y-3">
        <Skeleton variant="circular" width={48} height={48} />
        <Skeleton variant="text" width="70%" />
        <Skeleton variant="rounded" height={32} />
        <Skeleton variant="rectangular" height={24} width="45%" />
      </div> : phase === 'ready' ? <><h3 className="font-bold">Local example content</h3><p>The content is now available. No language entry or recording was invented.</p></> : <p>Simulated loading failure. Try loading the example again.</p>}
    </div>
    <div className="flex flex-wrap gap-3">
      <Button ref={reload} type="button" variant="outline" onClick={() => setPhase('loading')}>Load local content</Button>
      {phase === 'loading' && <><Button type="button" onClick={() => finish('ready')}>Simulate content ready</Button><Button type="button" variant="secondary" onClick={() => finish('error')}>Simulate content failure</Button></>}
    </div>
    <p role="status">{phase === 'loading' ? 'Local content loading; choose an outcome.' : phase === 'ready' ? 'Local content ready.' : 'Local content failed; retry is available.'}</p>
  </div>;
}

API reference

PropContract
varianttext | circular | rectangular | rounded; text default
width / heightCSS string or React numeric px value; optional
styleCaller style wins over width/height shorthand
className / native div props / refHTMLDivElement; no interactive state
aria-hidden / surrounding aria-busyAuthored explicitly; wrapper does not infer them
childrenNative content allowed, but avoid meaningful data inside a decorative placeholder

Behavior and accessibility

  • Hide decorative shapes from accessibility APIs and expose one concise task-level status.
  • Use aria-busy on the content region while content is pending, not on an unrelated whole page.
  • When loading finishes, replace shapes with actual source-backed content. Failure replaces the skeleton with an explanation and retry.
  • Do not animate an endless placeholder after a request has already failed.
  • Keep a stable enabled action/focus destination when simulation outcome controls disappear.

Failure cases

  • A circular or rectangular skeleton with no height and no visible area.
  • Screen readers encountering a list of meaningless shapes.
  • Pulsing placeholders presented as measured progress.
  • Fake vocabulary, imagery or recordings used to fill the promised shape.
  • A loading state without any path to failure, cancellation or retry.

Verification and release scope

  • Operate the checked example with keyboard and pointer; inspect the actual name, role, relationships and focus destination.
  • Check both themes, narrow reflow, long labels, reduced motion and forced colors. Text contrast and meaningful graphic contrast are separate checks.
  • Test the relevant waiting, completion, failure, cancellation and recovery paths. A local simulation is not a verified backend result.
  • Use a named screen-reader/browser pair to verify spoken output. DOM attributes and automated passes do not prove the announcement.
  • Retain source, review and permission evidence when applying these controls to a real language workflow. Color and motion cannot establish authority.