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:
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:
<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
<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:
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:
<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:
<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:
<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:
<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:
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:
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:
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:
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:
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:
<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:
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 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:
Click "Load Gallery" to fetch images
<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"andaria-live="polite"for screen readers - Loading Text - Always provide descriptive text via the
textoption 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 hasposition: relativeandmin-height
Example: ```` - Issue: Loader stays visible after operation completes
Solution: Always callloader.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 withtextoption first, or setText() creates new element
Example:{ text: 'Initial text...' }thenloader.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'withoverlay: truefor 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:
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
Modal
Dialogue windows with backdrops. Combine with loaders to show loading state inside modal content areas.
Toast
Temporary notifications. Use toasts to notify users when background operations complete after showing a loader.
Button Group
Connected button controls. Show loaders inside buttons during form submissions or async actions.
Card
Content containers. Use overlay loaders within cards to indicate loading state for card-specific content.
<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>