← Library

UI

Menu Icon — Cyclic Morph on Hover

Vanilla JSCSSSVG

A row of hamburger icons (≡) that morph through a 3-step cycle on hover: ✕ → ← → CLOSE → ✕ → … Each icon tracks its own state independently. State persists after hover, creating a progressive visual trail across the row.

Build an animated hamburger menu icon row in Vanilla JS + CSS.                                                                                       
                                                                                                                                                          
  ## Concept                                                                                                                                              
  A horizontal row of 8 hamburger icons (≡). As the cursor passes over each icon, it morphs to the next state in a repeating 3-step cycle: ✕ (close) → ←  
  (back arrow) → CLOSE (text label) → ✕ → ← → CLOSE…                                                                                                      
  Each icon tracks its own state index independently. State persists after hover (no auto-revert).                                                      
                                                                                                                                                          
  ## HTML                                                                                                                                                 
  ```html                                                                                                                                                 
  <div class="icon-row">                                                                                                                                  
    <div class="menu-icon" data-state="0">                                                                                                                
      <svg viewBox="0 0 24 24">                                                                                                                           
        <!-- 3 lines for hamburger -->                                                                                                                    
        <line class="l1" x1="3" y1="7"  x2="21" y2="7"  />                                                                                                
        <line class="l2" x1="3" y1="12" x2="21" y2="12" />                                                                                                
        <line class="l3" x1="3" y1="17" x2="21" y2="17" />                                                                                                
        <!-- X arms -->                                                                                                                                   
        <line class="x1" x1="5" y1="5"  x2="19" y2="19" />                                                                                                
        <line class="x2" x1="19" y1="5"  x2="5" y2="19" />                                                                                                
        <!-- Arrow back: shaft + head -->                                                                                                                 
        <line class="arrow-shaft" x1="20" y1="12" x2="4"  y2="12" />                                                                                      
        <polyline class="arrow-head" points="10,6 4,12 10,18" />                                                                                          
        <!-- CLOSE label (foreignObject or text element) -->                                                                                              
        <text class="close-text" x="12" y="16" text-anchor="middle">CLOSE</text>                                                                          
      </svg>                                                                                                                                              
    </div>                                                                                                                                                
    <!-- repeat x8 -->                                                                                                                                    
  </div>                                                                                                                                                  
                                                                                                                                                          
  States (per icon, cycles on each hover)                                                                                                                 
                                                                                                                                                        
  - State 0: hamburger (3 horizontal lines, hide X/arrow/text)                                                                                            
  - State 1: close X (diagonal lines, hide lines/arrow/text)                                                                                            
  - State 2: back arrow (shaft + arrowhead left, hide lines/X/text)                                                                                       
  - State 3: "CLOSE" text label (show text element, hide everything else)                                                                                 
                                                                                                                                                          
  CSS — SVG element visibility per state                                                                                                                  
                                                                                                                                                          
  Use data-state attribute on .menu-icon to show/hide SVG parts:                                                                                          
  /* Default: hide everything non-hamburger */                                                                                                          
  .menu-icon .x1, .menu-icon .x2,                                                                                                                         
  .menu-icon .arrow-shaft, .menu-icon .arrow-head,                                                                                                        
  .menu-icon .close-text { opacity: 0; }                                                                                                                  
                                                                                                                                                          
  .menu-icon[data-state="0"] .l1,                                                                                                                         
  .menu-icon[data-state="0"] .l2,                                                                                                                         
  .menu-icon[data-state="0"] .l3 { opacity: 1; }                                                                                                          
                                                                                                                                                          
  .menu-icon[data-state="1"] .x1,                                                                                                                         
  .menu-icon[data-state="1"] .x2 { opacity: 1; }                                                                                                          
  .menu-icon[data-state="1"] .l1,                                                                                                                         
  .menu-icon[data-state="1"] .l2,                                                                                                                         
  .menu-icon[data-state="1"] .l3 { opacity: 0; }                                                                                                          
                                                                                                                                                          
  .menu-icon[data-state="2"] .arrow-shaft,                                                                                                                
  .menu-icon[data-state="2"] .arrow-head { opacity: 1; }                                                                                                  
                                                                                                                                                          
  .menu-icon[data-state="3"] .close-text { opacity: 1; }                                                                                                  
                                                                                                                                                          
  /* Smooth transition */                                                                                                                                 
  .menu-icon svg line, .menu-icon svg polyline, .menu-icon svg text {                                                                                     
    transition: opacity 0.15s ease, d 0.3s ease;                                                                                                        
  }                                                                                                                                                       
                                                                                                                                                          
  JS — Cycle on mouseenter                                                                                                                                
                                                                                                                                                          
  const STATES = 4; // 0=burger, 1=X, 2=arrow, 3=CLOSE                                                                                                    
                                                                                                                                                          
  document.querySelectorAll(".menu-icon").forEach(icon => {                                                                                               
    icon.addEventListener("mouseenter", () => {                                                                                                           
      const current = parseInt(icon.dataset.state);                                                                                                       
      icon.dataset.state = (current + 1) % STATES;                                                                                                        
    });                                                                                                                                                   
  });                                                                                                                                                     
                                                                                                                                                          
  Styling                                                                                                                                                 
                              
  - Icon size: ~40px × 40px, spacing: 16px gap                                                                                                            
  - SVG stroke: #000, stroke-width: 2, stroke-linecap: round
  - Background: white (#fff)                                                                                                                              
  - Font for CLOSE text: bold monospace ~10px, fill: #000                                                                                                 
  - icon-row: display flex, align-items center, justify-content center, min-height 100vh