UI Component
✨ NewLayout
Animated Wave Mega Footer with Newsletter
A complete website footer topped with layered animated SVG waves, a newsletter call-to-action with validation and loading state, four link columns, brand-coloured social icons, a live status indicator and back-to-top button. On mobile the link columns turn into an accordion. Pure HTML, CSS and vanilla JavaScript.
September 26, 20263 views
- Footer
- footer
- svg-waves
- newsletter
- responsive
- accordion
- website-layout
Features Included
Three layered SVG waves moving at different speeds (seamless loop)
Newsletter CTA with email validation, shake on error and loading spinner
Brand block with logo, description and 5 social icons that light up in brand colours
Four link columns with animated underlines and 'New' / 'Hiring' pills
Status indicator, auto-updating copyright year and back-to-top button
Link columns collapse into an accordion on mobile
Animated Wave Mega Footer with Newsletter
A footer that finishes your site in style — flowing SVG waves, newsletter signup, mega link columns and a mobile accordion. HTML, CSS and vanilla JavaScript.
What's Included
- Three layered SVG waves moving at different speeds (seamless loop)
- Newsletter CTA with email validation, shake on error and loading spinner
- Brand block with logo, description and 5 social icons that light up in brand colours
- Four link columns with animated underlines and 'New' / 'Hiring' pills
- Status indicator, auto-updating copyright year and back-to-top button
- Link columns collapse into an accordion on mobile
Dependencies
- Google Fonts — Poppins https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap
Already linked inside the component's files — no extra setup needed.
Desktop Preview
HTML
html1<!doctype html> 2<html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <title>Wave Mega Footer | MetaSkillGroup</title> 7 <link rel="stylesheet" href="style.css" /> 8 </head> 9 <body> 10 <!-- created by metaskillgroup-team --> 11 <main class="page-demo"> 12 <p>Scroll down to see the footer ↓</p> 13 </main> 14 15 <footer class="footer"> 16 <!-- Animated layered waves --> 17 <div class="waves" aria-hidden="true"> 18 <svg viewBox="0 0 1440 120" preserveAspectRatio="none"> 19 <defs> 20 <path id="wave" d="M0 60 Q 360 110 720 60 T 1440 60 T 2160 60 T 2880 60 V120 H0Z" /> 21 </defs> 22 <g class="wave-layer w1"><use href="#wave" /></g> 23 <g class="wave-layer w2"><use href="#wave" /></g> 24 <g class="wave-layer w3"><use href="#wave" /></g> 25 </svg> 26 </div> 27 28 <div class="footer-body"> 29 <!-- CTA band --> 30 <section class="cta"> 31 <div> 32 <h2>Ready to build something great?</h2> 33 <p>Join 40,000+ developers getting one practical tip every week.</p> 34 </div> 35 <form class="newsletter" novalidate> 36 <div class="nl-field"> 37 <svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path d="M4 6h16v12H4z M4 7l8 6 8-6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg> 38 <input type="email" placeholder="you@example.com" aria-label="Email address" required /> 39 <button type="submit"> 40 <span class="nl-text">Subscribe</span> 41 <span class="nl-spinner" aria-hidden="true"></span> 42 </button> 43 </div> 44 <p class="nl-msg" role="status"></p> 45 </form> 46 </section> 47 48 <!-- Link columns --> 49 <div class="columns"> 50 <div class="brand"> 51 <a href="#" class="logo"> 52 <span class="logo-mark">M</span> 53 MetaSkill 54 </a> 55 <p>Free, production-ready UI components and templates for modern web projects.</p> 56 <ul class="socials"> 57 <li><a href="#" aria-label="GitHub" style="--brand: #f0f6fc"><svg viewBox="0 0 24 24"><path d="M12 2a10 10 0 0 0-3.16 19.49c.5.09.68-.22.68-.48v-1.7c-2.78.6-3.37-1.34-3.37-1.34-.45-1.16-1.1-1.46-1.1-1.46-.9-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.9 1.52 2.34 1.08 2.91.83.09-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.94 0-1.1.39-1.99 1.03-2.69-.1-.25-.45-1.27.1-2.64 0 0 .84-.27 2.75 1.02a9.6 9.6 0 0 1 5 0c1.91-1.29 2.75-1.02 2.75-1.02.55 1.37.2 2.39.1 2.64.64.7 1.03 1.6 1.03 2.69 0 3.84-2.34 4.69-4.57 4.93.36.31.68.92.68 1.85v2.75c0 .27.18.58.69.48A10 10 0 0 0 12 2z" fill="currentColor"/></svg></a></li> 58 <li><a href="#" aria-label="X / Twitter" style="--brand: #e7e9ea"><svg viewBox="0 0 24 24"><path d="M17.8 3h3.1l-6.8 7.8L22 21h-6.3l-4.9-6.4L5.2 21H2.1l7.3-8.3L1.8 3h6.4l4.4 5.8L17.8 3zm-1.1 16.2h1.7L7.4 4.7H5.6l11.1 14.5z" fill="currentColor"/></svg></a></li> 59 <li><a href="#" aria-label="LinkedIn" style="--brand: #0a66c2"><svg viewBox="0 0 24 24"><path d="M4.98 3.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5zM3 9h4v12H3zM9 9h3.8v1.7h.05c.53-1 1.83-2.05 3.77-2.05 4.03 0 4.78 2.65 4.78 6.1V21h-4v-5.6c0-1.34-.03-3.06-1.86-3.06-1.87 0-2.15 1.46-2.15 2.96V21H9z" fill="currentColor"/></svg></a></li> 60 <li><a href="#" aria-label="YouTube" style="--brand: #ff0033"><svg viewBox="0 0 24 24"><path d="M23 7.2a3 3 0 0 0-2.1-2.1C19 4.6 12 4.6 12 4.6s-7 0-8.9.5A3 3 0 0 0 1 7.2 31 31 0 0 0 .5 12a31 31 0 0 0 .5 4.8 3 3 0 0 0 2.1 2.1c1.9.5 8.9.5 8.9.5s7 0 8.9-.5a3 3 0 0 0 2.1-2.1 31 31 0 0 0 .5-4.8 31 31 0 0 0-.5-4.8zM9.75 15.02V8.98L15.5 12l-5.75 3.02z" fill="currentColor"/></svg></a></li> 61 <li><a href="#" aria-label="Instagram" style="--brand: #e1306c"><svg viewBox="0 0 24 24"><path d="M12 7a5 5 0 1 0 0 10 5 5 0 0 0 0-10zm0 8.2a3.2 3.2 0 1 1 0-6.4 3.2 3.2 0 0 1 0 6.4zM17.3 5.5a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4zM12 2c-2.7 0-3 0-4.1.06C4.2 2.2 2.2 4.2 2.06 7.9 2 9 2 9.3 2 12s0 3 .06 4.1c.14 3.7 2.14 5.7 5.84 5.84C9 22 9.3 22 12 22s3 0 4.1-.06c3.7-.14 5.7-2.14 5.84-5.84C22 15 22 14.7 22 12s0-3-.06-4.1C21.8 4.2 19.8 2.2 16.1 2.06 15 2 14.7 2 12 2zm0 1.8c2.67 0 2.99 0 4.04.06 2.7.12 3.97 1.4 4.1 4.1.05 1.05.06 1.37.06 4.04s0 2.99-.06 4.04c-.13 2.7-1.4 3.97-4.1 4.1-1.05.05-1.37.06-4.04.06s-2.99 0-4.04-.06c-2.71-.13-3.97-1.41-4.1-4.1C3.81 14.99 3.8 14.67 3.8 12s0-2.99.06-4.04c.13-2.7 1.4-3.97 4.1-4.1C9.01 3.81 9.33 3.8 12 3.8z" fill="currentColor"/></svg></a></li> 62 </ul> 63 </div> 64 65 <nav class="col" aria-label="Product"> 66 <h3>Product<span class="col-toggle" aria-hidden="true"></span></h3> 67 <ul> 68 <li><a href="#">Components</a></li> 69 <li><a href="#">Templates</a></li> 70 <li><a href="#">Projects <span class="pill">New</span></a></li> 71 <li><a href="#">Changelog</a></li> 72 <li><a href="#">Roadmap</a></li> 73 </ul> 74 </nav> 75 76 <nav class="col" aria-label="Resources"> 77 <h3>Resources<span class="col-toggle" aria-hidden="true"></span></h3> 78 <ul> 79 <li><a href="#">Documentation</a></li> 80 <li><a href="#">Blog</a></li> 81 <li><a href="#">Tutorials</a></li> 82 <li><a href="#">Community</a></li> 83 <li><a href="#">Help center</a></li> 84 </ul> 85 </nav> 86 87 <nav class="col" aria-label="Company"> 88 <h3>Company<span class="col-toggle" aria-hidden="true"></span></h3> 89 <ul> 90 <li><a href="#">About us</a></li> 91 <li><a href="#">Careers <span class="pill pill-hiring">Hiring</span></a></li> 92 <li><a href="#">Mentorship</a></li> 93 <li><a href="#">Contact</a></li> 94 <li><a href="#">Press kit</a></li> 95 </ul> 96 </nav> 97 98 <nav class="col" aria-label="Legal"> 99 <h3>Legal<span class="col-toggle" aria-hidden="true"></span></h3> 100 <ul> 101 <li><a href="#">Privacy policy</a></li> 102 <li><a href="#">Terms of service</a></li> 103 <li><a href="#">Cookie policy</a></li> 104 <li><a href="#">Licenses</a></li> 105 </ul> 106 </nav> 107 </div> 108 109 <!-- Bottom bar --> 110 <div class="bottom"> 111 <p>© <span class="year"></span> MetaSkillGroup. All rights reserved.</p> 112 <div class="status"> 113 <span class="status-dot"></span> 114 All systems operational 115 </div> 116 <button class="to-top" aria-label="Back to top"> 117 <svg viewBox="0 0 24 24" width="20" height="20"><path d="M12 19V5M5 12l7-7 7 7" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg> 118 </button> 119 </div> 120 </div> 121 </footer> 122 123 <script src="script.js"></script> 124 </body> 125</html> 126
CSS
css1/* Importing Google Fonts - Poppins */ 2@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap"); 3 4* { 5 margin: 0; 6 padding: 0; 7 box-sizing: border-box; 8 font-family: "Poppins", sans-serif; 9} 10 11:root { 12 --page: #eef4ff; 13 --footer: #0b1437; 14 --footer-2: #111c4a; 15 --text: #e6ebff; 16 --muted: #98a3cc; 17 --border: rgba(255, 255, 255, 0.09); 18 --accent: #38bdf8; 19 --accent-2: #818cf8; 20 --ok: #22c55e; 21 --error: #fb7185; 22} 23 24body { 25 background: var(--page); 26} 27 28a { 29 color: inherit; 30 text-decoration: none; 31} 32 33button { 34 font: inherit; 35 color: inherit; 36 border: none; 37 background: none; 38 cursor: pointer; 39} 40 41.page-demo { 42 min-height: 70vh; 43 display: grid; 44 place-items: center; 45 color: #5b6b96; 46 font-weight: 500; 47} 48 49/* ---------- Waves ---------- */ 50.footer { 51 position: relative; 52 color: var(--text); 53} 54 55.waves { 56 height: 120px; 57 margin-bottom: -2px; 58 overflow: hidden; 59} 60 61.waves svg { 62 width: 100%; 63 height: 100%; 64 display: block; 65} 66 67.wave-layer { 68 animation: wave linear infinite; 69} 70 71.w1 { 72 fill: rgba(56, 189, 248, 0.25); 73 animation-duration: 18s; 74} 75 76.w2 { 77 fill: rgba(129, 140, 248, 0.35); 78 animation-duration: 12s; 79 transform: translateY(12px); 80} 81 82.w3 { 83 fill: var(--footer); 84 animation-duration: 8s; 85 transform: translateY(24px); 86} 87 88@keyframes wave { 89 from { 90 translate: 0 0; 91 } 92 to { 93 translate: -1440px 0; 94 } 95} 96 97.w2 { 98 animation-name: wave-rev; 99} 100 101@keyframes wave-rev { 102 from { 103 translate: -1440px 0; 104 } 105 to { 106 translate: 0 0; 107 } 108} 109 110.footer-body { 111 background: var(--footer); 112 padding: 20px 24px 28px; 113} 114 115.footer-body > * { 116 max-width: 1200px; 117 margin-left: auto; 118 margin-right: auto; 119} 120 121/* ---------- CTA band ---------- */ 122.cta { 123 display: flex; 124 justify-content: space-between; 125 align-items: center; 126 gap: 32px; 127 flex-wrap: wrap; 128 padding: 32px; 129 border-radius: 24px; 130 background: 131 radial-gradient(circle at 0% 0%, rgba(56, 189, 248, 0.25), transparent 50%), 132 radial-gradient(circle at 100% 100%, rgba(129, 140, 248, 0.3), transparent 50%), 133 var(--footer-2); 134 border: 1px solid var(--border); 135} 136 137.cta h2 { 138 font-size: clamp(1.3rem, 3vw, 1.75rem); 139 font-weight: 700; 140 letter-spacing: -0.02em; 141} 142 143.cta p { 144 margin-top: 6px; 145 color: var(--muted); 146 font-size: 0.95rem; 147} 148 149.newsletter { 150 flex: 1; 151 max-width: 460px; 152 min-width: 260px; 153} 154 155.nl-field { 156 display: flex; 157 align-items: center; 158 gap: 10px; 159 padding: 6px 6px 6px 16px; 160 border-radius: 14px; 161 background: rgba(255, 255, 255, 0.06); 162 border: 1.5px solid var(--border); 163 color: var(--muted); 164 transition: 165 border-color 0.2s ease, 166 box-shadow 0.2s ease; 167} 168 169.nl-field:focus-within { 170 border-color: var(--accent); 171 box-shadow: 0 0 0 4px rgba(56, 189, 248, 0.15); 172} 173 174.newsletter.invalid .nl-field { 175 border-color: var(--error); 176 animation: shake 0.35s; 177} 178 179@keyframes shake { 180 25% { 181 transform: translateX(-5px); 182 } 183 75% { 184 transform: translateX(5px); 185 } 186} 187 188.nl-field input { 189 flex: 1; 190 min-width: 0; 191 padding: 10px 0; 192 background: none; 193 border: none; 194 outline: none; 195 color: var(--text); 196 font-size: 0.95rem; 197} 198 199.nl-field input::placeholder { 200 color: #6b77a3; 201} 202 203.nl-field button { 204 position: relative; 205 padding: 11px 20px; 206 border-radius: 10px; 207 font-weight: 600; 208 font-size: 0.92rem; 209 color: #0b1437; 210 background: linear-gradient(90deg, var(--accent), var(--accent-2)); 211 transition: 212 transform 0.2s ease, 213 box-shadow 0.2s ease; 214} 215 216.nl-field button:hover { 217 transform: translateY(-1px); 218 box-shadow: 0 8px 20px rgba(56, 189, 248, 0.35); 219} 220 221.nl-spinner { 222 position: absolute; 223 inset: 0; 224 width: 18px; 225 height: 18px; 226 margin: auto; 227 border-radius: 50%; 228 border: 2.5px solid rgba(11, 20, 55, 0.3); 229 border-top-color: #0b1437; 230 animation: spin 0.7s linear infinite; 231 display: none; 232} 233 234.newsletter.loading .nl-text { 235 visibility: hidden; 236} 237 238.newsletter.loading .nl-spinner { 239 display: block; 240} 241 242@keyframes spin { 243 to { 244 transform: rotate(360deg); 245 } 246} 247 248.nl-msg { 249 min-height: 1.3em; 250 margin-top: 8px; 251 padding-left: 4px; 252 font-size: 0.82rem; 253} 254 255.nl-msg.ok { 256 color: var(--ok); 257} 258 259.nl-msg.bad { 260 color: var(--error); 261} 262 263/* ---------- Columns ---------- */ 264.columns { 265 display: grid; 266 grid-template-columns: 1.6fr repeat(4, 1fr); 267 gap: 32px; 268 padding: 56px 0 44px; 269} 270 271.logo { 272 display: inline-flex; 273 align-items: center; 274 gap: 10px; 275 font-size: 1.25rem; 276 font-weight: 700; 277} 278 279.logo-mark { 280 display: grid; 281 place-items: center; 282 width: 36px; 283 height: 36px; 284 border-radius: 10px; 285 color: #0b1437; 286 background: linear-gradient(135deg, var(--accent), var(--accent-2)); 287} 288 289.brand p { 290 max-width: 290px; 291 margin: 16px 0 22px; 292 color: var(--muted); 293 font-size: 0.9rem; 294 line-height: 1.7; 295} 296 297.socials { 298 display: flex; 299 gap: 10px; 300 list-style: none; 301} 302 303.socials a { 304 display: grid; 305 place-items: center; 306 width: 40px; 307 height: 40px; 308 border-radius: 12px; 309 color: var(--muted); 310 background: rgba(255, 255, 255, 0.05); 311 border: 1px solid var(--border); 312 transition: 313 color 0.25s ease, 314 background 0.25s ease, 315 transform 0.25s ease, 316 border-color 0.25s ease; 317} 318 319.socials svg { 320 width: 18px; 321 height: 18px; 322} 323 324.socials a:hover { 325 color: #fff; 326 background: var(--brand); 327 border-color: var(--brand); 328 transform: translateY(-4px); 329} 330 331/* Light brand colours need dark icons */ 332.socials a[aria-label="GitHub"]:hover, 333.socials a[aria-label="X / Twitter"]:hover { 334 color: #0b1437; 335} 336 337.col h3 { 338 display: flex; 339 justify-content: space-between; 340 align-items: center; 341 margin-bottom: 16px; 342 font-size: 0.95rem; 343 font-weight: 600; 344} 345 346.col ul { 347 list-style: none; 348 display: flex; 349 flex-direction: column; 350 gap: 12px; 351} 352 353.col a { 354 position: relative; 355 display: inline-flex; 356 align-items: center; 357 gap: 8px; 358 font-size: 0.9rem; 359 color: var(--muted); 360 transition: 361 color 0.2s ease, 362 transform 0.2s ease; 363} 364 365/* Underline that grows from the left */ 366.col a::after { 367 content: ""; 368 position: absolute; 369 left: 0; 370 bottom: -3px; 371 width: 100%; 372 height: 1.5px; 373 background: var(--accent); 374 transform: scaleX(0); 375 transform-origin: left; 376 transition: transform 0.3s ease; 377} 378 379.col a:hover { 380 color: var(--text); 381 transform: translateX(3px); 382} 383 384.col a:hover::after { 385 transform: scaleX(1); 386} 387 388.pill { 389 padding: 1px 8px; 390 border-radius: 99px; 391 font-size: 0.68rem; 392 font-weight: 600; 393 color: var(--accent); 394 background: rgba(56, 189, 248, 0.14); 395} 396 397.pill-hiring { 398 color: var(--ok); 399 background: rgba(34, 197, 94, 0.14); 400} 401 402.col-toggle { 403 display: none; 404} 405 406/* ---------- Bottom bar ---------- */ 407.bottom { 408 display: flex; 409 justify-content: space-between; 410 align-items: center; 411 gap: 16px; 412 flex-wrap: wrap; 413 padding-top: 24px; 414 border-top: 1px solid var(--border); 415 font-size: 0.85rem; 416 color: var(--muted); 417} 418 419.status { 420 display: flex; 421 align-items: center; 422 gap: 8px; 423} 424 425.status-dot { 426 position: relative; 427 width: 8px; 428 height: 8px; 429 border-radius: 50%; 430 background: var(--ok); 431} 432 433.status-dot::after { 434 content: ""; 435 position: absolute; 436 inset: 0; 437 border-radius: 50%; 438 background: var(--ok); 439 animation: ping 1.8s ease-out infinite; 440} 441 442@keyframes ping { 443 to { 444 transform: scale(3); 445 opacity: 0; 446 } 447} 448 449.to-top { 450 display: grid; 451 place-items: center; 452 width: 44px; 453 height: 44px; 454 border-radius: 50%; 455 background: rgba(255, 255, 255, 0.06); 456 border: 1px solid var(--border); 457 transition: 458 background 0.2s ease, 459 transform 0.2s ease; 460} 461 462.to-top:hover { 463 background: var(--accent); 464 color: #0b1437; 465 transform: translateY(-4px); 466} 467 468/* ---------- Responsive ---------- */ 469@media (max-width: 1000px) { 470 .columns { 471 grid-template-columns: repeat(4, 1fr); 472 } 473 474 .brand { 475 grid-column: 1 / -1; 476 } 477} 478 479/* On phones the link columns become an accordion */ 480@media (max-width: 640px) { 481 .waves { 482 height: 70px; 483 } 484 485 .cta { 486 padding: 24px; 487 } 488 489 .columns { 490 grid-template-columns: 1fr; 491 gap: 0; 492 padding-top: 40px; 493 } 494 495 .brand { 496 margin-bottom: 24px; 497 } 498 499 .col { 500 border-bottom: 1px solid var(--border); 501 } 502 503 .col h3 { 504 margin: 0; 505 padding: 16px 0; 506 cursor: pointer; 507 } 508 509 .col-toggle { 510 display: block; 511 position: relative; 512 width: 14px; 513 height: 14px; 514 } 515 516 .col-toggle::before, 517 .col-toggle::after { 518 content: ""; 519 position: absolute; 520 top: 6px; 521 left: 0; 522 width: 14px; 523 height: 2px; 524 border-radius: 2px; 525 background: var(--muted); 526 transition: transform 0.3s ease; 527 } 528 529 .col-toggle::after { 530 transform: rotate(90deg); 531 } 532 533 .col.open .col-toggle::after { 534 transform: rotate(0); 535 } 536 537 .col ul { 538 max-height: 0; 539 overflow: hidden; 540 transition: 541 max-height 0.4s ease, 542 padding 0.4s ease; 543 } 544 545 .col.open ul { 546 max-height: 300px; 547 padding-bottom: 18px; 548 } 549 550 .bottom { 551 flex-direction: column-reverse; 552 text-align: center; 553 } 554} 555 556@media (prefers-reduced-motion: reduce) { 557 *, 558 *::before, 559 *::after { 560 animation: none !important; 561 transition-duration: 0.01ms !important; 562 } 563} 564
JavaScript
javascript1// Current year in the copyright line 2document.querySelector(".year").textContent = new Date().getFullYear(); 3 4// Newsletter form with validation + fake request 5const form = document.querySelector(".newsletter"); 6const input = form.querySelector("input"); 7const msg = form.querySelector(".nl-msg"); 8 9const showMessage = (text, type) => { 10 msg.textContent = text; 11 msg.className = `nl-msg ${type}`; 12}; 13 14form.addEventListener("submit", (e) => { 15 e.preventDefault(); 16 const email = input.value.trim(); 17 const valid = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(email); 18 19 form.classList.remove("invalid"); 20 if (!valid) { 21 // Re-trigger the shake animation 22 void form.offsetWidth; 23 form.classList.add("invalid"); 24 showMessage(email ? "That email doesn't look right." : "Please enter your email.", "bad"); 25 input.focus(); 26 return; 27 } 28 29 form.classList.add("loading"); 30 showMessage("", ""); 31 setTimeout(() => { 32 form.classList.remove("loading"); 33 showMessage(`🎉 You're in! Check ${email} to confirm.`, "ok"); 34 input.value = ""; 35 }, 1200); 36}); 37 38input.addEventListener("input", () => { 39 if (form.classList.contains("invalid")) { 40 form.classList.remove("invalid"); 41 showMessage("", ""); 42 } 43}); 44 45// Back to top 46document.querySelector(".to-top").addEventListener("click", () => { 47 window.scrollTo({ top: 0, behavior: "smooth" }); 48}); 49 50// Mobile accordion for the link columns 51const mobile = window.matchMedia("(max-width: 640px)"); 52 53document.querySelectorAll(".col h3").forEach((heading) => { 54 heading.addEventListener("click", () => { 55 if (!mobile.matches) return; 56 const col = heading.parentElement; 57 const open = col.classList.toggle("open"); 58 heading.setAttribute("aria-expanded", String(open)); 59 }); 60}); 61
Built by Meta Skill Group — copy it, use it, make it yours.
Built With
🌐
HTML5
🎨
CSS3
⚡
JavaScript
Compatibility
Browsers
ChromeOperaBraveFirefoxSafariArcVivaldiEdge
Devices
DesktopSmartwatchTVTabletMobile
Frameworks
Vanilla JSHTML/CSS
Found this useful?

Written by
MetaSkillGroup
September 26, 2026