Skip to main content

Loading Text

Animated shimmer text with cycling messages and color variants for engaging loading states

Loading Text

The LoadingText component provides animated shimmer text effects with cycling messages and color variants, creating engaging loading states that communicate progress to users.

Overview

Loading text serves as a lightweight alternative to spinners and progress bars, providing contextual feedback through animated text that shimmers and cycles through relevant messages. It’s particularly effective for operations where specific progress cannot be measured.

Interactive Demo

Interactive Loading Text

Customize size, variant, and animation behavior

Size:
Variant:
Loading Text Configuration
Adjust size, color, and animation settings

Message Cycling

Default cycling messages when animation is enabled

Auto-Cycling Messages

Automatically cycles through loading messages

Cycles: "Loading..." → "Preparing..." → "Almost there..." → "Fetching..."
<LoadingText animate=true />

API Reference

Props

PropTypeDefaultDescription
textstring'Loading...'Loading message text
size'sm' \| 'md' \| 'lg' \| 'xl''md'Text size variant
variant'default' \| 'jasper' \| 'info''default'Color variant
animatebooleantrueEnable animation and message cycling
classstring''Additional CSS classes

Usage Examples

Basic Loading Text

<script>
import LoadingText from '$lib/loading/components/LoadingText.svelte';

let isLoading = $state(false);

async function fetchData() {
  isLoading = true;
  try {
    await api.getData();
  } finally {
    isLoading = false;
  }
}
</script>

{#if isLoading}
  <LoadingText
    text="Fetching latest data..."
    size="md"
    variant="default"
    animate={true}
  />
{/if}

Custom Loading Messages

<script>
let uploadStatus = $state('idle'); // 'idle' | 'uploading' | 'processing'
let fileName = $state('document.pdf');

$: loadingMessage = uploadStatus === 'uploading'
  ? `Uploading ${fileName}...`
  : uploadStatus === 'processing'
  ? `Processing ${fileName}...`
  : 'Ready to upload';
</script>

{#if uploadStatus !== 'idle'}
  <LoadingText
    text={loadingMessage}
    variant="info"
    size="lg"
    animate={uploadStatus === 'processing'}
  />
{/if}

Form Integration

<script>
let submissionState = $state('idle'); // 'idle' | 'validating' | 'submitting' | 'success'

const stateMessages = {
  validating: 'Validating form data...',
  submitting: 'Submitting your information...',
  success: 'Form submitted successfully!'
};

async function handleSubmit() {
  submissionState = 'validating';

  // Validate
  await new Promise(resolve => setTimeout(resolve, 1000));

  submissionState = 'submitting';

  // Submit
  await new Promise(resolve => setTimeout(resolve, 2000));

  submissionState = 'success';

  // Reset after success message
  setTimeout(() => submissionState = 'idle', 2000);
}
</script>

<form onsubmit|preventDefault={handleSubmit}>
  <!-- Form fields -->

  {#if submissionState !== 'idle'}
    <LoadingText
      text={stateMessages[submissionState]}
      variant={submissionState === 'success' ? 'jasper' : 'default'}
      size="md"
      animate={submissionState !== 'success'}
    />
  {:else}
    <button type="submit">Submit Form</button>
  {/if}
</form>

Inline Button Loading

<script>
let buttonStates = $state({
  save: false,
  delete: false,
  export: false
});

async function handleAction(action) {
  buttonStates[action] = true;

  try {
    await performAction(action);
  } finally {
    buttonStates[action] = false;
  }
}
</script>

<div class="action-buttons">
  <button
    onclick={() => handleAction('save')}
    disabled={buttonStates.save}
  >
    {#if buttonStates.save}
      <LoadingText text="Saving..." size="sm" animate={false} />
    {:else}
      Save Changes
    {/if}
  </button>

  <button
    onclick={() => handleAction('delete')}
    disabled={buttonStates.delete}
  >
    {#if buttonStates.delete}
      <LoadingText text="Deleting..." size="sm" variant="info" animate={false} />
    {:else}
      Delete Item
    {/if}
  </button>

  <button
    onclick={() => handleAction('export')}
    disabled={buttonStates.export}
  >
    {#if buttonStates.export}
      <LoadingText text="Exporting..." size="sm" variant="jasper" animate={true} />
    {:else}
      Export Data
    {/if}
  </button>
</div>

With Loading States

<script>
import LoadingText from '$lib/loading/components/LoadingText.svelte';

let dataState = $state('loading'); // 'loading' | 'error' | 'empty' | 'loaded'
let retryCount = $state(0);

$: loadingMessage = retryCount > 0
  ? `Retrying connection (${retryCount}/3)...`
  : 'Loading your dashboard...';

async function loadData() {
  dataState = 'loading';

  try {
    const data = await api.fetchDashboard();
    if (data.length === 0) {
      dataState = 'empty';
    } else {
      dataState = 'loaded';
    }
  } catch (error) {
    dataState = 'error';
  }
}

async function retryLoad() {
  retryCount++;
  await loadData();
}
</script>

<div class="dashboard">
  {#if dataState === 'loading'}
    <LoadingText
      text={loadingMessage}
      variant="default"
      size="lg"
      animate={true}
    />
  {:else if dataState === 'error'}
    <div class="error-state">
      <p>Failed to load dashboard</p>
      <button onclick={retryLoad}>
        Try Again
      </button>
    </div>
  {:else if dataState === 'empty'}
    <div class="empty-state">
      <p>No data available</p>
    </div>
  {:else}
    <!-- Dashboard content -->
  {/if}
</div>

Message Cycling

When animate is true and using the default “Loading…” text, the component automatically cycles through these messages:

  1. “Loading…”
  2. “Preparing content…”
  3. “Almost there…”
  4. “Fetching data…”

Each message displays for 3 seconds before transitioning to the next. This cycling only occurs with the default text - custom messages remain static.

// Built-in cycling messages
const loadingMessages = [
  'Loading...',
  'Preparing content...',
  'Almost there...',
  'Fetching data...'
];

// Cycles every 3000ms when animate=true and text="Loading..."

Size Guidelines

Small (sm)

  • Font size: Muted typography size
  • Use for: Inline elements, button states, subtle indicators
  • Best in: Navigation items, form controls, small cards

Medium (md) - Default

  • Font size: Caption typography size
  • Use for: Standard loading states, form sections
  • Best in: Content areas, modal dialogs, panels

Large (lg)

  • Font size: Subheading typography size
  • Use for: Primary loading states, section headers
  • Best in: Main content areas, page sections, large components

Extra Large (xl)

  • Font size: Heading typography size
  • Use for: Full-page loading, application initialization
  • Best in: Splash screens, initial app loading, major transitions

Color Variants

Default Shimmer

  • Colors: Foreground color gradients (tertiary → secondary → primary)
  • Use for: General loading states, neutral contexts
  • Accessibility: High contrast across all themes

Jasper Accent

  • Colors: Jasper color gradients (600 → 500 → 400)
  • Use for: Primary actions, important operations, brand-focused loading
  • Context: Payment processing, premium features, key workflows

Info Accent

  • Colors: Sky blue gradients (600 → 500 → 400)
  • Use for: Information retrieval, data processing, neutral actions
  • Context: Data fetching, API calls, background operations

Animation Details

Shimmer Effect

  • Duration: 3 seconds per cycle
  • Easing: Linear for smooth, continuous motion
  • Direction: Left to right shimmer sweep
  • Hardware acceleration: Uses transform for optimal performance

Text Cycling

  • Interval: 3 seconds per message
  • Transition: Instant text change with continuous shimmer
  • Loop: Cycles through all messages repeatedly
  • Pause: Shimmer continues during text changes

Accessibility Features

Screen Reader Support

  • role="status": Indicates dynamic content updates
  • aria-live="polite": Announces text changes without interrupting
  • Clear messaging: Uses descriptive, action-oriented text

Visual Accessibility

  • High contrast: Shimmer effect maintains readable contrast ratios
  • Scalable text: Respects user font size preferences
  • Motion consideration: Shimmer is subtle and not overwhelming

Reduced Motion

The component respects prefers-reduced-motion settings:

@media (prefers-reduced-motion: reduce) {
  .loading-text-shimmer {
    animation: none;
    background: var(--foreground);
    -webkit-text-fill-color: inherit;
  }
}

Best Practices

When to Use Loading Text

Use loading text for:

  • Short operations (2-10 seconds)
  • Indeterminate progress operations
  • Inline loading states
  • Button loading states
  • Contextual loading feedback

Don’t use loading text for:

  • Long operations (use progress bars)
  • Operations with measurable progress
  • Critical system operations (use modal overlays)
  • Silent background operations

Message Guidelines

Good loading messages:

  • “Saving your changes…”
  • “Connecting to server…”
  • “Processing payment…”
  • “Fetching latest data…”

Avoid:

  • Technical jargon: “Executing POST request…”
  • Vague messages: “Please wait…”
  • Overly long: “We’re currently processing your request and will have results shortly…”
  • Negative framing: “This might take a while…”

Size and Context

Small text for:

  • Button labels during loading
  • Inline status updates
  • Navigation items
  • Form field validation

Medium text for:

  • Card content loading
  • Section updates
  • Modal dialogs
  • Standard feedback

Large/XL text for:

  • Primary loading states
  • Full-page operations
  • Application initialization
  • Critical user workflows

Performance Considerations

  • Text shimmer uses CSS animations for optimal performance
  • Component only renders when needed (conditional rendering recommended)
  • Animations use hardware acceleration where possible
  • Memory cleanup happens automatically on component unmount
  • Message cycling intervals are cleaned up properly