Skeleton 
Overview 
The Skeleton component provided by Element Plus is used to set placeholders in positions where content needs to be loaded. Compared to traditional loading effects, skeleton screens better maintain the stability of page structure, provide a better user experience, and give users an expectation of the content that will be loaded.
Learning Objectives 
Through this documentation, you will master:
- Basic concepts and usage scenarios of Skeleton
- Basic usage and parameter configuration
- Animation effects and custom styles
- Loading state control and debounce handling
- Application examples in actual projects
- Complete API documentation and best practices
Basic Usage 
Basic Skeleton Effect 
The simplest skeleton screen usage:
<template>
  <el-skeleton />
</template>More Parameters 
You can configure the number of skeleton screen paragraphs to be closer to the real rendering effect:
<template>
  <el-skeleton :rows="5" animated />
</template>The displayed number will be 1 more than the input number, with the first line being rendered as a paragraph start with a length of 33%.
Animation Effect 
Enable loading animation through the animated attribute:
<template>
  <div>
    <h4>Without Animation</h4>
    <el-skeleton :rows="3" />
    
    <h4>With Animation</h4>
    <el-skeleton :rows="3" animated />
  </div>
</template>Custom Style 
Use the named slot template to customize the skeleton screen template:
<template>
  <el-skeleton style="width: 240px">
    <template #template>
      <el-skeleton-item variant="image" style="width: 240px; height: 240px" />
      <div style="padding: 14px">
        <el-skeleton-item variant="p" style="width: 50%" />
        <div
          style="
            display: flex;
            align-items: center;
            justify-items: space-between;
            margin-top: 16px;
            height: 16px;
          "
        >
          <el-skeleton-item variant="text" style="margin-right: 16px" />
          <el-skeleton-item variant="text" style="width: 30%" />
        </div>
      </div>
    </template>
  </el-skeleton>
</template>Loading State 
Control whether to display the real content after loading through the loading attribute:
<template>
  <div>
    <el-switch v-model="loading" active-text="Loading" />
    
    <el-skeleton style="width: 240px" :loading="loading" animated>
      <template #template>
        <el-skeleton-item variant="image" style="width: 240px; height: 240px" />
        <div style="padding: 14px">
          <el-skeleton-item variant="p" style="width: 50%" />
          <div style="display: flex; align-items: center; justify-items: space-between; margin-top: 16px; height: 16px">
            <el-skeleton-item variant="text" style="margin-right: 16px" />
            <el-skeleton-item variant="text" style="width: 30%" />
          </div>
        </div>
      </template>
      
      <template #default>
        <el-card :body-style="{ padding: '0px', marginBottom: '1px' }">
          <img
            src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
            style="width: 240px; display: block"
          />
          <div style="padding: 14px">
            <span>Delicious hamburger</span>
            <div class="bottom card-header">
              <div class="time">{{ currentDate }}</div>
              <el-button text class="button">Operating button</el-button>
            </div>
          </div>
        </el-card>
      </template>
    </el-skeleton>
  </div>
</template>
<script setup>
import { ref } from 'vue'
const loading = ref(true)
const currentDate = new Date().toLocaleDateString()
</script>
<style scoped>
.time {
  font-size: 12px;
  color: #999;
}
.bottom {
  margin-top: 13px;
  line-height: 12px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.button {
  padding: 0;
  min-height: auto;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</style>Rendering Multiple Data 
Use the count attribute to control how many fake data items to render:
<template>
  <el-skeleton :rows="5" :count="3" animated />
</template>Tip: It is not recommended to render too many fake UI elements in the browser, as this will consume more time to destroy skeleton elements, leading to performance issues. For user experience, please try to keep the
countvalue small.
Preventing Rendering Jitter 
Avoid interface flickering caused by too-fast API requests through the throttle attribute:
<template>
  <div>
    <el-switch v-model="loading" active-text="Loading" />
    
    <el-skeleton 
      style="width: 240px" 
      :loading="loading" 
      animated 
      :throttle="500"
    >
      <template #template>
        <el-skeleton-item variant="image" style="width: 240px; height: 240px" />
        <div style="padding: 14px">
          <el-skeleton-item variant="p" style="width: 50%" />
          <div style="display: flex; align-items: center; margin-top: 16px; height: 16px">
            <el-skeleton-item variant="text" style="margin-right: 16px" />
            <el-skeleton-item variant="text" style="width: 30%" />
          </div>
        </div>
      </template>
      
      <template #default>
        <el-card :body-style="{ padding: '0px' }">
          <img
            src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
            style="width: 240px; display: block"
          />
          <div style="padding: 14px">
            <span>Delicious hamburger</span>
            <div class="bottom">
              <div class="time">{{ currentDate }}</div>
              <el-button text class="button">Operating button</el-button>
            </div>
          </div>
        </el-card>
      </template>
    </el-skeleton>
  </div>
</template>
<script setup>
import { ref } from 'vue'
const loading = ref(false)
const currentDate = new Date().toLocaleDateString()
</script>Initial Rendering Loading 
When the initial value is loading: true, you can set throttle: {initVal: true, leading: xxx} to control the immediate display of the initial skeleton screen:
<template>
  <div>
    <el-switch v-model="loading" active-text="Loading" />
    
    <el-skeleton 
      style="width: 240px" 
      :loading="loading" 
      animated 
      :throttle="{ initVal: true, leading: 500 }"
    >
      <template #template>
        <el-skeleton-item variant="image" style="width: 240px; height: 240px" />
        <div style="padding: 14px">
          <el-skeleton-item variant="p" style="width: 50%" />
        </div>
      </template>
      
      <template #default>
        <el-card>Delicious hamburger</el-card>
      </template>
    </el-skeleton>
  </div>
</template>
<script setup>
import { ref } from 'vue'
const loading = ref(true)
</script>Practical Application Examples 
Article List Skeleton Screen 
<template>
  <div class="article-list">
    <div class="toolbar">
      <el-button @click="loadArticles" :loading="loading">
        {{ loading ? 'Loading...' : 'Refresh Articles' }}
      </el-button>
    </div>
    
    <div class="articles">
      <el-skeleton 
        v-for="n in 3" 
        :key="n"
        :loading="loading" 
        animated 
        :throttle="300"
        class="article-skeleton"
      >
        <template #template>
          <div class="article-skeleton-item">
            <el-skeleton-item variant="image" style="width: 120px; height: 80px; margin-right: 16px" />
            <div class="content">
              <el-skeleton-item variant="h3" style="width: 60%; margin-bottom: 8px" />
              <el-skeleton-item variant="text" style="width: 100%; margin-bottom: 4px" />
              <el-skeleton-item variant="text" style="width: 80%; margin-bottom: 8px" />
              <div style="display: flex; align-items: center">
                <el-skeleton-item variant="text" style="width: 80px; margin-right: 16px" />
                <el-skeleton-item variant="text" style="width: 60px" />
              </div>
            </div>
          </div>
        </template>
        
        <template #default>
          <div class="article-item" v-for="article in articles" :key="article.id">
            <img :src="article.cover" alt="" class="cover" />
            <div class="content">
              <h3>{{ article.title }}</h3>
              <p>{{ article.summary }}</p>
              <div class="meta">
                <span>{{ article.author }}</span>
                <span>{{ article.publishTime }}</span>
              </div>
            </div>
          </div>
        </template>
      </el-skeleton>
    </div>
  </div>
</template>
<script setup>
import { ref } from 'vue'
const loading = ref(false)
const articles = ref([])
const loadArticles = async () => {
  loading.value = true
  
  // Simulate API request
  await new Promise(resolve => setTimeout(resolve, 2000))
  
  articles.value = [
    {
      id: 1,
      title: 'Vue 3 New Features Explained',
      summary: 'Deep dive into the new features and improvements in Vue 3...',
      author: 'John Doe',
      publishTime: '2024-01-15',
      cover: 'https://via.placeholder.com/120x80'
    },
    {
      id: 2,
      title: 'Element Plus Component Library Practice',
      summary: 'How to efficiently use Element Plus in your projects...',
      author: 'Jane Smith',
      publishTime: '2024-01-14',
      cover: 'https://via.placeholder.com/120x80'
    },
    {
      id: 3,
      title: 'Frontend Performance Optimization Guide',
      summary: 'Best practices for improving web application performance...',
      author: 'Mike Johnson',
      publishTime: '2024-01-13',
      cover: 'https://via.placeholder.com/120x80'
    }
  ]
  
  loading.value = false
}
// Initial loading
loadArticles()
</script>
<style scoped>
.article-list {
  padding: 20px;
}
.toolbar {
  margin-bottom: 20px;
}
.article-skeleton {
  margin-bottom: 20px;
}
.article-skeleton-item {
  display: flex;
  align-items: flex-start;
  padding: 16px;
  border: 1px solid #ebeef5;
  border-radius: 4px;
}
.article-skeleton-item .content {
  flex: 1;
}
.article-item {
  display: flex;
  align-items: flex-start;
  padding: 16px;
  border: 1px solid #ebeef5;
  border-radius: 4px;
  margin-bottom: 16px;
}
.article-item .cover {
  width: 120px;
  height: 80px;
  object-fit: cover;
  margin-right: 16px;
  border-radius: 4px;
}
.article-item .content {
  flex: 1;
}
.article-item h3 {
  margin: 0 0 8px 0;
  font-size: 16px;
  color: #303133;
}
.article-item p {
  margin: 0 0 8px 0;
  color: #606266;
  line-height: 1.5;
}
.article-item .meta {
  display: flex;
  gap: 16px;
  font-size: 12px;
  color: #909399;
}
</style>User Information Card Skeleton Screen 
<template>
  <div class="user-profile">
    <el-button @click="loadUserInfo" :loading="loading">
      {{ loading ? 'Loading...' : 'Load User Information' }}
    </el-button>
    
    <el-skeleton 
      :loading="loading" 
      animated 
      :throttle="400"
      style="margin-top: 20px"
    >
      <template #template>
        <div class="user-skeleton">
          <el-skeleton-item variant="image" style="width: 80px; height: 80px; border-radius: 50%" />
          <div class="user-info">
            <el-skeleton-item variant="h3" style="width: 120px; margin-bottom: 8px" />
            <el-skeleton-item variant="text" style="width: 200px; margin-bottom: 4px" />
            <el-skeleton-item variant="text" style="width: 150px; margin-bottom: 8px" />
            <div style="display: flex; gap: 8px">
              <el-skeleton-item variant="button" style="width: 60px; height: 32px" />
              <el-skeleton-item variant="button" style="width: 60px; height: 32px" />
            </div>
          </div>
        </div>
      </template>
      
      <template #default>
        <div class="user-card" v-if="userInfo">
          <img :src="userInfo.avatar" alt="" class="avatar" />
          <div class="user-info">
            <h3>{{ userInfo.name }}</h3>
            <p>{{ userInfo.email }}</p>
            <p>{{ userInfo.department }}</p>
            <div class="actions">
              <el-button size="small" type="primary">Follow</el-button>
              <el-button size="small">Message</el-button>
            </div>
          </div>
        </div>
      </template>
    </el-skeleton>
  </div>
</template>
<script setup>
import { ref } from 'vue'
const loading = ref(false)
const userInfo = ref(null)
const loadUserInfo = async () => {
  loading.value = true
  userInfo.value = null
  
  // Simulate API request
  await new Promise(resolve => setTimeout(resolve, 1500))
  
  userInfo.value = {
    name: 'John Smith',
    email: 'john.smith@example.com',
    department: 'Frontend Development',
    avatar: 'https://via.placeholder.com/80x80'
  }
  
  loading.value = false
}
</script>
<style scoped>
.user-profile {
  padding: 20px;
}
.user-skeleton {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: 20px;
  border: 1px solid #ebeef5;
  border-radius: 8px;
}
.user-skeleton .user-info {
  flex: 1;
}
.user-card {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: 20px;
  border: 1px solid #ebeef5;
  border-radius: 8px;
}
.user-card .avatar {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  object-fit: cover;
}
.user-card .user-info {
  flex: 1;
}
.user-card h3 {
  margin: 0 0 8px 0;
  color: #303133;
}
.user-card p {
  margin: 0 0 4px 0;
  color: #606266;
}
.user-card .actions {
  margin-top: 12px;
  display: flex;
  gap: 8px;
}
</style>API Documentation 
Skeleton Attributes 
| Attribute | Description | Type | Default | 
|---|---|---|---|
| animated | Whether to use animation | boolean | false | 
| count | How many templates to render, it's advised to use as small number as possible | number | 1 | 
| loading | Whether to display the DOM structure after loading is complete | boolean | false | 
| rows | Number of skeleton screen paragraphs | number | 3 | 
| throttle | Rendering delay (in milliseconds). A number represents delay display, or it can be set as delay hide, e.g. { leading: 500, trailing: 500 }. When you need to control the initial loading value, you can set{ initVal: true } | number / object | 0 | 
Skeleton Slots 
| Slot Name | Description | Scope | 
|---|---|---|
| default | The real DOM to be rendered | object | 
| template | Content for rendering the skeleton template | object | 
SkeletonItem Attributes 
| Attribute | Description | Type | Default | 
|---|---|---|---|
| variant | The current rendering skeleton type | 'p' / 'text' / 'h1' / 'h3' / 'caption' / 'button' / 'image' / 'circle' / 'rect' | 'text' | 
Best Practices 
Design Principles 
- Structural Similarity: Skeleton screens should be as close as possible to the structure and layout of the real content
- Reasonable Loading Time: Use throttleto avoid flickering caused by too-fast loading
- Moderate Animation: Animation can enhance the experience, but should not be too flashy to affect performance
Performance Optimization 
- Control Quantity: Avoid rendering too many skeleton screen elements at the same time
- Reasonable use of count: The countattribute should be set according to actual needs, not too large
- Timely Destruction: Switch to real content promptly after loading is complete
User Experience 
- Expectation Management: Skeleton screens should give users a reasonable expectation of the content to be loaded
- Loading Feedback: Combine with appropriate loading prompt text or progress indicators
- Error Handling: There should be corresponding error states when loading fails
Responsive Design 
- Mobile Adaptation: Adjust the size and layout of skeleton screens on mobile devices
- Flexible Layout: Use flexible layouts to ensure performance on different screen sizes
- Content Priority: Prioritize displaying skeletons for important content on small screens
Common Issues 
Skeleton Screen Not Displaying 
Issue: Set loading="true" but the skeleton screen does not display
Solutions:
- Check if the loadingattribute is correctly bound
- Confirm if there is a custom template but the template is empty
- Check if CSS styles affect the display
<!-- Incorrect example -->
<el-skeleton loading="true" />
<!-- Correct example -->
<el-skeleton :loading="true" />Custom Template Not Working 
Issue: Used the template slot but still showing the default style
Solutions: Ensure the slot name is correct and contains valid skeleton elements
<!-- Incorrect example -->
<el-skeleton>
  <template #default>
    <el-skeleton-item variant="text" />
  </template>
</el-skeleton>
<!-- Correct example -->
<el-skeleton>
  <template #template>
    <el-skeleton-item variant="text" />
  </template>
</el-skeleton>Animation Performance Issues 
Issue: Page stutters after enabling animation
Solutions:
- Reduce the number of skeleton screens displayed simultaneously
- Optimize the complexity of custom templates
- Consider disabling animation on low-performance devices
<template>
  <el-skeleton 
    :animated="!isLowPerformance" 
    :count="isLowPerformance ? 1 : 3"
  />
</template>
<script setup>
import { ref, onMounted } from 'vue'
const isLowPerformance = ref(false)
onMounted(() => {
  // Detect device performance
  isLowPerformance.value = navigator.hardwareConcurrency < 4
})
</script>Debounce Setting Not Working 
Issue: Set throttle but still have flickering
Solutions: Correctly configure throttle parameters
<!-- Basic debounce -->
<el-skeleton :throttle="500" />
<!-- Advanced debounce configuration -->
<el-skeleton :throttle="{ leading: 500, trailing: 300 }" />
<!-- Initial loading debounce -->
<el-skeleton :throttle="{ initVal: true, leading: 500 }" />Summary 
The Skeleton component is an important component for enhancing user experience, providing better visual feedback when content is loading. Through this documentation, you should be able to:
- Understand the design philosophy and usage scenarios of skeleton screens
- Master basic usage and advanced configuration
- Implement custom skeleton screen templates
- Reasonably use debounce and performance optimization
- Apply skeleton screens in actual projects to enhance user experience
In actual development, it is recommended to design corresponding skeleton screens according to specific content structures, pay attention to the balance between performance and user experience, and ensure that skeleton screens can truly enhance rather than affect user experience.