Loader

Visual feedback during asynchronous operations with four animated types and flexible sizing

Overview

Loaders (also called spinners or throbbers) provide visual feedback during asynchronous operations. They indicate that content is loading, a process is running, or the application is waiting for a response. Domma provides four animated loader types with flexible sizing, colouring, and positioning options.

Key Features

  • Four Animation Types - Spinner, dots, pulse, and bars
  • Flexible Sizing - Small, medium, large, or custom pixel size
  • Theme-Aware Colours - Primary, secondary, success, danger, or custom hex
  • Overlay Mode - Cover entire container with backdrop
  • Loading Text - Optional text label below animation
  • Static Helpers - Fullscreen loader and quick show/hide methods

When to Use Loaders

  • API Calls - Show whilst fetching data from a server
  • File Uploads - Indicate progress during file transfers
  • Page Transitions - Display whilst loading new content
  • Heavy Computations - Provide feedback during intensive JavaScript operations

Basic Usage

<!-- HTML Container -->
<div id="my-container" class="demo-area-min-h-120">
    <!-- Loader will be inserted here -->
</div>

<!-- JavaScript Initialisation -->
<script>
const loader = Domma.elements.loader('#my-container', {
    type: 'spinner',      // 'spinner', 'dots', 'pulse', 'bars'
    size: 'medium',       // 'small', 'medium', 'large', or number (px)
    color: 'primary'      // 'primary', 'secondary', 'white', or hex
});

// Show/hide
loader.show();
loader.hide();
loader.toggle();
</script>

Web Components

Domma Loader is now available as a native Web Component! Use modern <domma-loader> tags or the traditional wrapper API - both work identically with zero breaking changes.

Direct Web Component Usage - Animation Types

All four loader animations available as custom elements:

Direct Web Component Usage - Animation Types

Spinner

Dots

Pulse

Bars

<div style="width: 100px; height: 100px; position: relative; border: 1px solid var(--dm-border); border-radius: 8px;">
    <domma-loader type="spinner" size="medium" color="primary"></domma-loader>
    <p style="text-align: center; margin-top: 0.5rem; font-size: 0.75rem;">Spinner</p>
</div>
<div style="width: 100px; height: 100px; position: relative; border: 1px solid var(--dm-border); border-radius: 8px;">
    <domma-loader type="dots" size="medium" color="success"></domma-loader>
    <p style="text-align: center; margin-top: 0.5rem; font-size: 0.75rem;">Dots</p>
</div>
<div style="width: 100px; height: 100px; position: relative; border: 1px solid var(--dm-border); border-radius: 8px;">
    <domma-loader type="pulse" size="medium" color="info"></domma-loader>
    <p style="text-align: center; margin-top: 0.5rem; font-size: 0.75rem;">Pulse</p>
</div>
<div style="width: 100px; height: 100px; position: relative; border: 1px solid var(--dm-border); border-radius: 8px;">
    <domma-loader type="bars" size="medium" color="warning"></domma-loader>
    <p style="text-align: center; margin-top: 0.5rem; font-size: 0.75rem;">Bars</p>
</div>
<!-- Container with loader -->
<div style="position: relative; width: 100px; height: 100px;">
    <domma-loader type="spinner" size="medium" color="primary"></domma-loader>
</div>

<domma-loader type="dots" size="medium" color="success"></domma-loader>
<domma-loader type="pulse" size="medium" color="info"></domma-loader>
<domma-loader type="bars" size="medium" color="warning"></domma-loader>

Size Variants

Choose from predefined sizes or use custom pixel values:

Size Variants
<div style="width: 80px; height: 80px; position: relative; border: 1px solid var(--dm-border); border-radius: 8px;">
    <domma-loader type="spinner" size="small" color="primary"></domma-loader>
</div>
<div style="width: 120px; height: 120px; position: relative; border: 1px solid var(--dm-border); border-radius: 8px;">
    <domma-loader type="spinner" size="medium" color="primary"></domma-loader>
</div>
<div style="width: 160px; height: 160px; position: relative; border: 1px solid var(--dm-border); border-radius: 8px;">
    <domma-loader type="spinner" size="large" color="primary"></domma-loader>
</div>
<domma-loader type="spinner" size="small" color="primary"></domma-loader>
<domma-loader type="spinner" size="medium" color="primary"></domma-loader>
<domma-loader type="spinner" size="large" color="primary"></domma-loader>

<!-- Custom size in pixels -->
<domma-loader type="spinner" size="80" color="primary"></domma-loader>

With Loading Text

With Loading Text
<div style="width: 200px; height: 150px; position: relative; border: 1px solid var(--dm-border); border-radius: 8px;">
    <domma-loader type="spinner" size="medium" color="primary" text="Loading..."></domma-loader>
</div>
<div style="width: 200px; height: 150px; position: relative; border: 1px solid var(--dm-border); border-radius: 8px;">
    <domma-loader type="dots" size="medium" color="success" text="Please wait"></domma-loader>
</div>
<domma-loader type="spinner" size="medium" color="primary" text="Loading..."></domma-loader>
<domma-loader type="dots" size="medium" color="success" text="Please wait"></domma-loader>

Overlay Mode

Cover entire container with a dark backdrop:

Overlay Mode

Content behind the overlay

<div style="width: 300px; height: 200px; position: relative; border: 1px solid var(--dm-border); border-radius: 8px; background: var(--dm-bg-secondary);">
    <p style="padding: 1rem;">Content behind the overlay</p>
    <domma-loader type="spinner" size="large" color="white" text="Loading..." overlay=""></domma-loader>
</div>
<div style="position: relative; width: 300px; height: 200px;">
    <p>Content behind overlay</p>
    <domma-loader type="spinner" size="large" color="white" text="Loading..." overlay></domma-loader>
</div>

Programmatic API

Control loader visibility and appearance dynamically:

Programmatic API
<div style="width: 200px; height: 150px; position: relative; border: 1px solid var(--dm-border); border-radius: 8px; margin-bottom: 1rem;">
    <domma-loader id="programmable-loader" type="spinner" size="medium" color="primary" text="Loading..." visible="false"></domma-loader>
</div>
<div>
    <button type="button" class="btn btn-sm btn-primary" data-action="1">Show</button>
    <button type="button" class="btn btn-sm btn-secondary" data-action="2">Hide</button>
    <button type="button" class="btn btn-sm btn-info" data-action="3">Toggle</button>
    <button type="button" class="btn btn-sm btn-success" data-action="4">Change Type</button>
</div>
/**
 * Programmatic API - from public/showcase/elements/loader.
 */
export function mount(root) {
        // Web Components Section JavaScript
    const loaderTypes = ['spinner', 'dots', 'pulse', 'bars'];
    let currentTypeIndex = 0;
    function showProgrammableLoader() {
            const loader = $(root).find('#programmable-loader')[0];
            loader.show();
        };
    function hideProgrammableLoader() {
            const loader = $(root).find('#programmable-loader')[0];
            loader.hide();
        };
    function toggleProgrammableLoader() {
            const loader = $(root).find('#programmable-loader')[0];
            loader.toggle();
        };
    function changeLoaderType() {
            const loader = $(root).find('#programmable-loader')[0];
            currentTypeIndex = (currentTypeIndex + 1) % loaderTypes.length;
            const newType = loaderTypes[currentTypeIndex];
            loader.setType(newType);
            loader.setText(`Type: ${newType}`);
            loader.show();
        };

    // Handlers the old page had inline (onclick=…), wired here.
    $(root).find('[data-action="1"]').on('click', function (event) {
        showProgrammableLoader();
    });
    $(root).find('[data-action="2"]').on('click', function (event) {
        hideProgrammableLoader();
    });
    $(root).find('[data-action="3"]').on('click', function (event) {
        toggleProgrammableLoader();
    });
    $(root).find('[data-action="4"]').on('click', function (event) {
        changeLoaderType();
    });
}
const loader = $('domma-loader')[0];

// Show/hide control
loader.show();
loader.hide();
loader.toggle();

// Check state
if (loader.isVisible()) {
    console.log('Loader is visible');
}

// Update properties
loader.setText('Processing...');
loader.setType('dots');
loader.setSize('large');
loader.setColor('success');

Wrapper API (Backwards Compatible)

Existing code continues to work unchanged! The wrapper API uses Web Components internally:

Wrapper API (Backwards Compatible)
<div id="wrapper-loader-demo" style="width: 200px; height: 150px; position: relative; border: 1px solid var(--dm-border); border-radius: 8px;"></div>
/**
 * Wrapper API (Backwards Compatible) - from public/showcase/elements/loader.
 * E.loader() puts a <domma-loader> Web Component in the element.
 */
export function mount(root) {
    E.loader(root.querySelector('#wrapper-loader-demo'), {
        type: 'dots',
        size: 'large',
        color: 'success',
        text: 'Wrapper API!',
        visible: true
    });
}
// Traditional approach - still works!
const loader = Domma.elements.loader('#my-container', {
    type: 'dots',
    size: 'large',
    color: 'success',
    text: 'Loading data...',
    overlay: false
});

// Same API as before
loader.show();
loader.hide();
loader.setText('Updated text');
loader.destroy();

💡 Benefits of Web Components: Encapsulation - Loader styles isolated via Shadow DOM Four Animation Types - Spinner, dots, pulse, and bars Flexible - Works standalone or in overlay mode Backwards Compatible - Existing code works without changes

Quick Start

Get started with Loaders in seconds. Here's the simplest implementation:

Basic Spinner Loader

Create a container and initialise a loader:

Basic Spinner Loader
<div class="demo-area mb-4" id="quick-start-demo">
    <!-- Loader will be inserted here -->
</div>
/**
 * Basic Spinner Loader - from public/showcase/elements/loader.
 */
export function mount(root) {
    const quickStartLoader = E.loader(root.querySelector('#quick-start-demo'), {
            type: 'spinner',
            size: 'medium',
            color: 'primary'
        });
    quickStartLoader.show();
}
<div id="my-loader-container" class="demo-area-min-h-120">
    <!-- Loader appears here -->
</div>

<script>
const loader = Domma.elements.loader('#my-loader-container', {
    type: 'spinner',
    size: 'medium',
    color: 'primary'
});

loader.show();  // Display the loader
</script>

Static Fullscreen Loader

Quick fullscreen loader with a single line:

Static Fullscreen Loader
<button type="button" id="quick-fullscreen-btn" class="btn btn-primary">Show Fullscreen Loader</button>
/**
 * Static Fullscreen Loader - from public/showcase/elements/loader.
 */
export function mount(root) {
        // Quick Start Fullscreen
    $(root).find('#quick-fullscreen-btn').on('click', () => {
            const fs = E.fullscreenLoader('Loading something amazing...');
            setTimeout(() => fs.destroy(), 2000);
        });
}
// Show fullscreen loader for 2 seconds
const fsLoader = Domma.elements.fullscreenLoader('Loading content...');
setTimeout(() => fsLoader.destroy(), 2000);

Step-by-Step Tutorial

Let's build a data fetching interface that shows a loader whilst making an API call, then displays the results.

Step 1: Create the HTML Structure

Create a container with a button to trigger loading:

<button id="load-data-btn" class="btn btn-primary">Load Data</button>

<div id="data-container" class="demo-area mt-4"
     class="demo-area-min-h-200">
    <p class="text-center text-muted" id="placeholder-text">
        Click "Load Data" to fetch content
    </p>
    <div id="data-result" class="hidden"></div>
</div>

Step 2: Create the Loader Instance

Initialise a loader with overlay to block interaction:

const dataLoader = Domma.elements.loader('#data-container', {
    type: 'spinner',
    size: 'large',
    color: 'var(--dm-white)',
    overlay: true,           // Semi-transparent backdrop
    text: 'Loading data...'  // Text below spinner
});

Step 3: Show Loader During Async Operation

Display the loader whilst fetching data:

async function loadData() {
    // Hide placeholder
    $('#placeholder-text')[0].classList.add('hidden');

    // Show loader
    dataLoader.show();

    try {
        // Simulate API call
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();

        // Process data...
    } catch (error) {
        console.error('Failed to load data:', error);
    }
}

Step 4: Hide Loader and Display Results

Hide the loader and show fetched data:

async function loadData() {
    $('#placeholder-text')[0].classList.add('hidden');
    dataLoader.show();

    try {
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();

        // Hide loader
        dataLoader.hide();

        // Display results
        const resultEl = $('#data-result')[0];
        resultEl.innerHTML = `<h4>Data Loaded!</h4><pre>${JSON.stringify(data, null, 2)}</pre>`;
        resultEl.classList.remove('hidden');
    } catch (error) {
        dataLoader.hide();
        alert('Failed to load data');
    }
}

Step 5: Update Loader Text Dynamically

Change the loading message based on progress:

async function loadMultipleResources() {
    dataLoader.setText('Loading users...');
    dataLoader.show();

    const users = await fetch('/api/users').then(r => r.json());

    dataLoader.setText('Loading posts...');
    const posts = await fetch('/api/posts').then(r => r.json());

    dataLoader.setText('Loading comments...');
    const comments = await fetch('/api/comments').then(r => r.json());

    dataLoader.hide();
    // Display all data...
}

Step 6: Complete Working Demo

Try the complete data loading example:

Step 6: Complete Working Demo

Click "Load Data" to fetch content

Data Loaded Successfully!

Fetched at:

    {
      "users": 42,
      "posts": 128,
      "comments": 356
    }
    
<button type="button" id="tutorial-load-btn" class="btn btn-primary">Load Data</button>
<div id="tutorial-container" class="mt-4 p-4 bg-surface rounded border">
    <p class="text-center text-muted" id="tutorial-placeholder" data-if="status.value === 'idle'">
        Click "Load Data" to fetch content
    </p>
    <div id="tutorial-result" data-if="status.value === 'done'">
        <h4>Data Loaded Successfully!</h4>
        <p class="text-muted">Fetched at: <span data-bind-text="fetchedAt.value"></span></p>
        <pre class="mt-2 p-3 bg-dark text-white rounded">
    {
      "users": 42,
      "posts": 128,
      "comments": 356
    }
    </pre>
    </div>
</div>
/**
 * Step 6: Complete Working Demo - from public/showcase/elements/loader.
 *
 * The placeholder and the result panel are shown by data-if on a status
 * observable ('idle' -> 'loading' -> 'done'); only the fetch time varies.
 */
export function mount(root) {
    const status = M.observable('idle');
    const fetchedAt = M.observable('');
    const handle = M.applyBindings({status, fetchedAt}, root.querySelector('#tutorial-container'));

    // Tutorial Demo
    const tutorialLoader = E.loader(root.querySelector('#tutorial-container'), {
        type: 'spinner',
        size: 'large',
        color: 'var(--dm-white)',
        overlay: true,
        text: 'Loading data...'
    });
    $(root).find('#tutorial-load-btn').on('click', async () => {
        status.value = 'loading';
        tutorialLoader.show();

        // Simulate API call
        await new Promise(resolve => setTimeout(resolve, 1500));

        tutorialLoader.setText('Processing results...');
        await new Promise(resolve => setTimeout(resolve, 1000));

        tutorialLoader.hide();

        fetchedAt.value = D().format('HH:mm:ss');
        status.value = 'done';
    });

    return () => {
        handle.dispose();
        tutorialLoader?.destroy?.();
    };
}

Examples & Variations

Four Loader Types

Domma provides four different animation styles:

Four Loader Types

Spinner

Dots

Pulse

Bars

<div class="grid grid-cols-1 gap-4">
    <div id="type-spinner" class="p-4 bg-surface rounded border text-center">
        <p class="text-sm text-muted mb-2">Spinner</p>
    </div>
    <div id="type-dots" class="p-4 bg-surface rounded border text-center">
        <p class="text-sm text-muted mb-2">Dots</p>
    </div>
    <div id="type-pulse" class="p-4 bg-surface rounded border text-center">
        <p class="text-sm text-muted mb-2">Pulse</p>
    </div>
    <div id="type-bars" class="p-4 bg-surface rounded border text-center">
        <p class="text-sm text-muted mb-2">Bars</p>
    </div>
</div>
/**
 * Four Loader Types - from public/showcase/elements/loader.
 */
export function mount(root) {
        // Example 1: Loader Types
    const spinnerLoader = E.loader(root.querySelector('#type-spinner'), {type: 'spinner', size: 'medium', color: 'primary'});
    spinnerLoader.show();
    const dotsLoader = E.loader(root.querySelector('#type-dots'), {type: 'dots', size: 'medium', color: 'primary'});
    dotsLoader.show();
    const pulseLoader = E.loader(root.querySelector('#type-pulse'), {type: 'pulse', size: 'medium', color: 'primary'});
    pulseLoader.show();
    const barsLoader = E.loader(root.querySelector('#type-bars'), {type: 'bars', size: 'medium', color: 'primary'});
    barsLoader.show();
}
// Spinner (default)
Domma.elements.loader('#spinner-container', { type: 'spinner' }).show();

// Animated dots
Domma.elements.loader('#dots-container', { type: 'dots' }).show();

// Pulsing circle
Domma.elements.loader('#pulse-container', { type: 'pulse' }).show();

// Vertical bars
Domma.elements.loader('#bars-container', { type: 'bars' }).show();

Size Variants

Small, medium, and large sizes, plus custom pixel values:

Size Variants

Small (24px) | Medium (40px) | Large (64px) | Custom (80px)

<div class="flex items-center justify-around gap-4">
    <div id="size-small" class="p-3"></div>
    <div id="size-medium" class="p-3"></div>
    <div id="size-large" class="p-3"></div>
    <div id="size-custom" class="p-3"></div>
</div>
<p class="text-center text-sm text-muted mt-2">Small (24px) | Medium (40px) | Large (64px) | Custom
    (80px)</p>
/**
 * Size Variants - from public/showcase/elements/loader.
 */
export function mount(root) {
        // Example 2: Size Variants
    E.loader(root.querySelector('#size-small'), {type: 'spinner', size: 'small', color: 'primary'}).show();
    E.loader(root.querySelector('#size-medium'), {type: 'spinner', size: 'medium', color: 'primary'}).show();
    E.loader(root.querySelector('#size-large'), {type: 'spinner', size: 'large', color: 'primary'}).show();
    E.loader(root.querySelector('#size-custom'), {type: 'spinner', size: 80, color: 'primary'}).show();
}
// Small (24px)
Domma.elements.loader('#container', { size: 'small' }).show();

// Medium (40px - default)
Domma.elements.loader('#container', { size: 'medium' }).show();

// Large (64px)
Domma.elements.loader('#container', { size: 'large' }).show();

// Custom pixel size
Domma.elements.loader('#container', { size: 80 }).show();

Colour Variants

Theme-aware colours or custom hex values:

Colour Variants

Primary | Success | Danger | Warning | Dark | Custom (#9b59b6)

<div class="grid grid-cols-1 gap-4">
    <div id="color-primary" class="p-3 bg-surface rounded border" style="min-height: 80px; position: relative;"></div>
    <div id="color-success" class="p-3 bg-surface rounded border" style="min-height: 80px; position: relative;"></div>
    <div id="color-danger" class="p-3 bg-surface rounded border" style="min-height: 80px; position: relative;"></div>
    <div id="color-warning" class="p-3 bg-surface rounded border" style="min-height: 80px; position: relative;"></div>
    <div id="color-dark" class="p-3 bg-surface rounded border" style="min-height: 80px; position: relative;"></div>
    <div id="color-custom" class="p-3 bg-dark rounded border" style="min-height: 80px; position: relative;"></div>
</div>
<p class="text-center text-sm text-muted mt-2">Primary | Success | Danger | Warning | Dark | Custom
    (#9b59b6)</p>
/**
 * Colour Variants - from public/showcase/elements/loader.
 */
export function mount(root) {
        // Example 3: Colour Variants
    E.loader(root.querySelector('#color-primary'), {type: 'spinner', size: 'medium', color: 'primary'}).show();
    E.loader(root.querySelector('#color-success'), {type: 'spinner', size: 'medium', color: 'success'}).show();
    E.loader(root.querySelector('#color-danger'), {type: 'spinner', size: 'medium', color: 'danger'}).show();
    E.loader(root.querySelector('#color-warning'), {type: 'spinner', size: 'medium', color: 'warning'}).show();
    E.loader(root.querySelector('#color-dark'), {type: 'spinner', size: 'medium', color: 'dark'}).show();
    E.loader(root.querySelector('#color-custom'), {type: 'spinner', size: 'medium', color: 'var(--dm-primary)'}).show();
}
// Theme colours
Domma.elements.loader('#container', { color: 'primary' }).show();
Domma.elements.loader('#container', { color: 'success' }).show();
Domma.elements.loader('#container', { color: 'danger' }).show();
Domma.elements.loader('#container', { color: 'warning' }).show();

// Custom hex colour
Domma.elements.loader('#container', { color: 'var(--dm-primary)' }).show();

Overlay Mode

Cover container with semi-transparent backdrop:

Overlay Mode

Content Being Loaded

This content is blocked by the overlay loader.

<div id="overlay-container" class="p-4 bg-surface rounded border">
    <h4>Content Being Loaded</h4>
    <p>This content is blocked by the overlay loader.</p>
    <button type="button" id="show-overlay-btn" class="btn btn-primary">Show Overlay Loader</button>
</div>
/**
 * Overlay Mode - from public/showcase/elements/loader.
 */
export function mount(root) {
        // Example 4: Overlay Mode
    let overlayLoader = null;
    $(root).find('#show-overlay-btn').on('click', () => {
            if (!overlayLoader) {
                overlayLoader = E.loader(root.querySelector('#overlay-container'), {
                    type: 'spinner',
                    size: 'large',
                  color: 'var(--dm-white)',
                    overlay: true,
                    text: 'Loading content...'
                });
            }
            overlayLoader.show();
            setTimeout(() => overlayLoader.hide(), 2000);
        });
}
const overlayLoader = Domma.elements.loader('#container', {
    type: 'spinner',
    size: 'large',
    color: 'var(--dm-white)',
    overlay: true,           // Enable backdrop overlay
    text: 'Loading content...'
});

// Show for 2 seconds
overlayLoader.show();
setTimeout(() => overlayLoader.hide(), 2000);

With Loading Text

Add descriptive text below the loader animation:

With Loading Text
<div id="text-container" class="p-4 bg-surface rounded border" style="min-height: 150px; position: relative;">
    <!-- Loader with text will appear here -->
</div>
/**
 * With Loading Text - from public/showcase/elements/loader.
 */
export function mount(root) {
        // Example 5: With Loading Text
    const textLoader = E.loader(root.querySelector('#text-container'), {
            type: 'dots',
            size: 'medium',
            color: 'primary',
            text: 'Loading your data...'
        });
    textLoader.show();
    setTimeout(() => textLoader.setText('Almost there...'), 2000);
}
const textLoader = Domma.elements.loader('#container', {
    type: 'dots',
    size: 'medium',
    color: 'primary',
    text: 'Loading your data...'
});

textLoader.show();

// Update text dynamically
setTimeout(() => textLoader.setText('Almost there...'), 1500);

Static Helper Methods

Quick show/hide without creating instances:

Static Helper Methods

Click "Show Loader" to display

<div class="flex gap-2 mb-3">
    <button type="button" id="static-show-btn" class="btn btn-primary">Show Loader</button>
    <button type="button" id="static-hide-btn" class="btn btn-secondary">Hide Loader</button>
</div>
<div id="static-container" class="p-4 bg-surface rounded border">
    <p class="text-center text-muted">Click "Show Loader" to display</p>
</div>
/**
 * Static Helper Methods - from public/showcase/elements/loader.
 */
export function mount(root) {
        // Example 6: Static Helper Methods
    $(root).find('#static-show-btn').on('click', () => {
            E.showLoader(root.querySelector('#static-container'));
        });
    $(root).find('#static-hide-btn').on('click', () => {
            E.hideLoader(root.querySelector('#static-container'));
        });
}
// Quick show (creates loader if needed)
Domma.elements.showLoader('#container');

// Quick hide
Domma.elements.hideLoader('#container');

// Fullscreen loader
const fs = Domma.elements.fullscreenLoader('Processing...');
setTimeout(() => fs.destroy(), 2000);

Button Loading State

Show loader inside a button during submission:

Button Loading State
<button type="button" id="submit-btn" class="btn btn-primary">
    Submit Form
</button>
/**
 * Button Loading State - from public/showcase/elements/loader.
 */
export function mount(root) {
        // Example 7: Button Loading State
    let btnLoader = null;
    const submitBtn = $(root).find('#submit-btn')[0];
    $(root).find('#submit-btn').on('click', async function () {
            const originalText = this.textContent;
            this.disabled = true;
            this.textContent = '';

            btnLoader = E.loader(this, {
                type: 'spinner',
                size: 20,
              color: 'var(--dm-white)',
                centered: true
            });
            btnLoader.show();

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

            btnLoader.destroy();
            this.disabled = false;
            this.textContent = originalText;
        });
}
const submitBtn = $('#submit-btn')[0];
let btnLoader = null;

submitBtn.addEventListener('click', async () => {
    // Disable button and show loader
    submitBtn.disabled = true;
    submitBtn.textContent = '';

    btnLoader = Domma.elements.loader(this, {
        type: 'spinner',
        size: 20,
        color: 'var(--dm-white)',
        centered: true
    });
    btnLoader.show();

    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 2000));

    // Re-enable button
    btnLoader.destroy();
    submitBtn.disabled = false;
    submitBtn.textContent = originalText;
});

Real-World Example: Image Gallery Loader

Load and display images with a progress loader:

Real-World Example: Image Gallery Loader
<button type="button" id="gallery-load-btn" class="btn btn-primary mb-3">Load Gallery</button>
<div id="gallery-container" class="grid grid-cols-3 gap-4">
    <p class="text-center text-muted col-span-3">Click "Load Gallery" to fetch images</p>
</div>
/**
 * Real-World Example: Image Gallery Loader - from public/showcase/elements/loader.
 */
export function mount(root) {
        // Example 8: Gallery Loader
    const galleryLoader = E.loader(root.querySelector('#gallery-container'), {
            type: 'pulse',
            size: 'large',
            color: 'primary',
            overlay: true,
            text: 'Loading gallery...'
        });
    $(root).find('#gallery-load-btn').on('click', async () => {
            $(root).find('#gallery-container').html('');
            galleryLoader.show();

            galleryLoader.setText('Fetching images...');
            await new Promise(resolve => setTimeout(resolve, 1000));

            galleryLoader.setText('Loading 6 images...');
            await new Promise(resolve => setTimeout(resolve, 1500));

            galleryLoader.hide();

            // Display placeholder images
            const placeholderImages = [
              '/plugins/domma-showcase/public/assets/placeholders/200x150-2563eb-ffffff-image-1.svg',
              '/plugins/domma-showcase/public/assets/placeholders/200x150-16a34a-ffffff-image-2.svg',
              '/plugins/domma-showcase/public/assets/placeholders/200x150-dc2626-ffffff-image-3.svg',
              '/plugins/domma-showcase/public/assets/placeholders/200x150-f59e0b-1e293b-image-4.svg',
              '/plugins/domma-showcase/public/assets/placeholders/200x150-0ea5e9-ffffff-image-5.svg',
              '/plugins/domma-showcase/public/assets/placeholders/200x150-475569-ffffff-image-6.svg'
            ];

            $(root).find('#gallery-container').html(placeholderImages.map(url =>
                `<img src="${url}" class="w-full h-auto rounded border" alt="Gallery image">`
            ).join(''));
        });
}
const galleryContainer = $('#gallery-container')[0];
const galleryLoader = Domma.elements.loader(galleryContainer, {
    type: 'pulse',
    size: 'large',
    color: 'primary',
    overlay: true,
    text: 'Loading gallery...'
});

async function loadGallery() {
    galleryContainer.innerHTML = '';
    galleryLoader.show();

    try {
        // Fetch image URLs
        const response = await fetch('/api/images');
        const images = await response.json();

        galleryLoader.setText(`Loading ${images.length} images...`);

        // Load images
        const imgPromises = images.map(url => {
            return new Promise((resolve) => {
                const img = new Image();
                img.onload = () => resolve(img);
                img.src = url;
            });
        });

        await Promise.all(imgPromises);

        // Hide loader and display images
        galleryLoader.hide();
        images.forEach(url => {
            const imgEl = document.createElement('img');
            imgEl.src = url;
            imgEl.className = 'w-full h-auto rounded';
            galleryContainer.appendChild(imgEl);
        });
    } catch (error) {
        galleryLoader.hide();
        alert('Failed to load gallery');
    }
}

Best Practices

Accessibility

  • ARIA Attributes - Loaders automatically include role="status" and aria-live="polite" for screen readers
  • Loading Text - Always provide descriptive text via the text option for screen reader users
  • Focus Management - When showing overlay loaders, consider trapping focus or announcing the loading state
  • Colour Contrast - Ensure loader colour has sufficient contrast (4.5:1) against background, especially with overlay mode
  • Timeout Handling - Provide fallback error messages if loading takes too long or fails
  • Keyboard Users - Don't rely solely on loaders - provide skip links or cancel buttons for long operations

Performance

  • Destroy When Done - Call loader.destroy() after use to remove DOM elements and prevent memory leaks
  • Reuse Instances - Create loader once, then show/hide repeatedly instead of recreating: loader.show() / loader.hide()
  • CSS Animations - Loaders use CSS animations (not JavaScript), which are GPU-accelerated and performant
  • Lazy Initialisation - Don't create loaders on page load - initialise only when needed
  • Debounce Quick Operations - For operations under 300ms, consider skipping the loader to avoid flashing

Common Gotchas

  • Issue: Loader not visible or positioned incorrectly
    Solution: Ensure container has position: relative and min-height
    Example: ````
  • Issue: Loader stays visible after operation completes
    Solution: Always call loader.hide() in both success and error handlers
    Example: Use try/finally or .finally() to ensure hide() is called
  • Issue: Multiple loaders stacking in same container
    Solution: Destroy previous loader before creating new one, or reuse same instance
    Example: existingLoader?.destroy(); newLoader = Domma.elements.loader(...)
  • Issue: Fullscreen loader not covering modal/dropdown
    Solution: Fullscreen loader has z-index: 9999. Ensure modals don't exceed this
    Example: Adjust modal z-index or create loader inside modal
  • Issue: setText() not working
    Solution: Must initialise loader with text option first, or setText() creates new element
    Example: { text: 'Initial text...' } then loader.setText('New text')

Tips & Tricks

  • Minimum Display Time - Show loaders for at least 500ms to avoid jarring flashes on fast connections
  • White Overlay - Use color: 'white' with overlay: true for dark backdrops
  • Debounce Search - Combine with _.debounce() for live search: show loader only if typing pauses
  • Progress Updates - Use setText() to show incremental progress: "Step 1 of 3..."
  • Container Sizing - For inline loaders, set explicit container dimensions to prevent layout shift
  • Cleanup in SPA - Always destroy loaders when unmounting components/pages in single-page apps
<!-- Accessible Loader with Proper Container -->
<div id="data-container"
     role="region"
     aria-label="Data content"
     style="position: relative; min-height: 200px;">
    <!-- Content -->
</div>

<script>
const loader = Domma.elements.loader('#data-container', {
    type: 'spinner',
    size: 'medium',
    color: 'primary',
    overlay: true,
    text: 'Loading data, please wait...'  // Screen reader accessible
});

async function fetchData() {
    loader.show();

    try {
        const data = await fetch('/api/data').then(r => r.json());

        // Ensure minimum display time
        await new Promise(resolve => setTimeout(resolve, 500));

        // Process data...
    } catch (error) {
        console.error('Failed:', error);
        alert('Failed to load data. Please try again.');
    } finally {
        // Always hide loader, even on error
        loader.hide();
    }
}
</script>

API Reference

Constructor Options

Option Type Default Description
type String 'spinner' Animation type: 'spinner', 'dots', 'pulse', or 'bars'
size String | Number 'medium' Size: 'small' (24px), 'medium' (40px), 'large' (64px), or custom pixel number
color String 'primary' Colour: 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'white', 'dark', or custom hex
overlay Boolean false Show with semi-transparent backdrop covering entire container
text String '' Optional loading text displayed below animation
centered Boolean true Centre loader in container. If false, loader displays inline

Instance Methods

Method Parameters Returns Description
show() - this Display the loader. Chainable
hide() - this Hide the loader. Chainable
toggle() - this Toggle visibility (show if hidden, hide if visible). Chainable

Static Methods

Method Parameters Returns Description
Domma.elements.showLoader(selector, options) String | Element, Object Loader instance Quick show loader in container. Creates instance if needed, reuses if exists
Domma.elements.hideLoader(selector) String | Element void Quick hide loader in container. No-op if loader doesn't exist
Domma.elements.fullscreenLoader(text, options) String, Object Loader instance Show fullscreen loader covering entire viewport. Call instance.destroy() to remove

CSS Classes

Class Description
.dm-loader Main wrapper element containing loader animation and optional text
.dm-loader-spinner Circular spinning animation element
.dm-loader-dots Three bouncing dots animation container
.dm-loader-pulse Pulsing circle animation element
.dm-loader-bars Four vertical bars animation container
// Complete API Example
const loader = Domma.elements.loader('#my-container', {
    type: 'spinner',
    size: 'large',
    color: 'primary',
    overlay: true,
    text: 'Loading...',
    centered: true
});

// Instance methods
loader.show();                        // Display loader
loader.hide();                        // Hide loader
loader.toggle();                      // Toggle visibility
const visible = loader.isVisible();   // Check visibility → true/false
loader.setText('Almost done...');     // Update text
loader.destroy();                     // Clean up

// Static methods
Domma.elements.showLoader('#container');  // Quick show
Domma.elements.hideLoader('#container');  // Quick hide

const fs = Domma.elements.fullscreenLoader('Processing...');
setTimeout(() => fs.destroy(), 2000);     // Remove after 2s

Config Engine Integration

Use Domma's declarative $.setup() system to configure Loaders. Note: Loaders typically need imperative control (show/hide), but you can pre-configure them with the Config Engine.

Pre-Configure Loader

// Pre-configure loader, then control manually
let dataLoader;

$.setup({
    '#data-container': {
        component: 'loader',
        options: {
            type: 'spinner',
            size: 'large',
            color: 'var(--dm-white)',
            overlay: true,
            text: 'Loading data...'
        },
        initial: {
            // Loader hidden by default
        }
    }
});

// Get instance and control manually
dataLoader = Domma.elements.get('#data-container');

async function fetchData() {
    dataLoader.show();
    const data = await fetch('/api/data').then(r => r.json());
    dataLoader.hide();
}

Event-Triggered Loader

// Show loader on button click, hide after delay
$.setup({
    '#overlay-container': {
        component: 'loader',
        options: {
            type: 'pulse',
            size: 'large',
            color: 'var(--dm-white)',
            overlay: true,
            text: 'Loading content...'
        }
    },

    '#load-btn': {
        events: {
            click: (e, $el) => {
                const loader = Domma.elements.get('#overlay-container');
                loader.show();

                // Simulate async operation
                setTimeout(() => {
                    loader.setText('Almost done...');
                }, 1000);

                setTimeout(() => {
                    loader.hide();
                }, 2000);
            }
        }
    }
});

Live Demo

Loader configured via Config Engine:

Live Demo

Click button to show loader configured via Config Engine

<button type="button" id="config-trigger-btn" class="btn btn-primary mb-3">Show Config Loader</button>
<div id="config-demo-container" class="p-4 bg-surface rounded border" style="min-height: 150px; position: relative;">
    <p class="text-center text-muted">Click button to show loader configured via Config Engine</p>
</div>
/**
 * Live Demo - from public/showcase/elements/loader.
 */
export function mount(root) {
        // Config Engine Demo
    $.setup({
            '#config-demo-container': {
                component: 'loader',
                options: {
                    type: 'dots',
                    size: 'medium',
                    color: 'primary',
                    overlay: true,
                    text: 'Config Engine Loader'
                }
            },

            '#config-trigger-btn': {
                events: {
                    click: () => {
                        const loader = E.get(root.querySelector('#config-demo-container'));
                        loader.show();
                        setTimeout(() => loader.hide(), 2000);
                    }
                }
            }
        });
}
// Configuration
$.setup({
    '#config-demo-container': {
        component: 'loader',
        options: {
            type: 'dots',
            size: 'medium',
            color: 'primary',
            overlay: true,
            text: 'Config Engine Loader'
        }
    },

    '#config-trigger-btn': {
        events: {
            click: () => {
                const loader = Domma.elements.get('#config-demo-container');
                loader.show();
                setTimeout(() => loader.hide(), 2000);
            }
        }
    }
});

CSS Customisation

Override these CSS variables to customise Loader appearance and match your design system.

Variable Default Controls
--dm-primary var(--dm-blue-600) Spinner/animation colour
--dm-surface var(--dm-white) Overlay background (if used)
--dm-text var(--dm-slate-800) Loading text colour

Example Override

/* White spinner for dark backgrounds */
/* Override via JavaScript color option:
elements.loader('#container', { color: '#ffffff' }) */

Full CSS Customisation Cheat-Sheet →

Related Elements

Related Elements
<div class="grid grid-cols-1 gap-4"><a href="/showcase/elements/modal" class="card card-hover">
        <div class="card-body">
            <h3 class="card-title">Modal</h3>
            <p class="card-text">Dialogue windows with backdrops. Combine with loaders to show loading state
                inside modal content areas.</p>
        </div>
    </a>

    <a href="/showcase/elements/toast" class="card card-hover">
        <div class="card-body">
            <h3 class="card-title">Toast</h3>
            <p class="card-text">Temporary notifications. Use toasts to notify users when background operations
                complete after showing a loader.</p>
        </div>
    </a>

    <a href="/showcase/elements/button-group" class="card card-hover">
        <div class="card-body">
            <h3 class="card-title">Button Group</h3>
            <p class="card-text">Connected button controls. Show loaders inside buttons during form submissions
                or async actions.</p>
        </div>
    </a>

    <a href="/showcase/elements/card" class="card card-hover">
        <div class="card-body">
            <h3 class="card-title">Card</h3>
            <p class="card-text">Content containers. Use overlay loaders within cards to indicate loading state
                for card-specific content.</p>
        </div>
    </a>
</div>