Loading States
Display loading indicators and skeleton screens while content loads
Loading attributes help you provide visual feedback while data is being fetched. This improves perceived performance and user experience.
dbind_loading
Purpose: Show or hide elements based on the loading state of a collection or data fetch.
Syntax:
<!-- Show element while loading -->
<element dbind_loading="show"></element>
<!-- Hide element while loading -->
<element dbind_loading="hide"></element>
<!-- For specific collection -->
<element dbind_loading="show:collectionName"></element>How It Works
- When data fetch begins,
isLoadingbecomestrue - Elements with
dbind_loading="show"become visible - Elements with
dbind_loading="hide"become hidden - When fetch completes, states reverse
Basic Usage
<div dbind_collection="products">
<!-- Loading indicator -->
<div class="loading-spinner" dbind_loading="show">
<span class="spinner"></span>
Loading products...
</div>
<!-- Content (hidden while loading) -->
<div class="product-grid" dbind_loading="hide">
<div dbind_repeat="data" class="product-card">
<h3 dbind_data="name"></h3>
</div>
</div>
</div>Use Cases
1. Simple Loading Spinner
<div dbind_collection="data">
<div class="loading-container" dbind_loading="show">
<div class="spinner"></div>
<p>Loading...</p>
</div>
<div class="content" dbind_loading="hide">
<div dbind_repeat="data">
<p dbind_data="content"></p>
</div>
</div>
</div>CSS:
.spinner {
width: 40px;
height: 40px;
border: 3px solid #e5e7eb;
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}2. Loading Overlay
<div class="data-container">
<!-- Loading overlay -->
<div class="loading-overlay" dbind_loading="show">
<div class="loading-content">
<div class="spinner"></div>
<span>Updating...</span>
</div>
</div>
<!-- Content always visible but dimmed during loading -->
<div class="content" dbind_repeat="items">
<div class="item" dbind_data="name"></div>
</div>
</div>CSS:
.data-container {
position: relative;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}3. Button Loading State
<form dbind_submit="/api/save">
<input type="text" dbind_model="name">
<!-- Normal button -->
<button type="submit" dbind_loading="hide">
Save Changes
</button>
<!-- Loading button -->
<button type="button" disabled dbind_loading="show">
<span class="spinner-small"></span>
Saving...
</button>
</form>4. Search with Loading Indicator
<div class="search-container" dbind_collection="searchResults">
<div class="search-input-wrapper">
<input type="search" dbind_search="searchResults" placeholder="Search...">
<span class="search-loading" dbind_loading="show">
<div class="spinner-small"></div>
</span>
</div>
<div class="results" dbind_loading="hide">
<div dbind_repeat="data" class="result-item">
<h4 dbind_data="title"></h4>
</div>
</div>
</div>dbind_skeleton
Purpose: Display skeleton placeholder content that mimics the shape of the actual content while loading.
Syntax:
<element dbind_skeleton></element>
<element dbind_skeleton="type"></element>Skeleton Types
| Type | Description | Use For |
|---|---|---|
text | Single line of text | Titles, labels |
paragraph | Multiple lines | Descriptions, content |
avatar | Circular shape | Profile images |
image | Rectangle | Product images, thumbnails |
card | Card-shaped block | Product cards, posts |
table-row | Table row | Data tables |
Basic Usage
<div dbind_collection="products">
<!-- Skeleton grid (shown while loading) -->
<div class="skeleton-grid" dbind_loading="show">
<div class="skeleton-card" dbind_skeleton="card"></div>
<div class="skeleton-card" dbind_skeleton="card"></div>
<div class="skeleton-card" dbind_skeleton="card"></div>
<div class="skeleton-card" dbind_skeleton="card"></div>
</div>
<!-- Actual content -->
<div class="product-grid" dbind_loading="hide" dbind_repeat="data">
<div class="product-card">
<img dbind_src="image" dbind_alt="name">
<h3 dbind_data="name"></h3>
<p dbind_data="price" dbind_format="currency:USD"></p>
</div>
</div>
</div>Custom Skeleton Structure
Match the skeleton to your actual content structure:
<!-- Skeleton card matching real card layout -->
<div class="skeleton-card" dbind_skeleton>
<div class="skeleton-image" dbind_skeleton="image"></div>
<div class="skeleton-content">
<div class="skeleton-title" dbind_skeleton="text"></div>
<div class="skeleton-description" dbind_skeleton="paragraph"></div>
<div class="skeleton-price" dbind_skeleton="text"></div>
</div>
</div>Use Cases
1. Product Card Skeleton
<div class="product-catalog" dbind_collection="products">
<!-- Skeleton loading state -->
<div class="product-grid" dbind_loading="show">
<div class="product-skeleton" dbind_skeleton>
<div class="skeleton-image"></div>
<div class="skeleton-body">
<div class="skeleton-line skeleton-title"></div>
<div class="skeleton-line skeleton-short"></div>
<div class="skeleton-line skeleton-price"></div>
</div>
</div>
<div class="product-skeleton" dbind_skeleton>
<div class="skeleton-image"></div>
<div class="skeleton-body">
<div class="skeleton-line skeleton-title"></div>
<div class="skeleton-line skeleton-short"></div>
<div class="skeleton-line skeleton-price"></div>
</div>
</div>
<div class="product-skeleton" dbind_skeleton>
<div class="skeleton-image"></div>
<div class="skeleton-body">
<div class="skeleton-line skeleton-title"></div>
<div class="skeleton-line skeleton-short"></div>
<div class="skeleton-line skeleton-price"></div>
</div>
</div>
</div>
<!-- Actual products -->
<div class="product-grid" dbind_loading="hide" dbind_repeat="data">
<div class="product-card">
<img dbind_src="image" dbind_alt="name">
<h3 dbind_data="name"></h3>
<p dbind_data="description" dbind_format="truncate:80"></p>
<span dbind_data="price" dbind_format="currency:USD"></span>
</div>
</div>
</div>CSS:
.skeleton-image {
width: 100%;
height: 200px;
background: #e5e7eb;
border-radius: 8px;
}
.skeleton-line {
height: 16px;
background: #e5e7eb;
border-radius: 4px;
margin-bottom: 8px;
}
.skeleton-title {
width: 80%;
}
.skeleton-short {
width: 60%;
}
.skeleton-price {
width: 30%;
}
/* Shimmer animation */
.skeleton-image,
.skeleton-line {
background: linear-gradient(
90deg,
#e5e7eb 25%,
#f3f4f6 50%,
#e5e7eb 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}2. Blog Post Skeleton
<div class="blog-feed" dbind_collection="posts">
<!-- Skeleton posts -->
<div dbind_loading="show" class="skeleton-posts">
<article class="post-skeleton" dbind_skeleton>
<div class="skeleton-featured-image"></div>
<div class="skeleton-meta">
<div class="skeleton-avatar" dbind_skeleton="avatar"></div>
<div class="skeleton-meta-text">
<div class="skeleton-line" style="width: 120px;"></div>
<div class="skeleton-line" style="width: 80px;"></div>
</div>
</div>
<div class="skeleton-line skeleton-title" style="width: 90%;"></div>
<div class="skeleton-line" style="width: 100%;"></div>
<div class="skeleton-line" style="width: 95%;"></div>
<div class="skeleton-line" style="width: 60%;"></div>
</article>
<article class="post-skeleton" dbind_skeleton>
<!-- ... same structure ... -->
</article>
</div>
<!-- Actual posts -->
<div dbind_loading="hide" dbind_repeat="data">
<article class="blog-post">
<img dbind_src="featuredImage" dbind_alt="title">
<div class="post-meta">
<img dbind_src="author.avatar" dbind_alt="author.name" class="avatar">
<div>
<span dbind_data="author.name"></span>
<time dbind_data="publishedAt" dbind_format="date:medium"></time>
</div>
</div>
<h2 dbind_data="title"></h2>
<p dbind_data="excerpt"></p>
<a dbind_href="url">Read More</a>
</article>
</div>
</div>3. Table Skeleton
<div class="data-table-container" dbind_collection="users">
<!-- Skeleton table -->
<table class="skeleton-table" dbind_loading="show">
<thead>
<tr>
<th><div class="skeleton-line" style="width: 40px;"></div></th>
<th><div class="skeleton-line" style="width: 100px;"></div></th>
<th><div class="skeleton-line" style="width: 150px;"></div></th>
<th><div class="skeleton-line" style="width: 80px;"></div></th>
</tr>
</thead>
<tbody>
<tr dbind_skeleton="table-row">
<td><div class="skeleton-avatar-small"></div></td>
<td><div class="skeleton-line"></div></td>
<td><div class="skeleton-line"></div></td>
<td><div class="skeleton-line" style="width: 60%;"></div></td>
</tr>
<tr dbind_skeleton="table-row">
<td><div class="skeleton-avatar-small"></div></td>
<td><div class="skeleton-line"></div></td>
<td><div class="skeleton-line"></div></td>
<td><div class="skeleton-line" style="width: 60%;"></div></td>
</tr>
<tr dbind_skeleton="table-row">
<td><div class="skeleton-avatar-small"></div></td>
<td><div class="skeleton-line"></div></td>
<td><div class="skeleton-line"></div></td>
<td><div class="skeleton-line" style="width: 60%;"></div></td>
</tr>
</tbody>
</table>
<!-- Actual table -->
<table class="data-table" dbind_loading="hide">
<thead>
<tr>
<th>Avatar</th>
<th>Name</th>
<th>Email</th>
<th>Role</th>
</tr>
</thead>
<tbody dbind_repeat="data">
<tr>
<td><img dbind_src="avatar" dbind_alt="name" class="avatar-small"></td>
<td dbind_data="name"></td>
<td dbind_data="email"></td>
<td dbind_data="role"></td>
</tr>
</tbody>
</table>
</div>4. User Profile Skeleton
<div class="user-profile" dbind_collection="profile">
<!-- Skeleton profile -->
<div class="profile-skeleton" dbind_loading="show">
<div class="skeleton-cover-image"></div>
<div class="skeleton-profile-header">
<div class="skeleton-avatar-large" dbind_skeleton="avatar"></div>
<div class="skeleton-profile-info">
<div class="skeleton-line skeleton-name" style="width: 180px;"></div>
<div class="skeleton-line" style="width: 120px;"></div>
</div>
</div>
<div class="skeleton-bio">
<div class="skeleton-line" style="width: 100%;"></div>
<div class="skeleton-line" style="width: 90%;"></div>
<div class="skeleton-line" style="width: 75%;"></div>
</div>
<div class="skeleton-stats">
<div class="skeleton-stat" dbind_skeleton></div>
<div class="skeleton-stat" dbind_skeleton></div>
<div class="skeleton-stat" dbind_skeleton></div>
</div>
</div>
<!-- Actual profile -->
<div class="profile-content" dbind_loading="hide">
<div class="cover-image" dbind_style="coverImageStyle"></div>
<div class="profile-header">
<img dbind_src="data.avatar" dbind_alt="data.name" class="avatar-large">
<div class="profile-info">
<h1 dbind_data="data.name"></h1>
<span dbind_data="data.username"></span>
</div>
</div>
<p class="bio" dbind_data="data.bio"></p>
<div class="stats">
<div class="stat">
<span class="stat-value" dbind_data="data.followers" dbind_format="compact"></span>
<span class="stat-label">Followers</span>
</div>
<div class="stat">
<span class="stat-value" dbind_data="data.following" dbind_format="compact"></span>
<span class="stat-label">Following</span>
</div>
<div class="stat">
<span class="stat-value" dbind_data="data.posts" dbind_format="compact"></span>
<span class="stat-label">Posts</span>
</div>
</div>
</div>
</div>Loading State Patterns
Pattern 1: Replace Content
Content is completely replaced with skeleton while loading.
<div dbind_collection="items">
<div dbind_loading="show" class="skeleton-container">
<!-- Skeletons -->
</div>
<div dbind_loading="hide" class="content-container">
<!-- Actual content -->
</div>
</div>Pattern 2: Overlay Content
Content remains visible but with a loading overlay.
<div class="container" dbind_collection="items">
<div class="loading-overlay" dbind_loading="show">
<div class="spinner"></div>
</div>
<div class="content" dbind_repeat="data">
<!-- Content -->
</div>
</div>Pattern 3: Inline Loading Indicator
Small loading indicator within the content area.
<div dbind_collection="items">
<div class="header">
<h2>Items</h2>
<span class="loading-indicator" dbind_loading="show">
<div class="spinner-small"></div> Updating...
</span>
</div>
<div class="content" dbind_repeat="data">
<!-- Content -->
</div>
</div>Pattern 4: Progressive Loading
Show content as it becomes available.
<div dbind_collection="items">
<!-- Always visible header -->
<header>
<h1>Products</h1>
</header>
<!-- Loading indicator -->
<div class="loading-more" dbind_loading="show">
Loading more items...
</div>
<!-- Content that grows -->
<div class="content" dbind_repeat="data">
<div class="item" dbind_data="name"></div>
</div>
<!-- Load more button -->
<button dbind_loadmore="items" dbind_loading="hide">
Load More
</button>
</div>Complete Example: Dashboard with Loading States
<div class="dashboard">
<!-- Stats Section -->
<section class="stats-section" dbind_collection="stats">
<!-- Skeleton stats -->
<div class="stats-grid" dbind_loading="show">
<div class="stat-skeleton" dbind_skeleton="card">
<div class="skeleton-line" style="width: 40%;"></div>
<div class="skeleton-line skeleton-large" style="width: 60%;"></div>
</div>
<div class="stat-skeleton" dbind_skeleton="card">
<div class="skeleton-line" style="width: 40%;"></div>
<div class="skeleton-line skeleton-large" style="width: 60%;"></div>
</div>
<div class="stat-skeleton" dbind_skeleton="card">
<div class="skeleton-line" style="width: 40%;"></div>
<div class="skeleton-line skeleton-large" style="width: 60%;"></div>
</div>
<div class="stat-skeleton" dbind_skeleton="card">
<div class="skeleton-line" style="width: 40%;"></div>
<div class="skeleton-line skeleton-large" style="width: 60%;"></div>
</div>
</div>
<!-- Actual stats -->
<div class="stats-grid" dbind_loading="hide">
<div class="stat-card">
<span class="stat-label">Total Users</span>
<span class="stat-value" dbind_data="data.totalUsers" dbind_format="compact"></span>
</div>
<div class="stat-card">
<span class="stat-label">Revenue</span>
<span class="stat-value" dbind_data="data.revenue" dbind_format="currency:USD"></span>
</div>
<div class="stat-card">
<span class="stat-label">Orders</span>
<span class="stat-value" dbind_data="data.orders" dbind_format="number"></span>
</div>
<div class="stat-card">
<span class="stat-label">Conversion</span>
<span class="stat-value" dbind_data="data.conversion" dbind_format="percent:1"></span>
</div>
</div>
</section>
<!-- Recent Activity Section -->
<section class="activity-section" dbind_collection="activity">
<header class="section-header">
<h2>Recent Activity</h2>
<span class="refresh-indicator" dbind_loading="show">
<div class="spinner-small"></div>
</span>
</header>
<!-- Skeleton activity list -->
<div class="activity-list" dbind_loading="show">
<div class="activity-skeleton" dbind_skeleton>
<div class="skeleton-avatar-small"></div>
<div class="skeleton-activity-content">
<div class="skeleton-line" style="width: 80%;"></div>
<div class="skeleton-line" style="width: 40%;"></div>
</div>
</div>
<div class="activity-skeleton" dbind_skeleton>
<div class="skeleton-avatar-small"></div>
<div class="skeleton-activity-content">
<div class="skeleton-line" style="width: 70%;"></div>
<div class="skeleton-line" style="width: 35%;"></div>
</div>
</div>
<div class="activity-skeleton" dbind_skeleton>
<div class="skeleton-avatar-small"></div>
<div class="skeleton-activity-content">
<div class="skeleton-line" style="width: 85%;"></div>
<div class="skeleton-line" style="width: 45%;"></div>
</div>
</div>
</div>
<!-- Actual activity list -->
<div class="activity-list" dbind_loading="hide" dbind_repeat="data">
<div class="activity-item">
<img dbind_src="user.avatar" dbind_alt="user.name" class="avatar-small">
<div class="activity-content">
<p>
<strong dbind_data="user.name"></strong>
<span dbind_data="action"></span>
</p>
<time dbind_data="timestamp" dbind_format="date:relative"></time>
</div>
</div>
</div>
</section>
<!-- Data Table Section -->
<section class="table-section" dbind_collection="orders">
<header class="section-header">
<h2>Recent Orders</h2>
<input type="search" dbind_search="orders" placeholder="Search orders...">
</header>
<!-- Loading overlay for table -->
<div class="table-container">
<div class="table-loading-overlay" dbind_loading="show">
<div class="spinner"></div>
<span>Loading orders...</span>
</div>
<table class="data-table">
<thead>
<tr>
<th>Order ID</th>
<th>Customer</th>
<th>Amount</th>
<th>Status</th>
<th>Date</th>
</tr>
</thead>
<tbody dbind_repeat="data">
<tr>
<td dbind_data="orderId"></td>
<td dbind_data="customer.name"></td>
<td dbind_data="amount" dbind_format="currency:USD"></td>
<td>
<span class="status-badge" dbind_class="statusClass" dbind_data="status" dbind_format="capitalize"></span>
</td>
<td dbind_data="createdAt" dbind_format="date:medium"></td>
</tr>
</tbody>
</table>
<!-- Empty state -->
<div class="empty-table" dbind_empty="data" dbind_loading="hide">
<p>No orders found</p>
</div>
</div>
<!-- Pagination -->
<nav class="pagination" dbind_loading="hide">
Page <span dbind_page-info="page"></span> of <span dbind_page-info="totalPages"></span>
</nav>
</section>
</div>CSS:
.dashboard {
padding: 24px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 32px;
}
.stat-skeleton,
.stat-card {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.skeleton-large {
height: 32px;
margin-top: 12px;
}
.activity-skeleton {
display: flex;
gap: 12px;
padding: 16px 0;
border-bottom: 1px solid #e5e7eb;
}
.skeleton-avatar-small {
width: 40px;
height: 40px;
border-radius: 50%;
background: #e5e7eb;
}
.skeleton-activity-content {
flex: 1;
}
.table-loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
z-index: 10;
}
.table-container {
position: relative;
}
/* Shimmer animation */
[dbind_skeleton],
.skeleton-line,
.skeleton-avatar-small,
.skeleton-image {
background: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%);
background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}Performance Tips
1. Match Skeleton to Content
Design skeletons that match the actual content layout for a smooth transition.
2. Use Appropriate Counts
Show the same number of skeleton items as your typical page size.
3. Animate Subtly
Use subtle shimmer animations rather than aggressive pulsing.
4. Consider Reduced Motion
Respect user preferences for reduced motion:
@media (prefers-reduced-motion: reduce) {
[dbind_skeleton],
.skeleton-line {
animation: none;
background: #e5e7eb;
}
}Next Steps
- Animations - Transition effects
- Collections - Server-side data loading
- Accessibility - Accessible loading states