About this snippet

Add a premium, fluid notification experience to your web app. This component morphs seamlessly from an idle state to a loading indicator, and finally to a success message using spring-physics animations.

Size
3.8 KB
Uses
HTML, CSS, JavaScript
Loads
Nothing external
Views
642
<div class="island-wrapper">
  <!-- The Pill Component -->
  <div class="island" id="dynamic-island">
    
    <!-- State 1: Loading -->
    <div class="island-layer state-loading">
      <div class="spinner"></div>
      <span class="label">Syncing...</span>
    </div>
    
    <!-- State 2: Success -->
    <div class="island-layer state-success">
      <div class="icon-success">
        <svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="3" fill="none"><polyline points="20 6 9 17 4 12"></polyline></svg>
      </div>
      <div class="text-group">
        <span class="title">Success</span>
        <span class="subtitle">Data updated</span>
      </div>
    </div>
    
  </div>

  <!-- Trigger for Demo -->
  <button class="trigger-btn" onclick="triggerAction()">Save Changes</button>
</div>
* { box-sizing: border-box; }
body {
  font-family: system-ui, "Segoe UI", sans-serif;
  background: #f1f5f9;
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: 100px;
}

/* Wrapper to position the island (e.g., fixed at top) */
.island-wrapper {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 60px;
}

.island {
  background: #000;
  color: white;
  border-radius: 50px;
  /* Initial State: Tiny Dot or Hidden */
  width: 120px; 
  height: 36px;
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
  overflow: hidden;
  box-shadow: 0 4px 15px rgba(0,0,0,0.15);
  
  /* The Spring Animation */
  transition: width 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275),
              height 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

/* LAYERS: Absolute positioning to overlay states */
.island-layer {
  position: absolute;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  opacity: 0;
  transform: scale(0.9);
  transition: opacity 0.3s ease, transform 0.3s ease;
  pointer-events: none;
}

/* STATE STYLES */

/* 1. Loading State */
.island.mode-loading {
  width: 140px;
  height: 44px;
}
.island.mode-loading .state-loading {
  opacity: 1;
  transform: scale(1);
}

.spinner {
  width: 18px;
  height: 18px;
  border: 2px solid rgba(255,255,255,0.3);
  border-top-color: white;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

/* 2. Success State */
.island.mode-success {
  width: 220px;
  height: 54px;
  background: #000;
}
.island.mode-success .state-success {
  opacity: 1;
  transform: scale(1);
}

.icon-success {
  color: #4ade80; /* Bright Green */
  display: flex;
}

.text-group {
  display: flex;
  flex-direction: column;
  line-height: 1.1;
  text-align: left;
}
.title { font-weight: 600; font-size: 0.95rem; }
.subtitle { font-size: 0.75rem; color: #9ca3af; }

/* 3. Idle / Hidden State (Optional: could shrink to 0) */
.island:not(.mode-loading):not(.mode-success) {
  width: 0;
  height: 0;
  opacity: 0;
}

.trigger-btn {
  padding: 12px 24px;
  background: #2563eb;
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s;
}
.trigger-btn:hover { background: #1d4ed8; }

@keyframes spin {
  to { transform: rotate(360deg); }
}
const island = document.getElementById("dynamic-island");

function triggerAction() {
  // Prevent double click
  if (island.classList.contains("mode-loading") || island.classList.contains("mode-success")) return;

  // 1. Start Loading
  island.classList.add("mode-loading");

  // Simulate Network Request (1.5 seconds)
  setTimeout(() => {
    
    // 2. Switch to Success
    island.classList.remove("mode-loading");
    island.classList.add("mode-success");
    
    // 3. Close after delay (2.5 seconds)
    setTimeout(() => {
      island.classList.remove("mode-success");
    }, 2500);
    
  }, 1500);
}
Esc
↑↓ moveEnter select

Start from a template

Open an .html file, or HTML, CSS, JS, Markdown, Less or TypeScript files. You can also drop them here. Styles and scripts go into their panes.

Share

Source

Keyboard shortcuts

Save