CSS Opacity & Visibility
Opacity and Visibility are two CSS properties that are often misunderstood. Both can hide an element, but in very different ways. Opacity controls an element's transparency, while Visibility controls whether an element is visible while still affecting the layout.
A correct understanding of this difference is very important, because using the wrong property can cause hard-to-find layout bugs.
Main Differences
| Property | Visual | Takes Layout Space? | Can Be Animated? | Can Be Clicked? |
|---|---|---|---|---|
opacity: 0 |
Transparent | Yes | Yes (transition) | Yes (still possible) |
visibility: hidden |
Hidden | Yes | Yes (transition) | No |
display: none |
Gone | No | No | No |
Code Example
Differences between Opacity, Visibility, and display:none
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Opacity & Visibility</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, sans-serif;
padding: 30px;
background: #f0f2f5;
color: #334155;
}
h2 {
margin-bottom: 15px;
color: #1a1a2e;
}
.section {
margin-bottom: 40px;
}
.label {
font-size: 12px;
color: #64748b;
font-family: monospace;
background: #e2e8f0;
display: inline-block;
padding: 3px 8px;
border-radius: 4px;
margin-bottom: 15px;
}
.demo-row {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.demo-box {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
text-align: center;
min-width: 180px;
}
.demo-card {
width: 180px;
height: 150px;
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
margin: 0 auto 10px;
transition: all 0.5s;
}
/* ===== OPACITY LEVELS ===== */
.opacity-1 { opacity: 1; }
.opacity-075 { opacity: 0.75; }
.opacity-05 { opacity: 0.5; }
.opacity-025 { opacity: 0.25; }
.opacity-0 { opacity: 0; }
/* ===== VISIBILITY STATES ===== */
.vis-visible { visibility: visible; }
.vis-hidden { visibility: hidden; }
/* ===== DISPLAY NONE ===== */
.display-none { display: none; }
/* ===== TRANSITION DEMO ===== */
.transition-group {
display: flex;
gap: 20px;
margin-top: 15px;
}
.trans-box {
position: relative;
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
flex: 1;
min-height: 200px;
}
.trans-box h4 {
font-size: 14px;
margin-bottom: 15px;
color: #475569;
}
.trans-target {
width: 100%;
height: 100px;
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
transition: all 0.5s;
}
.trans-group:hover .trans-opacity {
opacity: 0;
}
.trans-group:hover .trans-visibility {
visibility: hidden;
}
.trans-group:hover .trans-display {
display: none;
}
/* ===== HOVER EFFECTS ===== */
.hover-group {
display: flex;
gap: 20px;
flex-wrap: wrap;
margin-top: 15px;
}
.hover-card {
position: relative;
width: 200px;
border-radius: 12px;
overflow: hidden;
background: white;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
cursor: pointer;
}
.hover-card-img {
width: 100%;
height: 150px;
background: linear-gradient(135deg, #43e97b, #38f9d7);
transition: opacity 0.3s;
}
.hover-card:nth-child(2) .hover-card-img {
background: linear-gradient(135deg, #fa709a, #fee140);
}
.hover-card:nth-child(3) .hover-card-img {
background: linear-gradient(135deg, #a18cd1, #fbc2eb);
}
.hover-card-overlay {
position: absolute;
inset: 0;
background: rgba(0,0,0,0.6);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
opacity: 0;
transition: opacity 0.3s;
}
.hover-card:hover .hover-card-overlay {
opacity: 1;
}
.hover-card-body {
padding: 15px;
}
.hover-card-body h4 {
font-size: 15px;
margin-bottom: 5px;
}
.hover-card-body p {
font-size: 13px;
color: #64748b;
}
/* ===== DISABLED STATE ===== */
.disabled-group {
display: flex;
gap: 15px;
margin-top: 15px;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-weight: 600;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5a67d8;
}
.btn-disabled {
background: #e2e8f0;
color: #94a3b8;
cursor: not-allowed;
opacity: 0.6;
}
.btn-visibility-disabled {
background: #667eea;
color: white;
cursor: not-allowed;
visibility: hidden;
}
/* ===== LOADING OVERLAY ===== */
.loading-demo {
position: relative;
width: 100%;
max-width: 400px;
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
}
.loading-content {
padding: 30px;
}
.loading-content h4 {
margin-bottom: 10px;
}
.loading-content p {
color: #64748b;
font-size: 14px;
line-height: 1.6;
}
.loading-overlay {
position: absolute;
inset: 0;
background: rgba(255,255,255,0.85);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 15px;
opacity: 0;
visibility: hidden;
transition: all 0.3s;
backdrop-filter: blur(2px);
}
.loading-overlay.active {
opacity: 1;
visibility: visible;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #e2e8f0;
border-top: 4px solid #667eea;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-text {
color: #475569;
font-weight: 600;
}
.btn-toggle {
margin-top: 15px;
padding: 10px 20px;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
}
/* ===== FADE IN/OUT ===== */
.fade-demo {
display: flex;
gap: 20px;
margin-top: 15px;
}
.fade-item {
flex: 1;
text-align: center;
}
.fade-box {
width: 150px;
height: 150px;
margin: 0 auto 10px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
transition: all 0.5s;
}
.fade-in .fade-box {
background: linear-gradient(135deg, #667eea, #764ba2);
opacity: 0;
}
.fade-in:hover .fade-box {
opacity: 1;
}
.fade-slide .fade-box {
background: linear-gradient(135deg, #43e97b, #38f9d7);
opacity: 0;
transform: translateY(20px);
}
.fade-slide:hover .fade-box {
opacity: 1;
transform: translateY(0);
}
.fade-scale .fade-box {
background: linear-gradient(135deg, #fa709a, #fee140);
opacity: 0;
transform: scale(0.8);
}
.fade-scale:hover .fade-box {
opacity: 1;
transform: scale(1);
}
/* ===== IMAGE HOVER EFFECTS ===== */
.img-hover-group {
display: flex;
gap: 20px;
margin-top: 15px;
}
.img-hover-item {
position: relative;
border-radius: 12px;
overflow: hidden;
}
.img-hover-item img {
width: 200px;
height: 150px;
object-fit: cover;
display: block;
transition: filter 0.3s;
}
.img-hover-item:hover img {
filter: brightness(0.6);
}
.img-hover-caption {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 12px;
background: linear-gradient(transparent, rgba(0,0,0,0.8));
color: white;
font-weight: 600;
font-size: 14px;
transform: translateY(100%);
transition: transform 0.3s;
}
.img-hover-item:hover .img-hover-caption {
transform: translateY(0);
}
</style>
</head>
<body>
<!-- Opacity Levels -->
<div class="section">
<h2>Opacity Levels</h2>
<span class="label">opacity: 0 → 1</span>
<div class="demo-row">
<div class="demo-box">
<div class="demo-card opacity-1">1.0</div>
<p style="font-size:13px;">opacity: 1</p>
</div>
<div class="demo-box">
<div class="demo-card opacity-075">0.75</div>
<p style="font-size:13px;">opacity: 0.75</p>
</div>
<div class="demo-box">
<div class="demo-card opacity-05">0.5</div>
<p style="font-size:13px;">opacity: 0.5</p>
</div>
<div class="demo-box">
<div class="demo-card opacity-025">0.25</div>
<p style="font-size:13px;">opacity: 0.25</p>
</div>
<div class="demo-box">
<div class="demo-card opacity-0">0.0</div>
<p style="font-size:13px;">opacity: 0 (invisible but still present)</p>
</div>
</div>
</div>
<!-- Visibility vs Opacity vs Display -->
<div class="section">
<h2>Comparison: opacity:0 vs visibility:hidden vs display:none</h2>
<p style="color:#64748b; margin-bottom:15px;">Hover over the container to see the difference. <strong>display:none</strong> removes the element from the layout, while the others still take up space.</p>
<div class="transition-group trans-group">
<div class="trans-box">
<h4>opacity: 0</h4>
<div class="trans-target trans-opacity">
Smooth Transition ✓
</div>
<p style="font-size:12px; color:#64748b; margin-top:10px;">Still clickable. Takes up space. Smooth transition.</p>
</div>
<div class="trans-box">
<h4>visibility: hidden</h4>
<div class="trans-target trans-visibility">
Smooth Transition ✓
</div>
<p style="font-size:12px; color:#64748b; margin-top:10px;">Cannot be clicked. Takes up space. Smooth transition.</p>
</div>
<div class="trans-box">
<h4>display: none</h4>
<div class="trans-target trans-display">
Cannot Be Animated ✗
</div>
<p style="font-size:12px; color:#64748b; margin-top:10px;">Cannot be clicked. Takes no space. Cannot be transitioned.</p>
</div>
</div>
</div>
<!-- Hover Overlay Effects -->
<div class="section">
<h2>Hover Overlay Effect</h2>
<span class="label">opacity transition on an overlay</span>
<div class="hover-group">
<div class="hover-card">
<div class="hover-card-img"></div>
<div class="hover-card-overlay">View Details</div>
<div class="hover-card-body">
<h4>Product A</h4>
<p>Short product description</p>
</div>
</div>
<div class="hover-card">
<div class="hover-card-img"></div>
<div class="hover-card-overlay">View Details</div>
<div class="hover-card-body">
<h4>Product B</h4>
<p>Short product description</p>
</div>
</div>
<div class="hover-card">
<div class="hover-card-img"></div>
<div class="hover-card-overlay">View Details</div>
<div class="hover-card-body">
<h4>Product C</h4>
<p>Short product description</p>
</div>
</div>
</div>
</div>
<!-- Disabled States -->
<div class="section">
<h2>Disabled States</h2>
<div class="disabled-group">
<button class="btn btn-primary">Active Button</button>
<button class="btn btn-disabled">opacity: 0.6</button>
<button class="btn btn-disabled" disabled>disabled attribute</button>
</div>
<p style="font-size:13px; color:#94a3b8; margin-top:10px;">
Use <code>opacity: 0.6</code> + <code>cursor: not-allowed</code> + the <code>disabled</code> attribute for a proper disabled state.
</p>
</div>
<!-- Loading Overlay -->
<div class="section">
<h2>Loading Overlay</h2>
<span class="label">opacity + visibility transition for a loading state</span>
<div class="loading-demo" id="loadingDemo">
<div class="loading-content">
<h4>User Data</h4>
<p>This is the card content that will be shown after loading finishes. The loading overlay uses opacity and visibility for a smooth transition.</p>
<p style="margin-top:10px;">Click the button below to toggle the loading state.</p>
</div>
<div class="loading-overlay" id="loadingOverlay">
<div class="spinner"></div>
<span class="loading-text">Loading data...</span>
</div>
</div>
<button class="btn-toggle" onclick="toggleLoading()">Toggle Loading</button>
</div>
<!-- Fade Effects -->
<div class="section">
<h2>Fade Effects</h2>
<div class="fade-demo">
<div class="fade-item fade-in">
<div class="fade-box">Fade In</div>
<p style="font-size:13px;">opacity: 0 → 1</p>
</div>
<div class="fade-item fade-slide">
<div class="fade-box">Fade + Slide</div>
<p style="font-size:13px;">opacity + transform</p>
</div>
<div class="fade-item fade-scale">
<div class="fade-box">Fade + Scale</div>
<p style="font-size:13px;">opacity + scale</p>
</div>
</div>
</div>
<!-- Image Hover -->
<div class="section">
<h2>Image Hover Effects</h2>
<div class="img-hover-group">
<div class="img-hover-item">
<img src="https://picsum.photos/400/300?random=70" alt="img1">
<div class="img-hover-caption">Image Title 1</div>
</div>
<div class="img-hover-item">
<img src="https://picsum.photos/400/300?random=71" alt="img2">
<div class="img-hover-caption">Image Title 2</div>
</div>
<div class="img-hover-item">
<img src="https://picsum.photos/400/300?random=72" alt="img3">
<div class="img-hover-caption">Image Title 3</div>
</div>
</div>
</div>
<script>
function toggleLoading() {
const overlay = document.getElementById('loadingOverlay');
overlay.classList.toggle('active');
}
</script>
</body>
</html>
Code Explanation
opacity
Controls an element's transparency. Values range from 0 (fully transparent) to 1 (fully opaque):
/* Fully transparent — the element is invisible but still present */
.invisible { opacity: 0; }
/* Semi-transparent */
.faded { opacity: 0.5; }
/* Fully opaque (default) */
.visible { opacity: 1; }
Characteristics of opacity: 0:
- The element still takes up layout space
- The element can still be clicked (this can be a bug!)
- The element can be animated with transition
- Inherited by all children — all children become transparent too
visibility
Controls whether an element is visible, without affecting the transparency of its children:
/* Hidden but still takes up space */
.hidden { visibility: hidden; }
/* Visible (default) */
.shown { visibility: visible; }
Characteristics of visibility: hidden:
- The element still takes up layout space
- The element cannot be clicked
- Can be animated (transition from visible to hidden, or vice versa)
- Children can override it — a child can remain visible
visibility: collapse
Specific to table elements — hides a row/column without taking up space:
tr { visibility: collapse; }
display: none
Not opacity or visibility, but often compared with them:
.invisible { display: none; }
- The element takes no space — removed from the layout
- Cannot be animated (transition does not work)
- Cannot be clicked
Comparison
/* Can be animated with transition */
.fade-out {
opacity: 0;
transition: opacity 0.3s;
}
/* Can be animated with transition */
.hide-soft {
visibility: hidden;
transition: visibility 0.3s;
}
/* CANNOT be animated */
.no-transition {
display: none; /* Transition does not work */
}
Opacity Transition
/* Fade in on hover */
.element {
opacity: 0;
transition: opacity 0.3s ease;
}
.element:hover {
opacity: 1;
}
/* Fade in on page load */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-in {
animation: fadeIn 0.5s ease forwards;
}
Everyday Use Cases
1. Disabled Button State
<!DOCTYPE html>
<html lang="en">
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; padding: 30px; background: #f0f2f5; }
.form-group {
display: flex;
flex-direction: column;
gap: 10px;
max-width: 300px;
}
.form-input {
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 15px;
transition: border-color 0.3s;
}
.form-input:focus {
border-color: #667eea;
outline: none;
}
.submit-btn {
padding: 12px;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
font-size: 15px;
cursor: pointer;
transition: all 0.3s;
}
.submit-btn:disabled {
background: #e2e8f0;
color: #94a3b8;
cursor: not-allowed;
opacity: 0.7;
}
</style>
</head>
<body>
<div class="form-group">
<input type="text" class="form-input" placeholder="Enter your name">
<input type="email" class="form-input" placeholder="Enter your email">
<button class="submit-btn" disabled>Send (Disabled)</button>
</div>
</body>
</html>
2. Toast Notification
<!DOCTYPE html>
<html lang="en">
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; padding: 30px; background: #f0f2f5; }
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 1060;
display: flex;
flex-direction: column;
gap: 10px;
}
.toast {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 20px;
background: white;
border-radius: 10px;
box-shadow: 0 8px 30px rgba(0,0,0,0.12);
font-size: 14px;
opacity: 0;
visibility: hidden;
transform: translateX(100%);
transition: all 0.4s;
border-left: 4px solid #43e97b;
}
.toast.show {
opacity: 1;
visibility: visible;
transform: translateX(0);
}
.toast.error { border-left-color: #f5576c; }
.toast.warning { border-left-color: #fbbf24; }
</style>
</head>
<body>
<button onclick="showToast('success')" style="padding:10px 20px;background:#43e97b;color:white;border:none;border-radius:8px;cursor:pointer;margin-right:10px;">
Success Toast
</button>
<button onclick="showToast('error')" style="padding:10px 20px;background:#f5576c;color:white;border:none;border-radius:8px;cursor:pointer;">
Error Toast
</button>
<div class="toast-container">
<div class="toast" id="toast-success">✓ Saved successfully!</div>
<div class="toast error" id="toast-error">✕ An error occurred!</div>
</div>
<script>
function showToast(type) {
const toast = document.getElementById('toast-' + type);
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 3000);
}
</script>
</body>
</html>
Explanation: The toast uses opacity: 0, visibility: hidden, and transform: translateX(100%) as its hidden state. The show class changes all three at the same time for a smooth entrance animation.
3. Skeleton Loading
<!DOCTYPE html>
<html lang="en">
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; padding: 30px; background: #f0f2f5; }
.skeleton-card {
width: 350px;
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
}
.skeleton-img {
width: 100%;
height: 200px;
background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
.skeleton-body {
padding: 20px;
}
.skeleton-line {
height: 14px;
background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 4px;
margin-bottom: 10px;
}
.skeleton-line.w-75 { width: 75%; }
.skeleton-line.w-50 { width: 50%; }
.skeleton-line.w-90 { width: 90%; }
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.real-content {
display: none;
padding: 20px;
}
.loaded .skeleton-img,
.loaded .skeleton-body {
opacity: 0;
position: absolute;
}
.loaded .real-content {
display: block;
}
.loaded-card {
position: relative;
overflow: hidden;
}
</style>
</head>
<body>
<div class="skeleton-card">
<div class="skeleton-img"></div>
<div class="skeleton-body">
<div class="skeleton-line w-90"></div>
<div class="skeleton-line w-75"></div>
<div class="skeleton-line w-50"></div>
</div>
</div>
</body>
</html>
Conclusion
Opacity & Visibility are properties that are often misunderstood. Key points:
opacity: 0— Element is transparent, still takes up space, can still be clicked, can be animatedvisibility: hidden— Element is hidden, still takes up space, cannot be clicked, can be animateddisplay: none— Element is gone from the layout, takes no space, cannot be animated- Fade effects — Combine opacity with transform for attractive animations
- Loading overlay — Use opacity + visibility for an overlay that can be toggled smoothly
- Disabled states — Opacity 0.5-0.7 + cursor:not-allowed for disabled buttons
For smooth show/hide animations, use opacity or visibility (not display: none). To remove an element from the layout entirely, use display: none.