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

  1. When data fetch begins, isLoading becomes true
  2. Elements with dbind_loading="show" become visible
  3. Elements with dbind_loading="hide" become hidden
  4. 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

TypeDescriptionUse For
textSingle line of textTitles, labels
paragraphMultiple linesDescriptions, content
avatarCircular shapeProfile images
imageRectangleProduct images, thumbnails
cardCard-shaped blockProduct cards, posts
table-rowTable rowData 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